聊聊 Android App Bundle

什么是 Android App Bundle

关于 Android App Bundle(下文简称 aab),引用以下官方的定义:

Android App Bundle 是一种发布格式,其中包含您应用的所有经过编译的代码和资源,它会将 APK 生成及签名交由 Google Play 来完成。Google Play 会使用您的 App Bundle 针对每种设备配置生成并提供经过优化的 APK,因此只会下载特定设备所需的代码和资源来运行您的应用。您不必再构建、签署和管理多个 APK 来优化对不同设备的支持,而用户也可以获得更小且更优化的下载文件包。

从 2021 年 8 月起,新应用需要使用 Android App Bundle 才能在 Google Play 中发布。因此对开发者来说,了解什么是 aab 非常重要。

如何构建 aab

构建 aab

像 CI、脚本等环境使用命令行构建,可以使用 Gradle 命令打包 debug 版本的 aab 包:./gradlew bundleDebug

aab 严格上来说是一种发布格式,真正在安装 app 时还是使用 apk 包。只是借助于 aab 这种发布格式,Google Play 提供了按条件分发或按需下载应用的某些功能。

动态下发能力

提到动态下发,大家可能首先会想到热修复。业内的热修复方案使用的比较多的是类加载方案,基于 dex 分包,从 Java 类加载机制作为切入点,将需要动态下发(修复)的代码打成 dex 包或者通过算法合成差量 dex 包下发至客户端,并在下次 APP 启动时优先加载新的修复过的类,Java 类加载机制会保证旧的类不再被加载,从而实现动态修复的效果。

Google Play 的动态下发方案有别于热修复,它不适用于对已有代码的动态修改,它更像一种插件化方案,借助于 aab 包的格式,将原来一个庞大的 apk 按照不同的维度拆分成独立的 apk,当用户在 Google Play 商店下载应用时,Android 系统通过与 Google Play 商店通信,为当前设备匹配并下载最小的 apk,从而实现更快的下载速度。

不仅如此,借助于 Dynamic Feature 的特性,配合 com.google.android.play:core 组件的动态加载代码功能(详见 Play Feature Delivery ),还可以实现按需下载模块,实现真正的插件化。

那么 aab 是如何为插件化方案助力的呢?

创建功能模块

首先我们创建一个功能模块(Dynamic Feature)看一下效果,在 Android Studio 中新建模块时选择 Dynamic Feature Module:

功能模块

这里我们添加一个名为 dynamicfeature 的模块。通过这种方式添加的模块,Android Studio 会自动对该模块的 build.gradle 应用插件 apply plugin: ‘com.android.dynamic-feature’ 用于编译功能模块。

我们打出 aab 包看下添加了功能模块之后的包结构和普通的模块有什么不同:

模块结构

对比一下 apk 格式:

apk 格式

可以看到 aab 格式和传统的 apk 格式有以下几个区别:

aab 格式 apk 格式
模块划分 分为 base 模块和 dynamic feature 模块。 无模块划分
manifest.xml 每个模块都有一个 只有一个
dex 每个模块的 dex 存储在各自模块的目录中 全部存放于根目录
资源索引表 resources.pb,使用 ProtoBuf 协议格式,每个模块都有一个 resource.arsc

除了上面提到的 resources.pb 以外,还有 assets.pb、native.pb,这三个 pb 文件是 aab 格式的重要部分,它们描述了 APP 的不同服务目标,动态下发时会根据这些目标而组织不同的资源进行下发。

动态下载功能模块

动态下载功能模块的示例代码详见 app-bundle-samples。下载并安装功能模块大致分为以下几个步骤:

一、使用工厂类创建下载服务

1
var manager: SplitInstallManager = SplitInstallManagerFactory.create(this)

二、通过模块名创建下载功能模块的请求

1
2
3
val request = SplitInstallRequest.newBuilder()
.addModule(name)
.build()

三、启动请求,并在下载后执行安装

1
manager.startInstall(request)

借助于 aab 格式对功能模块代码与资源的模块划分,因此可以很方便地从 aab 包中提取出功能模块的内容,再配合 Google Play 的下载服务,就可以无侵入地实现插件化,按需下载、安装、卸载功能模块,除此之外,还可以按需请求指定的资源,例如语言包等等。

如何使用 aab

上文有提到,aab 是一种发布格式,用于上传至 Google Play 对外发布,日常开发、测试时依然使用的是 apk 包。但如果想要对 aab 包做兼容测试,例如验证是否可以正确地提取 apk ,或者使用功能模块后,对应用的基本模块是否有影响等问题,这种情况下要怎么使用 aab 格式文件呢?这就需要用到官方提供的工具 BundleTool (下载地址)。下载 jar 后配置下环境变量就可以使用了。

bundletool 提供了诸多命令用于本地测试 aab 包,例如 build-apks 命令用于生成 apk:

1
bundletool build-apks --bundle=/xxx/test.aab --output=/xxx/test.apks --connected-device

上述命令的 output 的路径必须以 .apks 后缀结尾。上述命令会为应用支持的所有设备配置生成一组 APK,并将这些 APK 纳入到一个名为“APK set archive”的容器中,该容器以 .apks 作为文件扩展名。

根据名称 apks 顾名思义,大胆猜测 .apks 文件应该也只是一个压缩包,尝试手动把后缀改为 .zip 后解压可以看到文件的结构:

文件结构

文件结构

包含了 splits 和 standalones 文件夹和一个 .pb 后缀的文件。

生成的 apks 文件不同于 apk,我们无法直接使用 apks 文件安装 app,bundletool 另外提供了 install-apks 命令,用于在生成一组 apk 后,将其中适当的
apk 组合部署到已连接的设备。

1
bundletool install-apks --apks=/xxx/test.apks

通过 build-apks、install apks 组合使用,可以实现使用 aab 格式文件来安装 apk。通过我们上面的分析,我们大概能了解到在文章开头提到的“Google play 只会下载特定设备所需的代码和资源来运行您的应用”,其本质就是对不同的代码和资源做了分包,然后当不同的设备访问 Google Play 商店下载应用时,只需要下发对应的代码和资源包即可。bundletool 是开源的,我们跟随源码看一下 install-apks 命令做了什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//src/main/java/com/android/tools/build/bundletool/commands/InstallApksCommand.java

DeviceSpec deviceSpec = new DeviceAnalyzer(adbServer).getDeviceSpec(getDeviceId());
//执行安装前,获取需要安装的代码和资源
final ImmutableList<Path> apksToInstall =
getApksToInstall(toc, deviceSpec, tempDirectory.getPath());

//src/main/java/com/android/tools/build/bundletool/device/DeviceAnalyzer.java

//组合设备的各种信息,用于提取对应的资源
DeviceSpec.Builder builder =
DeviceSpec.newBuilder()
.setSdkVersion(deviceSdkVersion)
.addAllSupportedAbis(supportedAbis)
.addAllSupportedLocales(deviceLocales)
.setScreenDensity(deviceDensity)
.addAllDeviceFeatures(deviceFeatures)
.addAllGlExtensions(glExtensions);
if (codename != null) {
builder.setCodename(codename);
}
return builder.build();

正是通过获取设备的 sdk 版本、支持的 abi、设备屏幕密度等等信息,从 aab 中提取出对应的资源,从而达到“瘦身”下载的效果。

就像下图所示,完整的 aab 包里包含了应用的所有资源,例如 xhdpi、xxhdpi 等各类屏幕密度的图片资源;x86、arm64 等各类架构的 so 库;en、zh 等各国语言资源包,等等,aab 是一种大而全的存在(apk 也一样),但是通过 Google Play 商店的动态分发,实现了针对不同的设备的小而精的瘦身下载。

总结

aab 作为一种新的发布格式,海外市场通过 Google Play 商店可以便捷地实现“瘦身”下载与插件化能力,但在国内市场因为大部分手机都没搭载 Google 服务套件,因此无法体验到这些服务。但是通过探究其实现原理,我们可以借助 aab 实现自己的插件化方案,这种无侵入的插件化思想值得我们学习。