Unity项目发布谷歌AAB+PAD

一、使用aab+PAD的原因

一切的根源都在谷歌商店。
谷歌商店一直对上架的应用和游戏有严格的要求。最早期的时候,谷歌商店要求apk容量限制在50mb内,后来随着应用的普遍容量增大,谷歌商店把apk的容量限制放宽到100mb。
但对于游戏来说,100mb的容量明显不够用。于是谷歌在早期提出了apk+obb(Opaque Binary Blob安卓游戏通用数据包)的组合,每个应该可以包含一个100mb以内的apk安装包,然后附带2个obb资源文件,一个main.obb,一个patch.obb。这样游戏开发者可以在不改变apk内容的情况下,单纯改变main.obb的资源内容,或者通过patch.obb给游戏资源打补丁。这样用户在下载应用和更新应用的时候,可以不需要下载全部内容。
不过对于obb的版本管理,是需要游戏开发者自己去控制的,比如你在谷歌市场上传了新的obb内容,谷歌商店会帮用户下载到手机,但用户手机里面的旧的obb文件,谷歌是不会帮用户删掉的,为了不会无端端占用用户手机多余的硬盘空间,研发需要自己写代码去删掉旧的obb文件。然后手机里面同时存在多个obb文件,游戏应该加载哪一个,也是需要游戏研发自己写逻辑去判断。
后来谷歌商店提出了aab+PAD的方式上架应用,其中aab文件的容量不得超过150mb,然后资源包PAD的容量上限为2gb。不过由于打包和资源管理方式比较特殊,一直没有得到研发方的广泛支持。
到了2021年8月,谷歌商店全面停止apk+obb的应用上架方式,而强制研发团队改用aab+PAD的组合上架游戏应用。于是,很多研发团队都开始研究怎样发布aab+PAD了。

二、aab和PAD是什么?

aab:Android App Bundle
直接翻译应该是安卓应用包。
由于安卓手机的硬件和系统一直都非常复杂,举个简单的例子,早期的手机是32位的armv7,后来出的手机是64位的arm64,有些更早期的手机和模拟器,甚至是x86的。之前如果一个手机应用要支持32位手机和64位手机,还要支持x86的模拟器,必须在手机安装包里面内置3套代码库。这样对于一台手机来说,其实安装包里面有一些内容是不需要的。
aab的设计思路是,在研发导出安装包的时候,把这些不同的支持库都包含在aab里面,然后在用户下载的时候,谷歌商店会根据用户的硬件和系统的实际情况,从支持库里面挑选出用户需要的部分,然后组装成一个最符合用户实际情况的apk来给用户安装使用。
PAD:Play Asset Delivery
直接翻译应该是谷歌Play商店的资源分离
为什么强调是谷歌Play商店?因为这套PAD资源,其实是包含了cdn下载服务的,谷歌商店免费提供了cdn内容分发服务给上架谷歌商店的应用使用,所有的PAD文件,都是存放在谷歌商店的服务器上面的。
按照谷歌商店的想法,所有上架谷歌商店的应用和游戏,基于谷歌提供的cdn服务,可以实现游戏的资源下载和热更新,而且是免费的。很美好的想法。
实际情况是,这一套PAD只能用在谷歌商店,而且打包、加载的方式都是独特的,并不具有通用性。所以,我个人认为,除非是游戏一开始就打算只上谷歌商店,然后整个游戏的资源加载框架都是针对谷歌商店来写的,不然,游戏为了上其他渠道的商店,还是要自己重新搞一套通用的加载和热更新的框架,并花钱租用资源存储和cdn分发服务。

三、Unity对aab+PAD的支持

早在unity2017的某个版本开始,Unity已经有对发布aab的支持。
在这里插入图片描述

但这个功能发布的aab,是把资源全部打在安装包的base里面,如果游戏的容量很大,会很容易超过谷歌商店对于aab文件不得超过150mb的容量限制。
在这里插入图片描述

至于PAD分离和加载功能,unity似乎一直没有很明确的官方支持,都是需要取谷歌官网下载支持Unity的库导入项目使用。unity之前用于分离obb资源的功能,并不适用于aab模式。

谷歌插件对Unity的支持
插件下载:
https://github.com/google/play-unity-plugins/releases?spm=a2c6h.12873639.article-detail.4.dff34bbfofz9NZ
下载完之后导入Unity工程项目
在这里插入图片描述

在这里插入图片描述

导入了项目里面后,会看到出现了谷歌的工具栏,里面有关于PAD的设置,还有打包aab的选项。
在这里插入图片描述

打开Asset Delivery设置工具,用户可以在这里添加文件夹,并逐个(注意是逐个)对资源设置类型。
具体的类型有:
1.install-time:在安装apk的时候会同时下载并安装到手机里的资源。只要安装完apk,这部分的资源就能正常的在本地访问得到。
2.fast-follow:在安装完apk之后,这类型的资源会立刻进行下载。如果安装完apk立刻打开应用,这部分的内容并不一定能保证下载到本地。
3.on-demand:这种类型的资源在安装完apk之后,并不会自动下载,而是需要游戏自己写逻辑在需要的时候从服务器上下载。

从工具上看,似乎谷歌提供的功能还算比较合理。但实际上应用起来,会有3个问题:
1、虽然可以通过选择文件夹来批量导入文件,但进行设置的时候,居然是逐个文件设置的。如果有1万个文件,呵呵了
2、做这个插件界面功能的人好像比较敷衍,如果加载的文件很多,会真的把一万个文件全部列出来,导致Unity卡死,无法操作。如果不小心导入了很多文件导致卡死,可以退进程后,把这个文件删掉:Library/PlayAssetPackConfig.json
3、这个工具只认Unity的AssetBundle,而且选择文件夹的时候,必须是选择原生打包AssetBundle的文件夹,意思是文件夹里面必须包含一个和文件夹名一样的manifest文件,才能选择。而一般的游戏项目,都会有自己的资源加密手段,这样就导致了没法通过文件夹来选择了。

基于这3个问题,基本上可以认为,谷歌提供的这个Unity编辑器工具是不能正常使用的。幸好,他还提供了API,可以自己写代码去批量设置并生成配置文件。

四、代码设置PAD

代码设置PAD的pack配置,需要有3个要素
1、pack的名称(packName)
2、放置这个pack的路径
3、这个pack的类型是什么
然后,我们就可以调用api,把整个文件夹设置成同一个pack了。
至于一个游戏里面的资源,究竟要分成多少个pack,这是和游戏本身的加载策略有关系的。既可以把每一个文件设置成单独的pack,也可以把某一种类别的东西设置成pack,也可以把所有资源设置成同一个pack
设置一个pack的代码大概是这样的:
string packName = “pad”;//这里是你想打包的pack的名称,我这里随便命名为pad
string path = GetPADPath();//这里是指定一个pack生成后存放的路径,可以自己指定
var assetPackConfig = new Google.Android.AppBundle.Editor.AssetPackConfig();
assetPackConfig.AddAssetsFolder(packName, path, Google.Android.AppBundle.Editor.AssetPackDeliveryMode.InstallTime); Google.Android.AppBundle.Editor.AssetPacks.AssetPackConfigSerializer.SaveConfig(assetPackConfig);
上面主要的方法就是assetPackConfig.AddAssetsFolder,调用这个方法,就会把pack名称、路径和类型传进去assetPackConfig里面。如果有多个需要设置的pack,那么就调用AddAssetsFolder添加多次,最后再调用SaveConfig把设置保存下来。
保存后的配置路径是Library/PlayAssetPackConfig.json

五、打包aab+PAD

Unity自带的打包功能,是没有读取上面我们说的PlayAssetPackConfig.json,所以他并不会把自定义的pack打到aab里面。
我们需要自定义pack的时候,就不能用Unity自带的API打包。而要改成使用谷歌API提供的AppBundleBuilder类来打包。
我们直接调用谷歌API提供的
Google.Android.AppBundle.Editor.Internal.AppBundlePublisher.BuildWithPath(path)方法,把需要生成aab的路径传进去,就可以了。
如果有兴趣可以具体看看实现,他是先指定了打包的文件名和路径,从PlayAssetPackConfig.json里面读取了PAD的信息,然后再把config数据传到Build方法里面打包。
需要注意的是,如果你自定义了AndroidManifest.xml,那么直接打包是不能正常把包打出来的,需要设置一下gradle模板。
在这里插入图片描述

在ProjectSetting——Player——Publishing Settings里面,找到Custom Main Gradle Template和Custom Launcher Gradle Template这两项,打钩。
然后会在AssetsPluginsAndroid文件夹下出现mainTemplate.gradle和launcherTemplate.gradle这两个文件。
分别在这两个文件里面加上
packagingOptions {
exclude ‘AndroidManifest.xml’
}
在这里插入图片描述

这时候打包应该就能通过了。

六、aab文件结构分析

在这里插入图片描述

把打包出来的aab文件的后缀名改成压缩包(比如rar或者zip之类),就可以把aab文件解压缩出来。
可以看到里面包含的内容,其中base文件夹里面的,是对应正常apk里面的包内内容,比如Unity的StreamingAssets文件夹内容会放在base/assets/下
由于我刚才设置了一个自定义的pack,把所有资源都放到里面去,名字叫做pad,所以在aab文件里面,也会出现一个pad文件夹,里面就是我们的自定义pack的文件内容。
值得注意的是,pad里面的assets文件夹里面还有一个assetpack,assetpack里面才是真正的自定义pack文件内容,所以结构是pad/assets/assetpack/。如果我们直接打包aab文件并使用谷歌API读取,不需要关心这个问题。如果是导出安卓项目打包,并手动放入pack,则需要保证路径不要错,很多人可能会漏了assetpack那一层。

七、aab+PAD安装后的资源文件的读取方式

1、Resources和StreamingAssets文件夹里面的内容
放在Resources文件夹和StreamingAssets文件夹里面的内容,打包时会放到base文件夹,其实就等于正常的apk结构而已,所以,原来apk是怎样读取的,现在也怎样读取就可以了。包括了Resources.Load方法,AssetBundle.LoadFromFile等方法都是可以使用的。

2、自定义pack的内容
首先说明一点,PAD的pack内容根据类型不同,不一定直接可以在本地找到,所以按道理应该是需要自己判断fast-follow和on-demand的pack是否在本地,如果不在,还需要请求下载之后才能使用。
不过由于我个人觉得谷歌为我们精心打造的热更新功能并没有太大意义,所以我并没有花时间去研究。
以下我们直接讨论肯定已经在本地的install-time类型。
1.在最开始的时候,我是考虑能不能不使用谷歌API提供的方法,而使用类似StreamingAssets的加载方式,找到pack里面资源的具体路径,然后通过I/O方法或者AssetBundle的相关方法来读取。不过实际操作之后,发现我们是获取不到具体资源的路径,只能获取到资源所在的pack的apk路径。所以这个思路走不通
2.如果使用谷歌API来加载,可以配合API文档来看:
https://developer.android.google.cn/reference/unity
下面会用一些简单的代码例子来说明几个比较重要的方法。
3.我们先需要读取到对应的pack的信息,通过PlayAssetDelivery.RetrieveAssetPackAsync(packName);方法,返回一个PlayAssetPackRequest对象,然后如果以后需要读取对应pack的内容,都是从这个PlayAssetPackRequest对象里面获取
4.同步加载资源
由于谷歌API并没有提供直接同步加载的方法,所以我们只能通过其他手段加载。
假设我获取了PlayAssetPackRequest对象root,里面通过资源的相对了路径path,
AssetLocation asset = root.GetAssetLocation(path);得到了一个AssetLocation对象,这个对象里面,包括了资源的apk路径,字节偏移和长度等信息,我们可以加载到bytes,然后再通过 AssetBundle.LoadFromMemory系列方法生成AssetBundle。大概的代码实现如下:
AssetLocation asset = root.GetAssetLocation(path);
FileStream assetFileStream = File.OpenRead(asset.Path);
byte[] bs = new byte[(long)asset.Size];
assetFileStream.Seek((long)asset.Offset, SeekOrigin.Begin);
assetFileStream.Read(bs, 0, bs.Length);
AssetBundle ab = AssetBundle.LoadFromMemory(bs);
5.异步加载资源
在谷歌API提供的PlayAssetPackRequest里,自带了异步加载AssetBundle的方法,返回一个Unity的API的AssetBundleCreateRequest。
代码写法是:
假设我获取了PlayAssetPackRequest对象root
AssetBundleCreateRequest ab = root.LoadAssetBundleAsync(abName);
网上有文章说,这个方法同时加载数量较大的资源时,会出现out of memory
报错,我自己暂时没发现这个问题。
如果怕存在这个问题,也可以先读取bytes,然后用AssetBundle.LoadFromMemoryAsync方法来获得AssetBundleCreateRequest。
代码写法:
byte[] bs = LoadAssetBytesByPath(path);
AssetBundleCreateRequest ab = AssetBundle.LoadFromMemoryAsync(bs);
6.个人的一些看法
使用AssetBundle.LoadFromMemory来同步加载AssetBundle,由于需要读取整个文件的bytes,然后一起加载到内存去,比如会存在加载时间长和内存占用大的问题,这样的做法肯定没有AssetBundle.LoadFromFile那么好,但由于Unity官方暂时没有直接的读取方法支持,所以好像也没什么更好的处理方式。
异步加载如果使用AssetBundle.LoadFromMemoryAsync,也同样是有加载时间和内存占用的问题。至于谷歌API提供的LoadAssetBundleAsync方法是不是需要占用全部内存,还是只是一个引用,这个不是很清楚。
现阶段,其实只是停留在能用原来的Unity版本导出aab+PAD,然后能正常读取的程度。在实际运行中,能感觉除了是比之前直接用Unity的API加载资源是更卡顿的。这个问题,应该只有在后续版本的Unity里面官方直接提供解决方案支持,才能更好的解决这个问题。

八、aab文件的安装测试

aab文件是直接上传谷歌商店的,它并不是一个像apk一样能直接安装的文件。
如果我们要测试它,则需要根据实际情况。
fast-follow和on-demand这两种需要服务器支持的类型,似乎只能上传谷歌商店来测试,我也没用具体的研究过。
install-time这种类型的资源,除了上传谷歌商店,还可以本地模拟安装测试的。
这个时候我们需要在电脑上面安装JAVA,并需要一个bundletool的工具(网上搜一下应该都有),我使用的版本是bundletool-all-1.8.1.jar
具体的生成步骤是:
1、从aab生成apks
注意这里生成的是apks而不是apk。
我们通过命令行工具,定位到bundletool所在的文件夹,然后输入
java -jar bundletool-all-1.8.1.jar build-apks --bundle=你的aab文件路径 --output=需要输出apks文件的路径
比如我的bundletool文件在D盘的bundletool下,然后test.aab文件在D盘的aab文件夹下,想在D盘的apks文件夹下生成test.apks,那么命令可以这样:
java -jar D:bundletoolbundletool-all-1.8.1.jar build-apks --bundle=D:aabtest.aab --output=D:apkstest.apks
需要注意的是,需要生成的apks文件必须不是已经存在的文件,因为这个命令不会覆盖原有文件,如果之前就已经有了一个D:apkstest.apks文件,那么执行命令的时候会提示你文件已存在,并生成失败。
2、将apks安装到设备
生成了apks之后,还需要把这个apks安装到指定的设备上。
在命令行工具输入:
java -jar bundletool-all-1.8.1.jar install-apks --apks=你的apks路径 --device-id = 你想安装的设备id
设备id不是必须的,如果你的电脑当前只连接了一个设备,那么device-id可以不填
查看设备id的方法是,在命令行工具输入adb devices
在这里插入图片描述

假如我们现在需要把刚才生成在D盘的apks安装到设备,那么命令这样写:
java -jar D:bundletoolbundletool-all-1.8.1.jar install-apks --apks=D:apkstest.apks --device-id = 127.0.0.1:62001
如果只连接了一个设备,就可以简略写成:
java -jar D:bundletoolbundletool-all-1.8.1.jar install-apks --apks=D:apkstest.apks
运行命令之后,会现在C:Users你的用户名AppDataLocalTempplay-unity-build下生成一个临时文件,然后再将临时文件安装到设备上。
由于这个临时文件是不会自动删除,而且容量很大,所以在安装完之后,记得去c盘把临时文件删掉。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>