Android App Bundles是 Android 新推出的一种官方发布格式(.aab)。通过使用Android App Bundle你可以减少应用的包大小,从而提升安装成功率并减少卸载量。
什么是Android App Bundles
Android App Bundles是 Android 新推出的一种官方发布格式(.aab)。通过使用Android App Bundle你可以减少应用的包大小,从而提升安装成功率并减少卸载量。
从上图可以看出App Bundles文件格式,他包含Base Moudle和我们拆分的Feature Module文件夹,签名文件和其他的配置文件。每个Moudle文件夹内包含dex,manifest,res,和一个resources.pb文件。和APK的文件结构基本保持一致。base module和每个Dynamic Feature Module都包含各自的代码和资源,它们组成了原先apk文件的内容。
Google Play就是基于对aab文件处理,将App Bundle在多个维度进行拆分,在资源维度,ABI维度和Language维度进行了拆分,你只要按需组装你的Apk然后安装即可。如果你的手机是一个x86,xhdpi的手机,你在google play应用市场下载apk时,gogle play会获取手机的信息,然后根据App Bundle会帮你拼装好一个apk,这个apk的资源只有xhdpi的,而且so库只有x86,其他无关的都会剔除。从而减少了apk的大小。
动态交付(Google Play Dynamic Delivery)
通过Android App bundles可以基于维度的选择减少apk大小,另外Google Play还提供了动态交付功能。Android App Bundle 支持模块化,通过Dynamic Delivery with split APKs,将一个apk拆分成多个apk,按需加载(包括加载C/C++ libraries)
- Base Apk(基本apk):此APK中包含了所有其他拆分APK都可以访问的代码和资源,并提供应用的基本功能。当用户请求下载您的应用时,会首先下载并安装该APK。
- Configuration APKs
native libraries 和适配当前手机屏幕分辨率的资源 - Dynamic feature APKs
每个动态功能 APK 都包含您应用中的某项功能的代码和资源,并且您使用动态功能模块对相应功能进行了模块化处理。
对于上述三个类型apk
启用改功能需要我们在base module中集成Play Core Library。用户在Google Play下载一个通过Android App Bundle 方式开发的应用时,只会下载base module对应的apk文件,Dynamic Feature Module对应的apk文件会在运行时按需下载。Play Core Library用来在App运行时请求下载Dynamic Feature Module对应的apk。可以查看Play Core API 使用
Google play 在下载与更新APK是如何处理按需模块与基本模块的呢?
https://codelabs.developers.google.com/codelabs/on-demand-dynamic-delivery/index.html#7
Note:
To test the download of an on-demand module it's not enough to update the application on the device because the update operation also updates all the on-demand modules that are already installed.
To test the download of the module, you have to uninstall the application and install it again. In this way the on-demand modules are not going to be installed.
可以看出Google Play的下载与更新APK的逻辑很简单,每次下载时都会只下载非按需加载模块,下载了基本模块之后,就可以按需加载其他模块。如果之后我升级了APK,按需在加载模块是怎么处理的呢?Google Play会同时为我们更新。这样我们无需为按需加载模块做版本兼容处理。Base Moudle与 Dynamic Moudle版本永远都会是保存一直的。因为Google Play都是基于一个AAB文件构建出APK交付给用户。
Bundletool
我们通过android studio 的build bundle功能生成aab格式文件,我们必须测试 Google Play 使用该 Android App Bundle 生成 APK 的情形。验证方式1:bundletool 命令行工具进行测试。验证方式2:通过 Google Play 将您的 app bundle 上传到 Play 管理中心并使用测试轨道进行测试。
下面我们看看Bundletool的具体使用方式
使用bundletool build-apks 命令从 app bundle 生成一组 APK
java -jar bundletool-all-0.10.3.jar build-apks --bundle=58WuxianClient.aab --output=my_app.apks
我看查看一下apks的文件解结构,分为两个目录splits和standalones
splits目录:可以看出splits就是对各个moudle的在资源维度,ABI维度和Language维度的拆分。base moudle和car moudle基于维度各自产生了apk集合。当我们使用app bundle上传到google play后,在google play安装apk时(手机android版本>=21即android 5.0),google play就会在apk集合中找到和手机语言,API,分辨率相同的apk安装到手机从而减少apk大小。
standalones目录:因为对于小于21的android手机是不支持多apk的模式安装的,同时也不支持按需加载,所以对于该类型的手机要生成一个APK,当然也在维度进行了拆分。每个包的大小都在93.9M左右。(与现在的9.1.0版本相比增大了3M)
通过install-apks命令将 APK 部署到连接的设备(andoid 版本为5.1 OPPO R9)
java -jar bundletool-all-0.10.3.jar install-apks --apks=my_app.apks
从文件结构可以看出推送到手机的apk包含4个base.apk,split_config.armeabi.apk,split_config.xxhdpi.apk,split_config.zh.apk。根据手机的设备信息安装对应的apk.但是我们发现没看car.apk文件这是为什么呢?
<dist:module
dist:instant="false"
dist:onDemand="true"
dist:title="@string/title_dynamic_feature">
<dist:fusing dist:include="true" />
</dist:module>
因为我们启用了按需加载所以安装的模块中并没有car.apk,我们需要在base moudle中使用Play Core Library用来在App运行时请求下载Dynamic Feature Module对应的apk。
如果我们把上述的onDemand改为false,我们在重新安装apk,我们在查看一下apk的安装目录(为红米k20 通过android studio 无法直接查看安装目录只能通过adb命令),就会发现存在car模块对应的apk了,包含split_CarLib.apk和配置模块split_CarLib.config.xxhdpi.apk
192:50APP wangyongchuan$ adb shell pm path com.wuba
package:/data/app/com.wuba-flDXC2tcSXaidf_VbJVuMQ==/base.apk
package:/data/app/com.wuba-flDXC2tcSXaidf_VbJVuMQ==/split_CarLib.apk
package:/data/app/com.wuba-flDXC2tcSXaidf_VbJVuMQ==/split_CarLib.config.xxhdpi.apk
package:/data/app/com.wuba-flDXC2tcSXaidf_VbJVuMQ==/split_config.armeabi.apk
package:/data/app/com.wuba-flDXC2tcSXaidf_VbJVuMQ==/split_config.xxhdpi.apk
package:/data/app/com.wuba-flDXC2tcSXaidf_VbJVuMQ==/split_config.zh.apk
根据aab文件生成一个全量apk
bundletool 只生成一个包含应用的所有代码和资源的 APK,以使该 APK 与应用支持的所有设备配置兼容,使用 universal 参数。
java -jar bundletool-all-0.10.3.jar build-apks --bundle=58WuxianClient.aab --output=all.apks --mode=universal
我们是基于9.1.0版本只对二手车业务进行了改造发现生成全量apk包大小为(95.5M)增加了4.8M,那这些大小增加在哪里呢?
通过对比我们发现res文件增加了2.6M lib文件增加0.1M asset文件增加0.1 代码增加0.7M
获取当前设备的信息文件
java -jar bundletool-all-0.10.3.jar get-device-spec --output=device-spec.json
设备信息文件内容
{
"supportedAbis": ["arm64-v8a", "armeabi-v7a", "armeabi"],
"supportedLocales": ["zh-CN"],
"deviceFeatures": ["reqGlEsVersion=0x30000", "android.hardware.audio.output", "oppo.fulldiskencryption.unsupported", "oppo.guard.elf.support", "oppo.high.brightness.support", "oppo.hw.manufacturer.mtk", "oppo.inexact.alarm", "oppo.leather.proximity.sensor.support", "oppo.memory.auto.clean", "oppo.memory.auto.deep.clean", "oppo.multi.touch.camera.support", "oppo.ota.twokey.not.support", "oppo.otg.connection.menu.support", "oppo.quick.shot.support", "oppo.screen.hovering.support", "oppo.soundeffect.support", "oppo.support.single.partition", "oppo.sw.solution.device", "oppo.tp.limit.support", "oppo.volte.support"],
"screenDensity": 480,
"sdkVersion": 22
}
通过上述的booltool命令是使用方法,也许我们已经产出了如何应用与58工程,来加快apk编译与为后期使用App Bundle场景的改造。
1.将58工程基于App Bundle改造为Base Moudle与feature Moudle
2.改造完成开发模式直接点击构建就可以了,android stuido直接会将split apk推送到手机设备(android 5.0+)
3.对于正式版本构建第一步使用gradle命令构建aab格式文件,第二步使用universal 参数将aab转换为一个全量的apk,当做正式包使用。(后续的渠道包操作都是基于这个apk进行操作)
使用 Android App bundles项目依赖结构
从上图可以看出,使用AAB,项目的依赖结构发生了变化。有base和feature0模块,在base中无法直接引用feature模块的类,feature模块可以直接依赖base模块。
应用模块化带来的好处:
- 并行开发:将应用拆分不同的模块,各个团队开发自己负责的模块。
- 加快编译时间:Gradle 的并行项目执行优化,编译系统就能够并行地编译多个模块,从而显著减少编译时间。
- 动态模块按需加载减少apk大小。
动态模块上传到maven?
我们知道library工程模块可以通过aar的方式上传到maven仓库,动态模块的构建产物是apk可以上传到maven仓库吗?不可以。通过 Android App bundles模块化拆分的项目如果你想要编译它,必须要源码依赖工程。这样每个业务线开发时只能查看自己业务都构建功能,不可以查看其他业务线的功能。
AAB 拆分注意事项
- dynamic-feature moudle引用base modle资源时,不能直接使用R.drawdble 需要使用 [base moudle packagename].R.drawdble的方式
- dynamic-feature 项目名称不能以数据开头如58CarLib 需要改写为CarLib
- java.io.IOException: Cannot find PROCESSED_RES output for Main{type=MAIN, fullName=flavor1Debug, filters=[], versionCode=-1, versionName=null}异常 需要注释掉build.gradle的splite {abi{}}
- basemoude不可以访问feature moudle中的id
CarLib中arssc文件中R.id.title_filter_btn中的值为0x7e0704d8
BaseMoude中arssc文件中R.id.title_filter_btn中的值为0x7f090d85
因为feature与baseMoude都有各自的arsc文件,虽然属性名称一直但是id值是不一致的,所以basemoude中涉及访问feature moudle的id值都需要修改 - 动态模块配置制定title 必须通过如下方式配置 dist:title="@string/title_dynamic_feature" 不能直接编写字符串 并且改字符串必须写在base moudle中。
- 当打开on-demand(按需加载)时,必须开启Fusing(熔断操作)才能正常的让Api21以下的手机使用module
- 一般情况下,动态模块下发之后需要重启App才能加载成功,但是如果你使用SplitCompat library,就可以立即生效
- 如果下载的模块太大,需要用户确认,GP要求大于10MB需要用户确认
- 国际上可以使用Google Play的Play Core Lib直接从gp后台下载我们上传好的dynamic module.
- module中的AndroidManifest中定义的Activity不能有exported:true因为别的app不知道你何时安装好模块从而会引发问题
- proguard文件在生效的时候会merge base module和所有的dynamic module中的文件,所以在编写proguard的时候要注意这个问题。
res资源结构
每个feature moudle都会生成自己独立的arsc文件,同时为了不与其他moudle产生冲突,每个moudle其资源id的头两位都是有差异的。从图中可以看出car的feature moudle的资源id是以0x7e开始而不是0x7f。
因此在资源id使用时需要注意一下几点:
- dynamic-feature moudle引用base modle资源时,不能直接使用R.drawdble需要使用 [base moudle packagename].R.drawdble的方式
- basemoude不可以访问feature moudle中的id
CarLib中arssc文件中R.id.title_filter_btn中的值为0x7e0704d8
BaseMoude中arssc文件中R.id.title_filter_btn中的值为0x7f090d85
因为feature与baseMoude都有各自的arsc文件,虽然属性名称一直但是id值是不一致的,所以basemoude中涉及访问feature moudle的id值都需要修改。
类文件构建规则
原理
资源加载 与 类加载机制
展望
构建速度方面:
App Bundles 方案在减少APK大小方面,就有很大的优势。但是App Bundles方案依托与Google Play应该才能做到业务模块的按需加载。但是目前爱奇艺开源了Qigsaw框架,自己实现了一套类型Google Play的方案,同时保持API的使用与Google Play保持一致,这样就可以做到国内与国外场景的自由切换。
参考
https://juejin.im/entry/5b1e233ef265da6e0d7a33b3
https://blog.csdn.net/u010479969/article/details/81261
https://agehua.github.io/2019/03/27/Android-APP-Bundles-Introduction/
https://zhuanlan.zhihu.com/p/34346219
https://developer.android.com/guide/app-bundle/
https://developer.android.com/studio/projects/dynamic-delivery
https://developer.android.com/studio/command-line/bundletool
https://developer.android.com/guide/app-bundle/playcore
https://github.com/android/app-bundle-samples/tree/master/DynamicFeatures
https://zhuanlan.zhihu.com/p/83411322
https://codelabs.developers.google.com/codelabs/on-demand-dynamic-delivery/
https://codelabs.developers.google.com/codelabs/your-first-dynamic-app/index.htm
https://github.com/fireantzhang/AppBundleDemo
https://github.com/google/bundletool