PAD 踩坑记录

文章目录

      • 背景
      • 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

PAD 踩坑记录_第1张图片

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

PAD 踩坑记录_第2张图片
但是以这种方式安装只能调试 install-time 类型的包,对于 on-demand 方式的包,只能上传到 google play 上进行调试。

所以在前期调试包内的逻辑时,可以先将包类型全部设置成 install-time,包内逻辑调试完成之后,再设置成 on-demand 类型。

上传到 google play

我们可以先创建内部测试版本,然后将自己的 google 账号添加到内部测试人员当中,然后就可以通过 google play 进行下载测试了。
PAD 踩坑记录_第3张图片

上传完成之后可以清晰的看到每一个 asset pack 的类型和大小。
PAD 踩坑记录_第4张图片

参考代码

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.assets
            val 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 = 0
            while (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 放到这两个包内就可以了。
PAD 踩坑记录_第5张图片
但是,实际测试在 Unity 里面死活读不到资源,但是在 Android 工程里面又可以读取到。

没办法只能去看 Unity 相关的 API,我们在注释里面找到了下面这一句,asset pack包和 bundle 必须要保持相同的名称!
PAD 踩坑记录_第6张图片
于是我们又做创建了一个 common 包用于测试,果然能够读取到了。
PAD 踩坑记录_第7张图片

手动创建 assets pack 包?

Unity 能够读取到 bundle 资源了,但是这样又带来了另外一个问题,那就是需要创建好多个包,而且每次 Unity 工程增加一个 bundle 包,那么在 Android 工程里面就得增加一个对应的 asset pack 包。

手动创建肯定不太现实,会麻烦死的,那是不是可以通过脚本来实现呢?

仔细观察,发现每个 asset pack 包其实结构相对固定,只是内容不同,因此完全有可能通过脚本来动态的创建 asset pack 包。
PAD 踩坑记录_第8张图片
具体脚本如下:

WORKSPACE=****
projectPath=$WORKSPACE

parentName=padModules

#解压文件
unzipPad() {
    cd pad
    echo -e "\n"
    echo "> unzip pad start"
    
    if [ -d "installTime_path/" ]; then
        rm -r installTime_path/
    fi
    
    if [ -d "onDemand_path/" ]; then
        rm -r onDemand_path/
    fi

    if [ -d "AssetBundles/" ]; then
        rm -r AssetBundles/
    fi
    unzip -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/assets
    cp -rf AssetBundles/. $assets_path
}


#将不同的ab包创建成不同的模块
packModules() {
    gradlereplace='assetPacks = ['

    # on_demand
    path=onDemand_path/
    files=$(ls $path)
    for filename in $files
    do
        gradlereplace="$gradlereplace \":$parentName:$filename\" ,"
        buildModule $filename on-demand $path/$filename
    done

    # install_time
    path=installTime_path/
    files=$(ls $path)
    for filename in $files
    do
        gradlereplace="$gradlereplace \":$parentName:$filename\" ,"
        buildModule $filename install-time $path/$filename
    done

    gradlereplace=`echo ${gradlereplace%?}`
    gradlereplace="$gradlereplace ]"
    echo $gradlereplace
    
    echo "在Application的build.gradle里面添加模块"
    gradle=$projectPath/Application/build.gradle
    tobeReplace="assetPacks = \[\]"
    sed -i "s/$tobeReplace/$gradlereplace/" $gradle
}

#传递参数为模块名,类型[install-time或者on-demand],assetpack路径
buildModule() {
    moduleName=$1
    type=$2
    filePath=$3
    echo "开始创建:模块名$moduleName 类型 $type"
    
    echo "1.复制assetpack:模块名$moduleName"
    modulePath=$projectPath/$parentName/$moduleName
    assetsPath=$modulePath/src/main/assets/assetpack
    scriptFile=$modulePath/build.gradle.kts
    if [ -d $modulePath ]; then
        rm -r $modulePath
    fi
    mkdir -p $modulePath
    mkdir -p $assetsPath
    cp -rf $filePath $assetsPath
    
    echo "2.创建buildgradle文件:模块名$moduleName "
    cat>$scriptFile<<EOF
plugins {
    id("com.android.asset-pack")
}

assetPack {
    packName.set("$moduleName")
    dynamicDelivery {
        deliveryType.set("$type")
    }
}
EOF
    
    echo "3.在settings.gradle里面添加:模块名$moduleName "
    settings=$projectPath/settings.gradle
    include=:$parentName:$moduleName
    if [ `grep -c "$include" $settings` -ne '0' ];then
        echo has $include
    else
    cat>>$settings<<EOF
include '$include'
EOF
    fi
}

cd $WORKSPACE

unzipPad
packModules

参考文档:

https://developer.android.com/guide/playcore/asset-delivery/integrate-java?hl=zh-cn

你可能感兴趣的:(android,android,PAD,bundle)