9

Android App Bundle解析

 3 years ago
source link: https://zhuanlan.zhihu.com/p/86995941
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Android App Bundle解析

58同城 Android工程师

什么是Android App Bundle

v2-eb86f528d29cf5f071896c43e9ef9193_720w.jpg

Android App Bundle是 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的大小。

v2-12970665c0b1985b04c9af6040b7768d_b.jpg

动态交付(Google Play Dynamic Delivery)

通过Android App bundle可以基于维度的选择减少apk大小,另外Google Play还提供了动态交付功能。Android App Bundle 支持模块化,通过Dynamic Delivery with split APKs,将一个apk拆分成多个apk,按需加载(包括加载C/C++ libraries)。下面是split APK的几种类型:

  1. Base Apk(基本apk):此APK中包含了所有其他拆分APK都可以访问的代码和资源,并提供应用的基本功能。当用户请求下载您的应用时,会首先下载并安装该APK。
  2. Configuration APKs
    native libraries 和适配当前手机屏幕分辨率的资源
  3. Dynamic feature APKs (上图中的aab文件中的Car.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=app.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左右。(与现在版本相比增大了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 xxx$ adb shell pm path xx.xx
package:/data/app/xxxx-flDXC2tcSXaidf_VbJVuMQ==/base.apk
package:/data/app/xxxx-flDXC2tcSXaidf_VbJVuMQ==/split_CarLib.apk
package:/data/app/xxxx-flDXC2tcSXaidf_VbJVuMQ==/split_CarLib.config.xxhdpi.apk
package:/data/app/xxxx-flDXC2tcSXaidf_VbJVuMQ==/split_config.armeabi.apk
package:/data/app/xxxx-flDXC2tcSXaidf_VbJVuMQ==/split_config.xxhdpi.apk
package:/data/app/xxxx-flDXC2tcSXaidf_VbJVuMQ==/split_config.zh.apk

根据aab文件生成一个全量apk

bundletool 只生成一个包含应用的所有代码和资源的 APK,以使该 APK 与应用支持的所有设备配置兼容,使用 universal 参数。

java -jar bundletool-all-0.10.3.jar build-apks --bundle=app.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命令是使用方法,也许我们已经产出了如何应用与目前的工程,来加快apk编译与为后期使用App Bundle场景的改造。
1.将目前的工程基于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和feature模块,在base中无法直接引用feature模块的类,feature模块可以直接依赖base模块。
应用模块化带来的好处:

  1. 并行开发:将应用拆分不同的模块,各个团队开发自己负责的模块。
  2. 加快编译时间:Gradle 的并行项目执行优化,编译系统就能够并行地编译多个模块,从而显著减少编译时间。
  3. 动态模块按需加载减少apk大小。

res资源结构

每个feature moudle都会生成自己独立的arsc文件,同时为了不与其他moudle产生冲突,每个moudle其资源id的头两位都是有差异的。从图中可以看出car的feature moudle的资源id是以0x7e开始而不是0x7f。 因此在资源id使用时需要注意一下几点:

  1. dynamic-feature moudle引用base modle资源时,不能直接使用R.drawdble需要使用 [base moudle packagename].R.drawdble的方式。因为dynamic-feature的R文件只包含自身的资源。如何需要使用base Moudle的资源需要指定包名。
  2. 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值都需要修改。

对于第二点我们看一下具体的场景。

我们在base moudle中写一个BaseFragment,并且设置了布局文件。布局文件中设置了默认的title布局。title布局的id为R.id.title。并且设置了默认的文字。在feature moudle中我们继承这个BaseFragment写一下我们个性化的逻辑,如我们需要将title的布局改为红色背景。我们就需要重写布局。将title的布局backgroundcolor改为红色,并且id也必须是R.id.title。这个逻辑在之前工程结构是没有问题的。但是当我们转为feature moudle时就是有问题的,因为feature moudle与base moudle的id名称一样,但是他们的值是不一样的,这个baseFragment在获取R.id.title时,就会获得一个空指针。

动态模块上传到maven?

我们知道library工程模块可以通过aar的方式上传到maven仓库,动态模块的构建产物是apk可以上传到maven仓库吗?不可以。通过 Android App bundles模块化拆分的项目如果你想要编译它,必须要源码依赖工程。这样每个业务线开发时只能查看自己业务都构建功能,不可以查看其他业务线的功能。

AAB 拆分注意事项

  1. dynamic-feature moudle引用base modle资源时,不能直接使用R.drawdble 需要使用 [base moudle packagename].R.drawdble的方式
  2. dynamic-feature 项目名称不能以数字开头
  3. java.io.IOException: Cannot find PROCESSED_RES output for Main{type=MAIN, fullName=flavor1Debug, filters=[], versionCode=-1, versionName=null}异常 需要注释掉build.gradle的splite {abi{}}
  4. 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值都需要修改
  5. 动态模块配置制定title 必须通过如下方式配置 dist:title="@string/title_dynamic_feature" 不能直接编写字符串 并且改字符串必须写在base moudle中。
  6. dynamic-feature moudle与base moudle的manifest文件最终会统一融合一个manifest文件,所以要保障manifest的资源引用均在base moudle中。
  7. aab转为全量apk出现65535问题,我们需要hook transformClassesWithBundleMultiDexList的Task替换mainDexList文件,确保aab在转为全量apk时使用该规则。
  8. 当打开on-demand(按需加载)时,必须开启Fusing(熔断操作)才能正常的让Api21以下的手机使用module
  9. 一般情况下,动态模块下发之后需要重启App才能加载成功,但是如果你使用SplitCompat library,就可以立即生效
  10. 如果下载的模块太大,需要用户确认,GP要求大于10MB需要用户确认
  11. 国际上可以使用Google Play的Play Core Lib直接从gp后台下载我们上传好的dynamic module.
  12. module中的AndroidManifest中定义的Activity不能有exported:true因为别的app不知道你何时安装好模块从而会引发问题
  13. proguard文件在生效的时候会merge base module和所有的dynamic module中的文件,所以在编写proguard的时候要注意这个问题。

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

https://developer.android.com/guide/app-bundle/at-install-delivery


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK