오늘도 삽질중

Android Play Asset Delivery 구현하기 본문

안드로이드

Android Play Asset Delivery 구현하기

Choi3950 2022. 2. 2. 21:02
반응형

이 기능을 구현 하기 위한 비하인드 스토리가 있다.

필자는 현재 영상관련된 앱을 개발중에 있다. (게임이 아니다)

보통은 영상을 다운로드 방식으로 처리하는게 일반적이지만 테스트 일정으로 인하여 다운로드 기능을 배제하고 급하게 모든 mp4 파일을 앱에 적재해서 내부테스트로 앱을 배포해야 되는 상황이였다.

 

그래서 mp4 영상을 Raw 폴더에 넣고 스토어에 올리려고 하니 앱 사이즈가 200MB 이상은 올릴수가 없다.

급하게 부랴부랴 찾아보니 Play Asset Delivery 또는 Play Feature Delivery 두가지 방식을 찾을 수 있었다.

https://support.google.com/googleplay/android-developer/answer/2481797?hl=ko

 

문제는 작년 8월부터 적용되는 시점이라 자료도 많이 없었고 있다 하더라도 유니티와 결합한 자료가 대부분이였다.

일반적인 앱에서 적용을 하고 싶었는데 코드랩 또한 유니티와 결합된 내용이 많아서 구현에 어려움이 존재했다.

암튼 그중에서 Play Asset Delivery 을 구현하면서 어려웠던 점과 헷갈릴만한 포인트를 정리하고

간단하게 구현 방법을 작성해보았다.

 

참고로 해당 글은 유니티와 결합한 내용이 아니라 앱 안에서만 처리해야 하는 경우로 필자의 경험담을 비롯하여 작성되었습니다.

 

다행히도 해당 샘플이 있어서 매우 큰 도움이 되었다.

https://github.com/Mindinventory/Google-Play-Asset-Delivery-Sample

 

Play Asset Delivery 에 관련하여 세부옵션이나 디테일한 내용은 공식홈페이지 잘 명시되어 있으니 자세한 설명은 생략합니다.

필자는 앱 설치 후 최초 실행 -> MP4 Asset 다운로드 방식으로 구현하였습니다.

즉 앱 설치시에는 Asset 용량은 포함되지 않고 앱 설치가 완료된 후 Asset 리소스를 추후 다운로드하는 방식인 on-demand를 사용하였습니다.

 

헷갈린 포인트

a. 코드랩 또는 공식 홈페이지를 보면 NDK 설정 관련된 이야기가 나오는데 적용해야 되나요?

 -> 아니요. 앱 안에서만 적용할 경우 NDK 설정은 필요 없습니다.

 

구현상 어려웠던 점

a. 코드 작성 후 실행을 하면 테스트가 되지 않는다.

-> 이해가 잘 안되겠지만 Play Asset Delivery 를 테스트하려면 터미널로 adb 생성 -> 테스트를 위한 별도의 apk 생성 -> apk 실행

     이 3가지 과정이 있어야 한다. 이 3가지 과정을 Local Test 라고 공식홈페이지에도 명시되있다.

https://developer.android.com/guide/playcore/asset-delivery/test

 

b. 로컬 테스트 방식으로 테스트 시 확인 못하는 상황이 존재한다.

-> Play Asset Delivery 의 경우 Asset 의 용량이 150MB 이상일 경우 WiFi 로 다운로드를 받도록 진행합니다.

     로컬테스트의 경우 해당 케이스 테스트가 불가합니다.

     테스트를 하려면 내부테스트 또는 비공개테스트로 Play Store에 앱을 배포해야만 가능합니다.

     필자도 구현하면서 이게 참 까다로웠습니다.

 

 

구현방법

공식 문서에 있는 내용을 기반하여 진행했습니다.

https://developer.android.com/guide/playcore/asset-delivery/integrate-java#groovy

 

1. App 수준 build.gradle 에 라이브러리 import

api("com.google.android.play:core:1.10.3")
api("com.google.android.play:core-ktx:1.8.1")
implementation 'org.apache.commons:commons-io:1.3.2'

 

2.  Asset 리소스를 담을 최상위 디렉토리 생성 (상단 Android -> Project 레벨로 이동 후 생성)

     아래 스크린샷 참조

AssetDeliverySample 에서 디렉토리를 생성한다. 디렉토리명은 소문자와 밑줄만 포함하여 아무렇게나 작성하면 된다.

예제에서는 asset_pack_sample_video 로 생성하였다.

 

3. asset_pack_sample_video 에 build.gradle 파일 생성 및 내용 입력

파일 생성 후 build.gradle 내용을 아래처럼 채우면 된다.

plugins {
    id 'com.android.asset-pack'
}

assetPack {
    packName.set("asset_pack_sample_video")
    dynamicDelivery {
        deliveryType.set("on-demand")
    }
}

 

4. (예제의 경우) asset_pack_sample_video 에 실제 Asset에 담길 자식 디렉토리 생성 (src/main/asset)

해당 방법으로 src/main/asset 디렉토리 구조로 모두 생성해준다.

이해가 안된다면 5번 스크린샷참조

 

5. asset 디렉토리에 실제 사용해야 될 리소스 넣기

 

6. App 수준 build.gradle로 이동 후 assetPacks 추가하기

android {
    compileSdk 32
	
    ....
    ....

    assetPacks = [':asset_pack_sample_video']
}

 

7. settings.gradle 에 asset_pack_sample_video 추가하기

rootProject.name = "AssetDeliverySample"
include ':app'
include ':asset_pack_sample_video'

 

여기까지 다했으면 싱크 및 앱을 한번 실행해주자

다했다면 아래와 같은 스크린샷으로 구조가 잡혀있을 것이다.

 

8. Asset 파일을 불러오고 다운로드하는 코드 작성

class MainActivity : AppCompatActivity() {

    private val assetPackManager by lazy { AssetPackManagerFactory.getInstance(applicationContext) }
    private var waitForWifiConfirmation = false


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        initAssetPack()
    }


    private fun initAssetPack() {
        val assetPackSamplePath = getAbsoluteAssetPath(ASSET_PACK_SAMPLE_NAME , "")
        Log.e(TAG,"assetPackSamplePath : $assetPackSamplePath")

        if (assetPackSamplePath == null) {
            registerListener()

            val assetPackList: List<String> = listOf(ASSET_PACK_SAMPLE_NAME)
            assetPackManager.fetch(assetPackList)
        }

    }

    private fun registerListener(){
        assetPackManager.registerListener {
            when(it.status()) {
                AssetPackStatus.PENDING -> {}
                AssetPackStatus.DOWNLOADING -> {
                    val totalSize = it.totalBytesToDownload
                    val downloadedSize = it.bytesDownloaded
                    val percent = 100.0 * downloadedSize / totalSize

                    Log.e(TAG ,"totalSize : $totalSize")
                    Log.e(TAG ,"downloadedSize : $downloadedSize")
                    Log.e(TAG ,"percent : $percent")


                }
                AssetPackStatus.TRANSFERRING -> {}
                AssetPackStatus.COMPLETED -> {
                    Toast.makeText(this, "다운로드 완료",Toast.LENGTH_SHORT).show()
                }
                AssetPackStatus.FAILED -> {}
                AssetPackStatus.CANCELED -> {}
                AssetPackStatus.WAITING_FOR_WIFI -> {
                    //로컬 테스트는 WiFi 테스트 불가
                    downloadWifiConfirmation()
                }
                AssetPackStatus.NOT_INSTALLED -> {}
                AssetPackStatus.UNKNOWN -> {}
            }
        }
    }

    private fun downloadWifiConfirmation(){
        if (!waitForWifiConfirmation){
            assetPackManager.showCellularDataConfirmation(this)
                .addOnSuccessListener {
                    when(it){
                        RESULT_OK -> {
                            Toast.makeText(this , "WiFi 아니여도 다운로드 진행합니다.", Toast.LENGTH_LONG).show()
                            registerListener()
                        }

                        RESULT_CANCELED -> {
                            Toast.makeText(this , "WiFi 설정까지 다운로드 대기상태입니다", Toast.LENGTH_LONG).show()
                        }
                    }
                }
            waitForWifiConfirmation = true
        }

    }

    private fun getAbsoluteAssetPath(assetPack: String , relativeAssetPath: String) : String? {
        val assetPackPath: AssetPackLocation? = assetPackManager.getPackLocation(assetPack)
        assetPackPath?: return null
        val assetFolderPath = assetPackPath.assetsPath()
        assetFolderPath?: return null
        return FilenameUtils.concat(assetFolderPath , relativeAssetPath)
    }

    companion object {
        const val ASSET_PACK_SAMPLE_NAME = "asset_pack_sample_video"
        const val TAG = "MainActivity"
    }
}

 

이렇게 작성 후 실행해도 다운로드 테스트를 할 수가 없다.

이제 로컬 테스트를 시도해 보자

(로컬테스트의 경우 터미널에서 그래들 관련 설정 및 설치 디렉토리에 따라 저와 다른 명령어로 실행이 될 수 있으니 참고 바랍니다.)

 

1. 안스 터미널에서 appBundle 생성

해당 명령어가 정상적으로 동작하면 아래 스크린샷처럼 appBundle이 생성된다.

 

2. app-debug.aab 로 로컬테스트용 APK 생성하기

자세한 내용은 https://developer.android.com/guide/playcore/asset-delivery/test 링크를 참조

 

우선 해당 명령어를 실행하려면 bundletool 을 설치해야 되고 java -jar 명령어가 실행되야 한다.

java -jar bundletool.jar build-apks --bundle=/Users/MyMacBook/AndroidStudioProjects/AssetDeliverySample/app/build/outputs/bundle/debug/app-debug.aab \--output=/Users/MyMacBook/Desktop/LocalTest/padTest.apks --local-testing

명령어가 길지만 요약을 해보면

java -jar bundletool.jar build-apks --bundle={해당프로젝트의 앱번들 경로}/app-debug.aab \--output={APK가 생성될 경로}/padTest.apks --local-testing

 

여기서 핵심은 마지막에 꼭! --local-testing 이걸 붙여줘야 한다.

 

3. 해당 APK를 디바이스에 설치한다.

실 디바이스를 써도 되고 에뮬로 진행해도 무방하지만 필자는 실 디바이스에 설치했다.

참고로 해당 명령어는 adb 가 인식되는 경로에서 실행해야 한다

java -jar bundletool.jar install-apks --apks=/Users/MyMacBook/Desktop/LocalTest/padTest.apks

 

좀 더 설명하자면 bundletool 도 실행이 되야하기 때문에 필자는 bundletools 이 설치된 경로를 터미널에 직접 지정해서 아래처럼 실행했다.

(bundletools 을 안드로이드 sdk/tools 에 설치했다)

java -jar /Users/MyMacBook/Library/Android/sdk/tools/bundletool.jar install-apks --apks=/Users/MyMacBook/Desktop/LocalTest/padTest.apks

 

 

이제 앱을 실행하면 상단 노티에 파일시스템~~ 이런식으로 뜨고

일정 시간이 지나면 다운로드 완료 토스트가 나온다.

이후 안스에서 에셋파일이 제대로 다운됬는지 확인이 가능하다.

View -> Tool Windows -> Device File Exploer 로 접근하여 data->data->해당프로젝트 패키지명으로 접근하면 아래 스크린샷처럼 assets 폴더에 영상이 다운로드 된것을 볼 수 있다.

 

 

모든 설치가 끝난 이후 이 mp4 파일을 가져오려면 아래 함수를 사용한다.

private fun getVideoPath(assetPackName : String , videoName: String): String?{
    val assetPath = getAbsoluteAssetPath(assetPackName , "")
    assetPath?: return null
    val file = File(assetPath.plus(File.separator).plus(videoName))
    return file.absolutePath
}
        //비디오 파일 경로를 가져오려면
        val videoAssetFilePath = getVideoPath(ASSET_PACK_SAMPLE_NAME,"1_a_1_alt.mp4")

 

 

실제 프로젝트에는 다운로드 안내 팝업 및 총 다운로드 용량을 보여주는 로직도 추가가 되있으나

내용이 너무 길어져서 생략했다.

해당 내용을 구현하면서 정말 어려움이 많았는데 누군가에 도움이 됬으면 좋겠습니다.

반응형
Comments