文章目录
- 背景
- 1. 工程里配置PAD
- 2. 调试
- bundletool
- 上传到 google play
- 参考代码
- 3.踩过的坑
- Unity 读取不到资源
- 手动创建 assets pack 包?
背景
Google 提供了 PAD (Play Asset Delivery) 的能力,能够支持将一个应用拆分成多份,这样用户就可以按需下载。
PAD 支持三种分发模式,具体如下:
分发模式 | 备注 |
---|---|
install-time | 用户安装的时候就会下载,和 apk 内的 assets 目录下的资源使用方式一致 |
fast-follow | 在用户安装完应用之后会自动下载 |
on-demand | 只有在需要的场景才会下载 |
我们的应用分成两个工程,一个是 Unity 工程,会将各种资源打包成 bundle 包,另外一个是 android 工程,引用 Unity 打包出来的各种资源。
对于我们的应用场景来说,只需要 install-time 和 on-demand 方式,把必须的一些 bundle 包设置成 install-time 的方式,用户安装完 app,就能直接使用,
而对于非必须的 bundle 包则设置成 on-demand,进入到特定的场景之后再动态去下载。
1. 工程里配置PAD
在 Android 里面引入 PAD 相对来说比较简单,按照官文档一步一步操作就可以了。
官方文档:https://developer.android.com/guide/playcore/asset-delivery/integrate-java?hl=zh-cn
配置成功之后,执行以下命令就能够生成 aab 包
./gradlew bundleDebug
或者
./gradlew bundleRelease
2. 调试
生成出来的 aab 包不能直接安装到手机上,需要借助 bundletool 工具调试或者将其上传到 google play 上。
bundletool
地址:https://github.com/google/bundletool/releases
将 aab 包和 bundletool 放到一个文件夹下,执行以下指令,该指令会自动将 aab 包安装到手机上。
java -jar bundletool-all-1.11.2.jar build-apks --bundle=Application-debug.aab --output=app.apks
java -jar bundletool-all-1.11.2.jar install-apks --apks=app.apks
但是以这种方式安装只能调试 install-time 类型的包,对于 on-demand 方式的包,只能上传到 google play 上进行调试。
所以在前期调试包内的逻辑时,可以先将包类型全部设置成 install-time,包内逻辑调试完成之后,再设置成 on-demand 类型。
上传到 google play
我们可以先创建内部测试版本,然后将自己的 google 账号添加到内部测试人员当中,然后就可以通过 google play 进行下载测试了。
上传完成之后可以清晰的看到每一个 asset pack 的类型和大小。
参考代码
Android 当中获取 install-time 类型的资源,和获取普通的 assets 目录下的资源方式一样
fun getInstallTimeAssetBundle(context: Context, assetPackName:String) {GlobalScope.launch(Dispatchers.IO) {try {QLog.i(TAG,QLog.USR,"开始获取installTime "+assetPackName)val assetManager: AssetManager = context.assetsval list = assetManager.list("assetpack")QLog.i(TAG,QLog.USR,"list:"+list)list?.forEach {QLog.i(TAG,QLog.USR,"fileName:"+it)}val stream: InputStream = assetManager.open(assetPackName)val byteOutputStream:ByteArrayOutputStream = ByteArrayOutputStream()val byte = ByteArray(1024)var length = 0while (stream.read(byte)>0){byteOutputStream.write(byte)}QLog.i(TAG,QLog.USR,"获取结束"+assetPackName+",length="+byteOutputStream.size())} catch (e: Exception) {QLog.e(TAG, QLog.USR, "getAssetBundle error $assetPackName:" + e)}}
}
Android 当中获取 on-demand 类型的资源
fun getOnDemandAssetBundle(context: Context,assetPackName: String){GlobalScope.launch(Dispatchers.IO) {try {QLog.i(TAG, QLog.USR, "开始获取ondemand:" + assetPackName)val manager: AssetPackManager =AssetPackManagerFactory.getInstance(context.applicationContext)QLog.i(TAG, QLog.USR, "注册监听:" + assetPackName)manager.registerListener { assetpackState ->QLog.i(TAG,QLog.USR,"status:"+assetpackState.status()+",name:"+assetpackState.name())}QLog.i(TAG, QLog.USR, "开始fetch:" + assetPackName)val assetsate = manager.requestFetch(listOf(assetPackName))QLog.i(TAG,QLog.USR,"返回的state: length:"+assetsate.totalBytes()+",status:"+assetsate.packStates)} catch (e: Exception) {QLog.e(TAG, QLog.USR, "getOnDemandAssetBundle error $assetPackName:" + e)}}
}
Unity 当中读取 bundle 资源,这里有个坑,就是 Unity 读取的资源必须要求 asset pack 包和 bundle 包必须是相同的名称,否则就读取不到,而 Android 当中只要把路径和 bundle 名称写进去就能够读到了。
PlayAssetDelivery.RetrieveAssetBundleAsync(assetBundleName);
3.踩过的坑
Unity 读取不到资源
最初,我的设想是创建两个包,一个是 install-time,一个是 on-demand。这样我们只需要把对应类型的 bundle 放到这两个包内就可以了。
但是,实际测试在 Unity 里面死活读不到资源,但是在 Android 工程里面又可以读取到。
没办法只能去看 Unity 相关的 API,我们在注释里面找到了下面这一句,asset pack包和 bundle 必须要保持相同的名称!
于是我们又做创建了一个 common 包用于测试,果然能够读取到了。
手动创建 assets pack 包?
Unity 能够读取到 bundle 资源了,但是这样又带来了另外一个问题,那就是需要创建好多个包,而且每次 Unity 工程增加一个 bundle 包,那么在 Android 工程里面就得增加一个对应的 asset pack 包。
手动创建肯定不太现实,会麻烦死的,那是不是可以通过脚本来实现呢?
仔细观察,发现每个 asset pack 包其实结构相对固定,只是内容不同,因此完全有可能通过脚本来动态的创建 asset pack 包。
具体脚本如下:
WORKSPACE=****
projectPath=$WORKSPACEparentName=padModules#解压文件
unzipPad() {cd padecho -e "\n"echo "> unzip pad start"if [ -d "installTime_path/" ]; thenrm -r installTime_path/fiif [ -d "onDemand_path/" ]; thenrm -r onDemand_path/fiif [ -d "AssetBundles/" ]; thenrm -r AssetBundles/fiunzip -oq installTime.zip -d installTime_path/unzip -oq onDemand.zip -d onDemand_path/unzip -oq AssetBundles.zip -d AssetBundles/echo "- unzip pad end "echo -e "\n"
}#将ab包copy到assets目录
packIn() {assets_path=$WORKSPACE/xxx/src/main/assetscp -rf AssetBundles/. $assets_path
}#将不同的ab包创建成不同的模块
packModules() {gradlereplace='assetPacks = ['# on_demandpath=onDemand_path/files=$(ls $path)for filename in $filesdogradlereplace="$gradlereplace \":$parentName:$filename\" ,"buildModule $filename on-demand $path/$filenamedone# install_timepath=installTime_path/files=$(ls $path)for filename in $filesdogradlereplace="$gradlereplace \":$parentName:$filename\" ,"buildModule $filename install-time $path/$filenamedonegradlereplace=`echo ${gradlereplace%?}`gradlereplace="$gradlereplace ]"echo $gradlereplaceecho "在Application的build.gradle里面添加模块"gradle=$projectPath/Application/build.gradletobeReplace="assetPacks = \[\]"sed -i "s/$tobeReplace/$gradlereplace/" $gradle
}#传递参数为模块名,类型[install-time或者on-demand],assetpack路径
buildModule() {moduleName=$1type=$2filePath=$3echo "开始创建:模块名$moduleName 类型 $type"echo "1.复制assetpack:模块名$moduleName"modulePath=$projectPath/$parentName/$moduleNameassetsPath=$modulePath/src/main/assets/assetpackscriptFile=$modulePath/build.gradle.ktsif [ -d $modulePath ]; thenrm -r $modulePathfimkdir -p $modulePathmkdir -p $assetsPathcp -rf $filePath $assetsPathecho "2.创建buildgradle文件:模块名$moduleName "cat>$scriptFile<<EOF
plugins {id("com.android.asset-pack")
}assetPack {packName.set("$moduleName")dynamicDelivery {deliveryType.set("$type")}
}
EOFecho "3.在settings.gradle里面添加:模块名$moduleName "settings=$projectPath/settings.gradleinclude=:$parentName:$moduleNameif [ `grep -c "$include" $settings` -ne '0' ];thenecho has $includeelsecat>>$settings<<EOF
include '$include'
EOFfi
}cd $WORKSPACEunzipPad
packModules
参考文档:
https://developer.android.com/guide/playcore/asset-delivery/integrate-java?hl=zh-cn