东半球第二好懂的AAB文档

974 阅读8分钟

  什么是AAB?

  AAB全程Android App Bundle,是由谷歌推出的一种,面向应用开发者&应用分发渠道(商店) 的特殊应用打包格式

  谷歌于2021年起,已强制要求所有app上传AAB到应用商店,不允许再上传apk。

  AAB诞生前的故事

  远古时代

  远古时代,应用开发者上传完整的apk包至应用商店,最终分发给用户的也是完整的apk包:

  这会导致用户安装在设备上的apk里,包含了许多用户不需要的功能:

  • 低分辨率的手机,安装包里却有高分辨率的图片
  • 英文的手机,安装包里却有100+种语言
  • 32位的手机,安装包里却有64位的so文件
  • 欧美的手机,安装包里却有只面向亚洲的功能代码

  中世纪

  为此,Google Play Store发明了............发明了multiple apks!

  developer.android.com/google/play…

  看下这个恐怖的压缩包:

  2个abi纬度x7个dpi纬度 = 14个apk

  多一个x86 abi?多一个xxxxhdpi?多一个语言纬度?100个语言?? ➡️ 爆炸的apk数量

  动态切换语言?动态代码下发???不太可能做到 ➡️ 不够极**致

  AAB的诞生

  由于上述问题,Android Lolipop中悄咪咪地引入了一个新功能:split apk

  • 允许一个包名下安装多个apk
  • 每个apk都是完整的apk结构,必含AndroidManifest.xmlresources.arscMETA-INF签名,可选包含classes.dex
  • AOSP会在app打开时自动读取所有split apk到classloader、AssetsManager中
  • 除了主包外,其余包需在AndroidManifest.xml中添加split=xxx,表明为split包

为什么 AOSP 要标记为split包,而不让split包和base包平级?

从AOSP的代码/功能中,可以猜测几个原因:

  1. 安卓的古早代码,就是从一个apk里解析~~~~ApplicationInfo~~~~的,多个apk的兼容逻辑不好写

  2. 为了区分app安装split apk还是覆盖升级的情况

  3. AOSP一开始就准备为split apk做isolatedSplits模式,这个模式下:每个split apk之间的class、resource不再合并在一起,而是各自独立,能共享访问的只有base包。

  除了split apk之外,谷歌对apk分发还有更多考量:

  • 谷歌还需要一种比,把所有split apk打包成zip,更高效的包格式,以节省网络带宽
  • 谷歌希望自己有机会对apk包进行更多透明的包体积优化
  • 排列组合、整体大包在一些情况下依然需要,谷歌不想为此造成过多的带宽浪费

  于是,谷歌推出了全新的AAB文件格式:

  • 开发者上传AAB文件到Google Play,Google Play再生成各个split apk分发给用户
  • Play Store再根据用户手机配置,按需安装所有需要的split apk。

  • AAB文件格式易于谷歌商店解析,以便于再生成apk
  • AAB文件内容相比apk,更加紧实:

  183MB的AAB生成202MB的完整apk

  • AAB格式大量应用protobuf:pb格式的aab描述文件、pb格式的arsc/AndroidManifest.xml
  • AAB作为中间产物,可以携带部分仅面向开发者的元信息,如:proguard mapping

  同时,谷歌开源了将AAB编译为apk的工具:bundletool

  Q:不支持split apk(<5.0)的设备,就不能使用AAB了吗?

  A:也是可以使用AAB格式的。bundletool除了默认模式,提供了另外两种模式:universalstandalone

  universal模式时,AAB将不再分资源apk,会生成一个包含了所有资源的universal.apk

  standalone模式时,AAB会排列组合abi、dpi来生成apk

standalone其实不是一个独立的bundletool模式,而是默认模式的附加产物。

当app的minsdk>19(>安卓4.4)时,standalone apk将不再会生成

  Q:谷歌这个神奇的网站是怎么统计到数据的?play.google.com/sdks/catego…

  A:每个AAB包里都记录了这个app所用到的library的信息:

  灵活的AAB多语言

  当一个应用AAB化后,Google Play在给用户安装此app时,只会安装系统需要的语言包

  手机如果如上图设置,Google Play Store就只会为app安装简体中文、日文、繁体中文(香港/台湾)的语言包

  同时,Google Play也可以为app动态添加语言:

  • 动态语言下发: app可以请求安装新的语言包(如TikTok的设置-语言页),这时候app就需要在运行时向Google Play请求安装一个语言包。这就是动态语言下发。

  • 自动语言包更新: Google Play Store会注册android.intent.action.LOCALE_CHANGED广播,当系统语言发生更改时自动为app安装新的语言包

不是有很意思的小知识

Google Play收到android.intent.action.LOCALE_CHANGED广播时,app还是运行着的,Google Play会怎么办?

A. 等待app退出,再安装语言包

B. 在app运行时,偷偷为app装上语言包

C. 通知app,让其发起新语言包请求

D. 钝角

正确答案是:◼︎(刮开屏幕,查看正确答案)

  Dynamic Feature简介

  Dynamic Feature体系类似于国内的插件化体系(如Mira),但是解决的问题不同:

  国内的插件化体系,是为了减少包体积、业务代码热更新;而Dynamic Feature只是为了减少包体积,不具备热更新能力。

  开发者可以将不常用的功能制作成独立于核心包的dynamic feature包,并配置只在一些情况安装。谷歌目前允许两种Dynamic Feature安装方式:

  • install-time:在app安装时,根据开发者指定的条件(例如,只在美国安装、只在Android>7安装),顺带安装
  • on-demand:app在运行时,主动向Google Play Store请求动态安装

Dynamic FeatureMira
插件版本与宿主保持一致,不允许不一致允许不同插件版本
安装途径Play Store帮安装、app自行安装app自行安装
分发方式Google Play服务器自建服务器
安装方式Split apk(PlayStore安装)/反射classloader注入(自行安装)反射classloader注入
编译工具链AGP原生支持Mira魔改AGP支持

  以上配置规则翻译成中文就是:

  • 谷歌小程序(Google Play Instant):否

  • Dynamic Feature对用户展示的标题:Oppo Push Module

  • Google Play Store帮我们安装:

    • 除了国家是US时,都安装
  • Fusing至Universal APK中:是

  最终Dynamic Feature编译至AAB中:

  AAB用bundletool生成的结果:

Dynamic Feature 在不支持split apk(<5.0)的系统上,怎么办?

bundletool提供了universal模式,允许生成包含了所有资源的universal.apk。

那么dynamic feature默认也会进入universal apk吗?

默认是不会的!DF模块必须在AndroidManifest.xml<dist:fusing dist:include="true" />,才会被集成到universal.apk中

  Dynamic Feature下发

  当Google Play判断Dynamic Feature符合直接安装条件时,就会将Dynamic Feature apk作为split apk,在app首次安装/更新时顺带安装


  当Google Play未自动安装Dynamic Feature时,开发者可以手动请求Dynamic Feature安装,即on-demand模式:

  developer.android.com/guide/playc…

// 创建一个SplitInstallManager.
val splitInstallManager = SplitInstallManagerFactory.create(context)

// 创建安装Dynamic Feature的请求
val request =
    SplitInstallRequest
        .newBuilder()
        // 想装几个装几个
        // 这些模块会在一个session里一起装上
        .addModule("pictureMessages")
        .addModule("promotionalFilters")
        .build()

splitInstallManager
    // 需要在前台提交请求,然后请求就会异步开始
    .startInstall(request)
    .addOnSuccessListener { sessionId -> ... }
    .addOnFailureListener { exception ->  ... }

  当我们发起下载后,会发生的事:

  1. Dynamic Feature SDK会将请求发送给Play Store,Play Store开始Dynamic Feature下载
  2. Play Store在DF下载过程中,会随时向app发送进度回调
  3. Play Store下载完成后,会将df apk的content resolver uri发送给app
  4. app读取Play Store下载好的df apk,验证,解包,解压到app的data目录下

  至此,Dynmiac Feature的“Install Request”就算完成了

  然后就可以正常运行Dynamic Feature里的代码了!吗?

  其实,完成上述步骤后,Dynamic Feature其实并没有“Install”(虽然回调叫做INSTALLED),离Install还差最后一步:

SplitCompat.install(context)

  SplitCompat如其名,就是在运行时“compat” split apk的helper类。其工作原理,就是将解包好的dex、classloader、arsc反射到app中。

  很蠢对吧!谷歌自己发明的AOSP、GMS,居然还是用这么黑科技的方法为app启用on-demand Dynamic Feature?开发者能忍谷歌自己也不能忍!

on-demand没用到split apk?安卓4.4在技术上也兼容运行时 DF

没错!Dynamic Feature的运行时安装法,在安卓4.4的设备上也是能正常运行的!但是谷歌出于一致性的考量,禁用了这个用法。

但是我们通过反编译Play Store,就能发现,谷歌给一些app特别开了后门(如facebook),他们的一些app的确可以在4.4的设备上要求Play Store下发Dynamic Feature给他们。

不是有很意思的小知识

on-demand的Dynamic Feature在app升级后会发生什么?

A. Google Play会在升级时顺便用install-time方式安装Dynamic Feature

B. Play Store会通过修改app data目录下df文件的方法升级Dynamic Feature模块

C. App在启动后会自动开始Dynamic Feature的升级

D. Dynamic Feature被卸载

正确答案是:◼︎(刮开屏幕,查看正确答案)