Google aab生成格式剖析

687 阅读4分钟

App Bundle 文件格式

1. 基本文件格式结构

image.png

解压后的 AAB 中的内容和 APK 很相似,但又有不少区别:

image.png

后三个.pb文件是 AAB 格式的重要部分,它们描述了 APP 的不同服务目标,动态下发根据这些目标从 drawable/hdpilib/armeabi-v7a 或者 values/es 等路径中组织不同资源进行下发。

2. split apks

Split APKs 机制是 AAB 实现动态下发的基础,它允许将一个庞大的 APK 按不同维度拆分成独立的 APK,当用户在 GP 下载应用时,Android Framework 通过 IPC 与 GP 通信,为当前设备匹配并下载最小构成的 APK, 这只在 Android 5.0 以上的设备才有效。

image.png

AAB 上传后,GP 通过分析找出所有设备的共同资源, 生成一个 Base APK,当用户下载应用时,Base APK 将被首先安装。

GP 又根据languagedensityabi 等三个维度,分别生成 Configuration APKs(Splits), Splits 与 Base 共享 versionCode 、packageName等,在进程管理器中以一个应用的形式存在。

image.png

当用户从市场下载应用时,GP 根据设备类型,为其下发不同的 Splits,实现最小化下发。

如下图,针对三种不同设备下发不同 Splits

image.png

当用户的设备发生 Configuration Changed (比如切换了系统语言)时,GP 会下发新的 Splits 到手机,如果此时手机不在线会等待下次上线时自动下发。

Split APKs 的这种动态下发只能用于 Android 5.0 以上设备,对于更旧的设备,AAB 会根据 这些 Splits 的矩阵生成多个 Standalone 的 APK,虽然缺少了动态下发的能力必须一次安装到位,但是相对于传统 APK 仍然减小了一定包大小。

image.png

作为开发者,我们无需关心这些具体的下发策略,只需要向市场上传一个 AAB ,后续就交给 FW 和 GP 去处理了。

image.png

3. 上传Google Play

生成 AAB 后就可以上传应用市场了,GP 中上传 AAB 和 APK 的入口在一起,当然 8 月以后就没有 APK 的上传入口了。

image.png

例如可以查看 AAB 支持的屏幕密度,以及包体积的减少等信息。

4. Bundle Tool

AAB 是无法直接安装到手机的,如果想本地对 AAB 做测试,需要将 AAB 转成 APK,这需要使用 Google 官方提供的 Bundletool 工具。

Bundletool 可以获取当前设备信息

bundletool get-device-spec --output=/tmp/device-spec.json

设备的 Configurations 信息输出到指定 json 中

{
  "supportedAbis": ["arm64-v8a", "armeabi-v7a", "armeabi"],
  "supportedLocales": ["zh-CN"],
  "deviceFeatures" : // ...
  "screenDensity": 480,
  "sdkVersion": 28
}

Bundletool 根据 json 生成 .apks 中间文件

bundletool build-apks 
--bundle=/MyApp/my_app.aab 
--output=/MyApp/my_app.apks 
--ks=/MyApp/keystore.jks
--ks-pass=file:/MyApp/keystore.pwd
--ks-key-alias=MyKeyAlias
--key-pass=file:/MyApp/key.pwd
--device-spec=file:device-spec.json

image.png

apks 的产物分为 splitsstandalones 两个目录,splits 是按照 Configuration维度拆分的 Split APKs,必须依赖 base.apk 一起安装;standalone 必须独立安装,这是为了兼容 Android 5.0 以下的版本。

toc.pb 是 apks 的存档清单,包含 APK 集合信息的描述文件

然后再根据 json 文件,从 apks 中提取 apk :

bundletool extract-apks 
--apks=${apksPath} 
--device-spec={deviceSpecJsonPath} 
--output-dir={outputDirPath}

最后,通过 Bundletool 将 apk 安装到手机上。 注意该命令实际安装 apk 并非 apks

bundletool install-apks --apks=/MyApp/my_app.apks

总结一下 Bundletool 生成 APK 的整体流程:

image.png

5. 创建 Dynamic Feature

除了下发 Configuration APKs,还可以以业务模块为单元“插件化”地动态下发,也就是所谓的 Dynamic Features(简称 DF)

IDE 中选择 New 一个 DF 的 Module:

image.png

点击 next,选择 DF 的安装时机,例如一次安装到位或是按需安装

image.png

创建好的 DynamicFeature Module, 目录和一个普通的 Gadle Module 类似

image.png

但是 build.gradle 中 plugin 有所不同:com.android.dynamic-feature

plugins {
    id 'com.android.dynamic-feature'
    id 'kotlin-android'
}

build.gradle 中也无需配置 versionCodeversionNamesignConfig等,DF 本质上也是 Split APKs,所以共享 Base APK 的这些信息。

此时再打开 app/ 的 build.gradle,会发现多了如下配置

dynamicFeatures = [':dynamicfeature']

这是 APP 当前支持的所有 DF 的声明

最后,DF 的 Manifeset 也发生了变化:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:dist="http://schemas.android.com/apk/distribution"
    package="com.github.dynamicfeature">
​
    <dist:module
        dist:instant="false"
        dist:title="@string/title_dynamicfeature">
        <dist:delivery>
            <dist:on-demand />
        </dist:delivery>
        <dist:fusing dist:include="true" />
    </dist:module>
</manifest>
  • dist:delivery: 在创建 Dynmaic Feature 的 Module 时选择的下发方式, onDemand 表示方式为按需下发
  • title:当用户确认下载 Module 时,标识相关名称
  • fusing include:设为 ture,意味着 5.0 以下的设备可以以 multi-APK 的形式安装此 Feature,此时必须设置为 onDemand 方式。

6. 安装 Dynamic Feature

当应用支持 DF 之后,我可以按需的请求并安装这些 Features,这需要集成 Play Core SDK

implementation 'com.google.android.play:core:$latest_version'

Play Core 允许用户通过交互的方式请求 DF 的下载安装,并监听下载状态

image.png

6.1 发起下载请求

DF 的下载需要借助 SplitInstallManager

SplitInstallManager splitInstallManager =
    SplitInstallManagerFactory.create(context);

创建 SplitInstallRequest, 请求下载 Module

//动态请求模块
SplitInstallRequest request =
    SplitInstallRequest
        .newBuilder()
        .addModule("someDynamicModule")
        .build();

addModule() 可以多次调用,添加多个请求的 DF

使用 SplitInstallManager 启动 Request 进行请求,并设置回调监听为下载状态

splitInstallManager
        .startInstall(request)
        .addOnSuccessListener {  }
        .addOnFailureListener {  }
        .addOnCompleteListener {  }

startInstall() 调用后会立即发起请求。另外还可以使用 deferredInstall 延迟请求, 当应用切到后台启动时才开始请求。

splitInstallManager
      .deferredInstall(Arrays.asList("someDynamicModule"));

除了请求指定 DF 以外,也可以请求指定的资源,比如安装语言资源

SplitInstallRequest request =
    SplitInstallRequest.newBuilder()
        .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
        .build();

发起请求后,会返回一个 Int 值作为 session ID,通过调用 cancelInstall(Int), 可以取消当前的下载。

发起请求后,可能无法正常建立链接,此时会返回错误信息如下

image.png

6.2 下载安装

成功建立了连接后,便进入下载、安装阶段。使用 SplitInstallStateUpdatedListener 能够监听下载安装的状态,可以根据这些状态为对下载进度等进行用户提示

val stateListener = SplitInstallStateUpdatedListener { state ->
    when (state.status()) {
        PENDING -> { }
        DOWNLOADING -> { }
        DOWNLOADED -> { }
        INSTALLED -> { }
        INSTALLING -> { }
        REQUIRES_USER_CONFIRMATION -> { }
        FAILED -> { }
        CANCELING -> { }
        CANCELED -> { }
    }
}
splitInstallManager.registerListener(stateListener)

image.png

6.3 卸载模块

成功安装后,通过 getInstalledModules 可以获取所有已安装的 Module

val installedModules = splitInstallManager.installedModules

另外,通过 deferredUninstall 可以对 DF 进行指定卸载

splitInstallManager
        .deferredUninstall(listOf("someDynamicModule"))
        .addOnSuccessListener {  }
        .addOnFailureListener {  }
        .addOnCompleteListener {  }

6.4 AAB 使用效果

image.png

6.5 国内使用

Qigsaw 是爱奇艺提供的一套基于 Android App Bundle 的动态化方案,无需 Google Play Service 即可在国内体验 Android App Bundle 开发工具。它支持动态下发插件 APK,采用单类加载器方式,让应用能够在不重新安装的情况下实现动态安装插件。

此外,华为应用市场也早就支持了 AAB 的上传和动态下发,所以不要再说 AAB 是打压华为的产物了 。