ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Unity AAB资源模式转换:突破Google Play 150MB限制的自动化方案

Unity AAB资源模式转换:突破Google Play 150MB限制的自动化方案 简介面向Android与Unity开发者的AAB资源包转换工具集主要解决Unity导出的AAB包内资源无法按install-time模式分发、导致Google Play 150MB限制难以突破的问题适合从事大型移动应用发布与包体优化的工程师使用。资源以zip压缩包发布共14个文件约48.98MB涵盖可执行程序、jar工具、shell脚本以及aapt/aapt2、adb、bundletool等Android打包链路的常用组件并附有txt、docx与md格式的说明和操作文档能够支撑从资源解析、模式转换到重新打包的完整流程。工具链设计紧凑自动化程度高搭配配置示例、密钥文件和日志辅助机制可帮助开发者节省手动调整AAB的时间同时加深对Android App Bundle资源模型的理解。目前已有197人学习或下载对于熟悉Unity导出流程、急需控制应用包体积的移动端工程师来说是一份参考价值和可操作性兼备的实用资料。 做Unity出海项目的人应该都撞上过Google Play那个150MB的墙。辛辛苦苦把游戏做到一个安装包全量装完结果Bundle一传到Console就报超限提示“Your Android App Bundles initial install-time download size exceeds 150MB”。最难受的是这玩意儿还不是APK时代你随便处理个zip就能糊弄过去的AAB拆成base模块和asset pack之后什么资源该在安装时下、什么资源该按需下Google Play有一套自己的规则。我之前在项目里踩了一整天才把这个问题理顺。网上关于Unity打包AAB的教程不少但讲“怎么把AAB包里的资源批量调成install-time模式”的基本没有要么是讲概念要么是让你用Play Asset Delivery插件手工配置对已有包体做后处理、自动转换这种需求很少有人提。所以把我自己写的一套转换工具和完整思路整理出来给同样被150MB限制和资源递送模式坑过的朋友参考。这篇文章覆盖三件事AAB资源模式到底怎么影响包体审核、Unity生成的包为什么需要手动调整、以及如何写一个自动转换工具把资源稳定切到install-time模式同时保证在限制之内。1. 先弄明白Google Play的150MB限制到底卡在哪1.1 AAB不是APK限制口径完全不同很多人刚接触AAB时会有个误区以为Google Play限制的是AAB文件本身的体积。实际上AAB上传到Console时允许的最大文件大小是200MB左右真正卡人的是这个限制——用户在Play Store点击安装后设备上解出来并安装的那部分内容压缩后不能超过150MB。也就是说你把一个180MB的AAB传上去如果里面的资源全部属于install-time递送模式那在设备端生成的安装APK可能就超过150MB直接报错。但如果你把其中一部分资源拆到独立的asset pack里并标记成fast-follow或者on-demand那么这部分的体积就不会占用安装时的150MB额度上传审核反而可以通过。这也是“突破150MB限制”最常用的途径不是去压缩资源而是通过递送模式重新分配体积。这里必须强调一句这并不是什么旁门左道Google Play本身就把AAB设计成可以按递送模式拆分我们只是合理利用官方机制。只是Unity默认的打包方式往往没有帮我们做这种精细的分配。1.2 三种递送模式直接影响玩家体验AAB里的资源模块有3种递送模式它们在体验上的差异很直观模式下载时机玩家体验计入安装包150MB典型场景install-time安装应用时一并下载安装打开即是完整游戏无需等待是核心资源、基础贴图、主要玩法场景fast-follow安装完成后自动后台下载进入游戏后可能短暂等待但下载由商店自动触发否新手引导资源、次要玩法、部分音频on-demand玩家运行到特定功能时才下载玩到某个功能时会看到加载进度条受限网络下体验较差否高等级关卡、超清贴图包、DLC内容所以install-time资源越多玩家体验越完整但代价是安装包体积显著变大容易顶到150MB。fast-follow和on-demand资源体积再大也不占用初始安装额度但会造成玩家实际游戏过程中的不确定性——尤其是网络不好的用户装完游戏后还要等资源下载。1.3 “转换成install-time”能解决什么问题在什么场景下你会想把资源转回install-time模式我实际遇到的情况是Unity用Play Asset DeliveryPAD打包的asset pack默认递送模式是fast-follow或on-demand导致游戏安装后玩家进了主界面还得等资源包慢慢下。遇到网速不稳的用户直接卡在加载页流失率非常难看。还有另一种情况游戏立项时为了过审把大量资源扔到了fast-follow里结果玩家反馈“进游戏要等三分钟下载资源包”运营同学天天被骂。这时候如果能有一个工具自动把AAB包里的fast-follow和on-demand资源全部切回install-time让玩家安装时一次性拿全资源体验立刻就能上来。当然前提是切完之后install-time部分不能超过150MB否则又会被商店打回来。所以严格来说这个工具解决的不是“突破150MB”这个物理限制而是“在150MB限制内把尽量多的资源塞进install-time模式让玩家一次装完”。2. Unity打包AAB时资源到底是怎么归档的2.1 Unity默认行为大多数资源都在base模块里Unity通过Gradle构建AAB时默认情况下几乎所有的资源——包括StreamingAssets、Addressables打出来的AssetBundle、Resources目录里的内容——都会被放进base模块。base模块的递送模式固定是install-time所以很多中小型游戏直接导出AAB其实所有资源都是install-time根本不需要额外转换。这种情况唯一的痛点是如果base模块的内容已经超过150MB那上传就麻烦。常见处理方式要么是压缩资源要么是把一部分资源拆出来做asset pack放到非install-time模式。如果游戏本来就不大低于150MB那你压根不需要这种工具随便导出一个AAB传上去就完事。2.2 什么情况下资源会被打成fast-follow或on-demandUnity工程里如果使用了以下方案构建产物里就会出现非install-time的模块官方Play Asset Delivery插件在Unity中创建Asset Pack文件夹并配置递送模式构建时会把对应资源打成独立asset pack。Addressables配合PADAddressables能选择把远程资源作为asset pack输出此时会生成fast-follow或on-demand的模块。自定义Gradle脚本手动配置有些团队为了精细控制会在生成的Gradle工程里手动添加assetPack配置指定deliveryType。从AAB文件结构上说base以外的模块会出现在assets根目录下或modules目录里具体取决于Unity/Gradle版本并且会在BundleConfig.pb文件中声明递送模式。很多团队卡住就是卡在这一层构建出来以后看到AAB里确实有assetpack/xx这种目录但不知道怎么把这些资源改成install-time。2.3 需要转换工具的典型场景我自己遇到过的、需要工具介入的两个场景第一个场景是由PAD插件管理的多语言或多分辨率资源。PAD默认把可选资源包用fast-follow模式分发但在国内出海项目里很多玩家装完游戏马上就会进入核心玩法根本没有等待下载的耐心。这时就需要强制把fast-follow改成install-time。第二个场景是CI打包流水线需要统一处理。团队成员手工在Android Studio里改Gradle脚本太容易出错而且每次Unity升级后生成的Gradle工程都会有细微变化。这时候你会发现与其让开发到处找配置不如写一个独立工具在AAB生成后自动化地把所有模块的递送模式统一成install-time。这样从构建到出包全流程可控。3. 动手实现AAB资源转换工具3.1 技术选型Python protobuf为什么不选Java我当时第一反应是用Java写毕竟AAB和Gradle都是Android生态里的东西Java处理起来似乎更“正统”。但仔细一想这个工具要跑在两个地方本地构建机和CI服务器。CI服务器上不一定装了JDK但Python环境几乎处处都有。而且AAB本质上是一个zip文件用Python的zipfile来处理非常顺手。最终选了Python 3 protobuf的组合整个脚本不到200行放在仓库的tools目录下任何机器只要装了Python就能跑。需要准备的环境依赖Python 3.8protobuf库pip install protobufprotoc编译器用于把protoEqual生成Python类官方bundletool.jar用于验证转换结果3.2 解构AAB找到BundleConfig.pb读懂delivery_typeAAB结构其实没有很多人想的那么复杂。一个标准的AAB解压开后你会看到app.aab ├── base/ │ ├── manifest/ │ │ └── AndroidManifest.xml │ ├── dex/ │ ├── assets/ │ ├── res/ │ └── ... ├── assetpack_xxx/ │ ├── assets/ │ │ └── ... │ └── manifest/ │ └── AndroidManifest.xml ├── BundleConfig.pb └── META-INF/其中BundleConfig.pb是这个工具的观察对象。这个文件是Google定义的protobuf序列化消息里面记录整个Bundle的配置包括每个模块的递送类型。具体的proto定义可以在bundletool的GitHub仓库里找到核心部分长这样简化后message BundleConfig { repeated AssetModule asset_modules 1; } message AssetModule { string name 1; DeliveryType delivery_type 2; } enum DeliveryType { INSTALL_TIME 0; FAST_FOLLOW 1; ON_DEMAND 2; }用protoc把官方proto生成Python类之后就能读取和修改delivery_type了。很多网上教程卡在这里因为直接读BundleConfig.pb看到的是一堆乱码不搞protobuf定义根本没法下手。3.3 核心转换逻辑与代码实现完整思路分六步用Python的zipfile解压AAB到临时目录用protobuf反序列化BundleConfig.pb遍历asset_modules列表把所有delivery_type不为INSTALL_TIME的模块改成INSTALL_TIME重新序列化BundleConfig.pb并覆盖原文件重新打包成zip保持原来的文件压缩方式和目录结构使用jarsigner或apksigner重新签名核心代码长这样import shutil import zipfile from pathlib import Path import bundle_config_pb2 def convert_aab_to_all_install_time(aab_path: str, output_path: str): work_dir Path(aab_work) if work_dir.exists(): shutil.rmtree(work_dir) work_dir.mkdir() # 1. 解压AAB with zipfile.ZipFile(aab_path, r) as z: z.extractall(work_dir) # 2. 读取BundleConfig.pb config_path work_dir / BundleConfig.pb config bundle_config_pb2.BundleConfig() with open(config_path, rb) as f: config.ParseFromString(f.read()) # 3. 将所有模块设置为install-time changed [] for module in config.asset_modules: if module.delivery_type ! bundle_config_pb2.INSTALL_TIME: old_type module.delivery_type module.delivery_type bundle_config_pb2.INSTALL_TIME changed.append((module.name, old_type)) # 4. 写回 with open(config_path, wb) as f: f.write(config.SerializeToString()) # 5. 重新打包 if Path(output_path).exists(): Path(output_path).unlink() with zipfile.ZipFile(output_path, w, zipfile.ZIP_DEFLATED) as zout: for file_path in sorted(work_dir.rglob(*)): if file_path.is_file(): arcname file_path.relative_to(work_dir).as_posix() zout.write(file_path, arcname) shutil.rmtree(work_dir) return changed这里面有两个细节必须注意。重新打包时不能把压缩比调太高或太低建议保持ZIP_DEFLATED这种常规方式。过于激进的压缩级别可能导致构建时间翻倍收益却几乎为零。更重要的是源码里如果有自定义的.so库或已压缩资源zipfile默认识别不了Android用的对齐规则所以转换完成后一定要用bundletool做一次完整校验不能跳过。3.4 接入Unity构建流程Gradle Task方案脚本本身只是工具真正的价值在自动化。我的做法是把Python脚本嵌入Unity导出的Gradle工程的构建流程中通过一个自定义Gradle Task在bundleRelease之后自动执行转换。在app/build.gradle里加一段task convertAabToInstallTime(group: build) { doLast { def aabPath file(${buildDir}/outputs/bundle/release/app-release.aab) def outputDir file(${buildDir}/outputs/bundle/release_converted) outputDir.mkdirs() def outputPath file(${outputDir}/app-release-converted.aab) exec { workingDir project.rootDir commandLine python3, tools/convert_aab.py, --input, aabPath.path, --output, outputPath.path } } } tasks.whenTaskAdded { task - if (task.name bundleRelease) { task.finalizedBy convertAabToInstallTime } }这样每次执行./gradlew bundleReleaseAAB生成后会立刻被转换并在release_converted目录下输出新包。发布时直接用转换后的AAB不需要手工操作。如果Unity导出的工程每次构建都会重新生成Gradle工程你可以写一个Unity Editor脚本在构建完成后读取生成的build.gradle做一次文本替换把这段Task插入进去保证每次都是全自动。4. 验证和上线别让转换工具搞出新的问题4.1 用官方bundletool做最终验证转换工具跑完后绝对不能直接传到Google Play Console必须先做两轮验证第一轮验证模块递送类型是否真的改了。解压转换后的AAB再用protobuf读一遍BundleConfig.pb确认所有delivery_type都变成INSTALL_TIME。Python脚本里可以加一行打印输出列出变更前后对照。第二轮验证包体是否能正常生成APKS以及底部install-time部分是否在150MB以内。用官方bundletool命令java -jar bundletool.jar build-apks \ --bundleapp-release-converted.aab \ --outputapp.apks \ --ksrelease.keystore \ --ks-passpass:yourpass \ --ks-key-aliasalias \ --key-passpass:yourpass执行成功后再用get-size total看各模块体积java -jar bundletool.jar get-size total \ --apksapp.apks \ --dimensionsABI重点看install-time那一列如果总和超过150MB说明转换过头了——资源全部变成install-time后直接越过商店限制。这时候需要决策要么放弃部分资源要么保持一部分fast-follow把工具做成可配置的“只转指定模块”而不是无脑全转。4.2 重新签名与上传注意事项AAB本身属于签名包直接改完内容后签名必然失效。在上传Google Play之前必须重新签名。这里有个容易被忽略的点Google Play会校验AAB的签名是否与其支持的签名方案匹配。建议使用apksigner而非jarsigner因为apksigner默认包含了v1/v2/v3等签名方案兼容性更稳。签名命令参考apksigner sign \ --ks release.keystore \ --ks-key-alias your_alias \ --ks-pass pass:yourpass \ --out app-release-sign.aab \ app-release-converted.aab签名后再次用bundletool build-apks验证确保无误传。上传时还有一个坑如果AAB里的模块配置跟声明不符Console会直接拒绝并提示“Module delivery settings mismatch”。遇到这个报错优先检查是不是只改了BundleConfig.pb但没改对应模块的AndroidManifest.xml里的dist:delivery属性。二者必须保持同步。所以完整工具不能只处理BundleConfig.pb还要顺带遍历*/manifest/AndroidManifest.xml把dist:delivery的值同步改成install-time。5. 实际项目中的经验与踩坑5.1 常见问题速查表问题现象原因解决方案AAB解析失败protobuf反序列化报错或字段丢失proto版本不匹配使用与bundletool发布版本一致的proto定义上传被拒Module delivery settings mismatchBundleConfig.pb与manifest不一致同步修改manifest里的dist:delivery转换后包体超过150MBConsole报install-time size超限所有资源都塞进install-time体积顶破天花板改造成白名单模式只转换关键资源模块安装后资源加载异常玩家打开游戏报找不到资源文件资源模块递送模式改变后加载逻辑仍按远程下载处理检查游戏内资源加载路径确保assset pack已本地化签名校验失败bundletool报签名错误转换后忘记重新签名用apksigner重新签名后再验证5.2 我的几点实操心得第一点不建议把所有模块无条件全转成install-time。我最初写工具时图省事直接循环改掉所有非install-time模块结果某次项目AAB体积偏大转完之后install-time部分直接冲到一百六七十MB上传被拒。后来在工具里加了一个--whitelist参数允许指定只转换哪些模块其他保持不变才算彻底解决问题。第二点转换工具要做成“先报告后执行”的形式。在CI上运行时先输出当前各模块的递送类型和预估体积让构建脚本决定是否真的执行转换。这个设计帮我省了不少麻烦——有时候Unity升级后构建产物的模块名称变了如果工具能先打印一份清单管理者一眼就能看出哪些模块异常。第三点资源体积本身还是要持续优化。工具只是重新分配递送模式不改变任何资源的实际大小。如果AAB原始尺寸就已经200MB以上再怎么转也没用install-time必然超限。最合理的策略是双管齐下一方面用ASTC压缩纹理、做AssetBundle的LZ4压缩从源头减体积另一方面用转换工具精确分配资源模式把玩家最依赖的部分放进install-time其余用fast-follow兜着。我在给一个MMO项目接入这套流程时最开始就是想简单地把所有PAD资源拉回install-time结果发现安装包逼近170MB。后来改成配置文件驱动只把新手场景、主城贴图、核心UI这几个模块转成install-time其余音效和高清过场动画保持fast-follow最终install-time稳定在143MB左右玩家首局体验明显改善商店审核也一次性通过。工具本身不难写难的是清晰定位哪些资源必须“先装后玩”哪些可以“边玩边下”。最后再分享一个细节记得在正式版本转换前备份原始AAB。因为工具修改的是二进制protobuf和XML文件一旦proto版本与本地环境不匹配极有可能生成一个损坏的AAB到时候没有原始包对照排查起来会非常痛苦。备份一份原始包也就几秒钟的事但救急的时候能帮你省下一个下午。本文还有配套的精品资源点击获取
返回列表