• 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
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    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
    
    • 1
    • 2
    • 3

    在这里插入图片描述
    但是以这种方式安装只能调试 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)
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    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)
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    Unity 当中读取 bundle 资源,这里有个坑,就是 Unity 读取的资源必须要求 asset pack 包和 bundle 包必须是相同的名称,否则就读取不到,而 Android 当中只要把路径和 bundle 名称写进去就能够读到了。

    PlayAssetDelivery.RetrieveAssetBundleAsync(assetBundleName);
    
    • 1

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116

    参考文档:

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

  • 相关阅读:
    B. Applejack and Storages
    unity打包到安卓游玩流程
    08-JS对象、原型及原型链
    勘误、刷新和正本清源-《企业应用架构模式》将出修订中译本
    【探索SpringCloud】服务发现-Nacos服务端数据结构和模型
    MySQL数据库基本操作和完整性约束类型详解
    react +antd table 滚动事件实现防抖
    科研诚信与学术规范MOOC-错题集
    9步打造个人ip
    Hadoop HA (二) --------- HDFS-HA 手动模式
  • 原文地址:https://blog.csdn.net/hbdatouerzi/article/details/127725826