按照 Google Play 的政策,现在上架新的应用/游戏,都需要是 AAB 文件,但是有时候并没有直接用 Gradle 打 AAB 包的条件。如果能够将 APK 转为 AAB,并且能够正常运行还能上传到 Google Play 那就好办了。所以本文就探讨一下如何实现这个转换。
一、结构对比
首先我们先了解下 APK 的包体结构(左)和 AAB 包体结构(右)的区别。
结构表:
| APK 结构 | AAB 结构 |
|---|---|
| res | base(基础的资源目录) - assets (对应 APK 中的 assets,但是只存放基础的配置文件,大文件不放此处) - dex (对应 APK 中 classes.dex) - lib (对应 APK 中的 lib) - manifest (对应 APK 中的 manifest) - res (对应 APK 中的 res) - root (kotlin、original/META-INF等都在这个文件夹里,具体可以参考文末的对照表) |
| META-INF | META-INF (存放元数据) |
| lib | module (自定义名称的目录,可以存在多个,通常用来分割比较大的资源,避免 150MB 限制) |
| assets | BUNDLE-METADATA |
| resources.arsc | BundleConfig.pb |
| classes.dex | |
| AndroidManifest.xml |
APK 的详细结构这里就不细讲了,我们主要看下 AAB 详细结构,如下图:
那么了解了两者间的区别和对应的目录,接下来我们开始实现转换的流程。
二、转换的实现
整个流程分为四步:反编 APK -> 构建不同的 module -> 合并构建 AAB -> 签名。
1. 工具和环境
首先配置好相应环境和谷歌官方对应的开发工具,如下:
- java 环境 + jarsigner.exe
- bundletool.jar
- android.jar
- APKtool.jar
- smali.jar
- aapt2
JAVA 环境选 8/11,工具的版本最好是用最新的。
2. 反编译 APK
然后使用 APK_tool.jar 进行反编译,得到反编译后的文件夹。
命令行如下:
java -jar [APKtool 文件] [-s (可选)] d [--only -main-classes (可选)] [需要反编的 APK 文件] -o [反编后输出的目录]
这里得到的反编译后的目录如下图:
3. 构建 module
本流程主要是通过编译资源文件,链接资源文件,解压并复制相关文件到目录作为 module 。
构建 AAB 需要 base 和其他自定义的 module ,在这里用 GameAssetsRes 来当做自定义 module。当然,也可以加其他 module,生成步骤都是一样的。
在构建 module 之前,我们要先学习下 aapt2 的功能,具体可看 官方文档,这里只用到 编译资源文件 和 链接资源文件。
3.1 编译资源文件
通过 aapt2 对包含资源文件的目录进行编译,生成 .zip 文件备用。
命令行如下:
[aapt2 执行程序] compile --dir [资源文件夹] -o [生成资源的 zip]
那我们生成的 .zip 文件 是干嘛的呢?解压可以看到里面是各种资源文件编译成的 .flat 文件(由 aapt2 编译而成的二进制文件)。
3.2 链接资源文件
通过 aapt2 link 链接 .zip 以及 AndroidManifest.xml 文件,并将其打包为 .APK。
命令行如下:
[aapt2 执行程序] link --proto-format [资源 zip] -o [输出的 .APK] -I [android.jar] --manifest [manifest 文件] --min-sdk-version [最小版本] --target-sdk-version [目标版本] --version-code [版本号] --version-name [版本名] --compile-sdk-version-name [编译 sdk 的版本名]
如果没有 .zip,也可以增加 -R 命令直接指定 .flat 文件,此时可以去掉 -o [输出的 .APK],在命令行末尾加上 --auto-add-overlay (新资源可以覆盖旧资源,不会导致有同样名称的资源冲突)。
PS:[manifest 文件] 参数后面的几个版本相关的参数,为可选配置,配置之后会注入到 AndroidManifest.xml 中。
3.3 构建 base
了解了 aapt2 的两个功能用法之后,我们直接开始构建 base,因为它是最重要的,包含了 res 资源 和 dex 以及 manifest。
首先新建一个 base 文件夹,作为构建 base 的主要工作空间,然后开始构建。
构建分为以下几步:
- 编译 反编译 APK 步骤后生成目录下的 res 文件夹,生成 compile.zip。
- 链接 compile.zip 以及反编译目录下的 AndroidManifest.xml,生成 base.apk。
- 解压 base.apk,将 res、AndroidManifest.xml,resources.pb 按照文章末尾的 对照表 ,复制到 base 文件夹下对应位置。
- 反编译目录中的其他文件按照附录对照表复制到 base 文件夹下对应位置。(如果游戏的 assets 比较大,则复制必要的配置文件,确保 base.apk 不超过 200MB 即可)
- 将 base 文件夹压缩为 base.zip。
base.zip 就作为一个 module。
4. 构建其他的 module
因为是游戏,所以其他的资源都整合到一个 module 中,我们新建一个 GameAssetsRes(可自定义名称) 文件夹。
步骤和 构建 base 类似,但是因为 res 资源我们都放 base 中了,所以不用再编译资源文件,直接链接资源文件即可。
- 没有 .flat 的 .zip,只需要链接下面的 AndroidManifest.xml 即可,生成 GameAssetsRes.APK。(这里选择安装时分发模式 install-time,详细可以了解官方文档)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/APK/res/android"
xmlns:dist="http://schemas.android.com/APK/distribution"
package="${applicationId}"
platformBuildVersionCode="30"
platformBuildVersionName="11"
split="GameAssetsRes(可自定义名称)"
android:compileSdkVersion="30"
android:compileSdkVersionCodename="11" >
<dist:module dist:type="asset-pack" >
<dist:fusing dist:include="true" />
<dist:delivery>
<dist:install-time />
</dist:delivery>
</dist:module>
</manifest>
- 解压 GameAssetsRes.APK,将 AndroidManifest.xml,resources.pb 按照附录的对照表复制到 GameAssetsRes 文件夹下对应位置。
- 将 GameAssetsRes 文件夹压缩为 GameAssetsRes.zip。
5. 构建 AAB
前面的步骤生成了 base.zip、GameAssetsRes.zip,构建 AAB 需要使用 bundletool.jar,在 --modules 后加入两者的绝对路径,用 , 分隔开即可。
命令行:
java -jar [bundletool.jar ] build-bundle --modules [module的 zip 文件列表] --output=[输出的 aab]
6. 签名
最后就是对 AAB 进行签名,安卓的签名方式 v1 是 jarsigner,v2 是用 APKsigner,但是 AAB 只能通过 jarsigner 签名。签名之后最终的 AAB 就可以拿去上传 Google play 后台 了。
命令行:
jarsigner -digestalg SHA1 -sigalg SHA1withRSA -keystore [keystore 文件] -storepass [store password] -keypass [key password] [AAB文件] [store alias]
三、附录
对照表
| 源文件 | 目标文件 |
|---|---|
| base.APK/AndroidManifest.xml | ./base/manifest/AndroidManifest.xml |
| base.APK/res | ./base/root/res |
| base.APK/resources.pb | ./base/resources.pb |
| GameAssetsRes.APK/AndroidManifest.xml | ./GameAssetsRes/manifest/AndroidManifest.xml |
| GameAssetsRes.APK/resources.pb | ./GameAssetsRes/resources.pb |
| repackage/assets | ./base/assets(app配置文件和小文件放此处)、./GameAssetsRes/assets(大资源移动到此处) |
| repackage/lib | ./lib |
| repackage/kotlin | ./base/root/kotlin |
| repackage/unknown | ./base/root |
| repackage/original/META-INF | ./base/root/META-INF |
| repackage/smali、repackage/smali_classes2 ... | ./base/dex/classes.dex、./base/dex/classes2.dex ...(可以通过 smali.jar编译 smali为 dex,也可以直接用 APK 解压的 dex) |
文件对应关系
- repackage: 反编译目录
- base.APK: 生成的资源 .apk
- GameAssetsRes.APK: 分割的游戏资源 .apk
- ./lib: AAB 目录中的 lib 文件夹
- ./base: AAB 目录中的 base 文件夹
- ./GameAssetsRes: AAB 目录中的 GameAssetsRes 文件夹