PAD 踩坑记录

背景

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.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 放到这两个包内就可以了。
在这里插入图片描述
但是,实际测试在 Unity 里面死活读不到资源,但是在 Android 工程里面又可以读取到。

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

手动创建 assets pack 包?

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

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

仔细观察,发现每个 asset pack 包其实结构相对固定,只是内容不同,因此完全有可能通过脚本来动态的创建 asset pack 包。
在这里插入图片描述
具体脚本如下:

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值