[TOC]
# AAB
## App Bundle 文件格式
Android App Bundle是一种全新的应用上传格式(.aab),它包含所有编译代码和资源。当您上传aab文件至Google Play后,Google Play将aab文件拆分成一系列APKs并签名。
![](https://img.kancloud.cn/af/f7/aff73d9c348cd6814265d8bd5c477b21_992x650.png)
![](https://img.kancloud.cn/85/5c/855c698a584b420b0f4cc1a23e6f9412_1200x582.png)
### AAB 中的内容和 APK异同
| aab fiels | descriptions |
| :-- | :-- |
| base/feature1/feature2 | base 是应用的基本功能,feature 承载各 DynamicFeature 的内容(后文介绍) |
| manifest.xml | APK 中只有一个 manifest 且是二进制格式,AAB 会存在于每个模块中e中,且使用 ProtoBuf(pb)格式,便于处理 |
| dex | 与 APK 不同,AAB 将每个模块的 dex 文件存储在各自目录中 |
| res/assets/libs | 该目录与 APK 中相同,当上传 AAB 时,GP 会检查这些目录并仅打包满足目标设备需要的最小文件 |
| resources.pb | 类似于 resource.arsc 文件,是一个资源索引表,其中描述了应用程序内部存在的资源和目标的细节,可用于 GP 针对不同设备配置 APK。 |
| assets.pb | 相当于应用程序 assets 的资源表,可用于 GP 针对不同设备配置 APK。例如将 assets 资源放到 assets/languages#lang\_xx 或 assets/i18n#lang\_xx 路径下,则会根据语言配置下发 assets 资源。 |
| native.pb | 这相当于native库的资源表,可用于 GP 针对不同设备配置 APK |
后三个`.bp`文件是 AAB 格式的重要部分,它们描述了 APP 的不同服务目标,动态下发根据这些目标从 `drawable/hdpi`、`lib/armeabi-v7a` 或者 `values/es` 等路径中组织不同资源进行下发。
## Dynamic Feature
此外,您也可以在应用项目中添加dynamic feature模块,这些模块并不需要在应用首次安装时一起被下载安装。您可以通过使用Play Core Libray在应用运行过程中动态安装dynamic feature。dynamic feature类似国内插件化提供的能力,但dynamic feature功能更强大。
通过下图,可以看到dynamic feature可以基于设备配置选取对应的Configuration Split APKs,如此可以进一步减小dynamic feature安装包体积。
![](https://img.kancloud.cn/5b/14/5b14a12918e6305a92e9cb9eaecba6d9_1200x828.png)
### Split APKs(Android5.0)
Android App Bundle之所以能够支持应用运行期间安装dynamic feature,得益于Android 5.0推出的Split APKs功能。
Split APKs是Android 5.0引入的一种全新应用安装机制,其目的是为解决APK体积日益增大问题。Split APK可以将一个完整庞大的APK按照CPU架构、屏幕密度等维度拆分成多个独立APKs。当应用APK下载更新时,依据当前设备配置选取对应配置APKs安装即可。
Android 5.0之前,一个APK代表一个应用。在Split APKs问世之后,一个应用可能对应多个APKs。所有Split APKs拥有相同包名和签名。
Android提供两种方式安装Split APKs。
adb install-multiple [base-apk, split1-apk]
PackageInstaller.
Android App Bundle为dynamic feature提供全新插件com.android.dynamic-feature,它的编译产物是.apk文件。当您的项目编译完成后,Android Studio通过命令adb install-multiple命令将base apk和split apks安装至您的手机。如果您的开发手机系统版本低于5.0,则会依据当前手机设备组装成一个完整apk文件安装至该手机。
![](https://img.kancloud.cn/c9/70/c970587017cce5654631afdcb404b0b7_1200x741.png)
下面我们重点介绍第二种安装方式,Android 5.0提供PackageInstaller用于安装Base APK和Split APKs。
#### PackageInstaller
当第三方应用通过PackageInstaller在应用运行期安装Split APKs时,系统会启动安装器界面供用户选择是否安装此次更新。
![](https://img.kancloud.cn/fe/e7/fee74cd2d081eadf72efa4e622f5e5e9_1200x814.png)
在用户选择安装后,应用将会被系统“杀死”。当应用再次启动之后,Split APKs就会生效。
在我们实际测试过程中,某些国产手机对PackageInstaller有改动,导致无法正常安装Split APKs。
系统应用可以静默安装Split APKs,且当Split APKs安装完成后,可以决定是否“杀死“应用进程。
```
public static class SessionParams implements Parcelable {
/** {@hide} */
@SystemApi
public void setDontKillApp(boolean dontKillApp) {
if (dontKillApp) {
installFlags |= PackageManager.INSTALL_DONT_KILL_APP;
} else {
installFlags &= ~PackageManager.INSTALL_DONT_KILL_APP;
}
}
...
...
}
```
SessionParams是PackageInstaller内部类,setDontKillApp可决定当APK安装完成后是否杀死应用进程。setDontKillApp属于系统Api,因此第三方应用无法调用。
### Split APKs加载原理
![](https://img.kancloud.cn/3f/e5/3fe50bd73d204873270c613db51e69b2_1200x705.png)
通过Android 9.0 LoadedAPK源码片段,我们一起了解下Split APKs加载过程。
是的,系统级的代码本身就支持了Split Apk的加载
#### ClassLoader
通过createOrUpdateClassLoaderLocked方法名,可以知道该方法是用于创建和更新ClassLoader
![](https://img.kancloud.cn/87/23/8723ba9a1a73db1b8d0291e14644f958_1200x808.png)
该方法有两个核心步骤。
如果mClassLoader为空,则创建PathClassLoader实例。
如果addedPaths不为空,则更新PathClassLoader实例
该方法指明,应用进程是可以动态加载Split APKs代码
#### Resources
通过getResources方法代码片段,可知Split APKs的资源路径作为mResources创建参数
![](https://img.kancloud.cn/6a/9a/6a9a07ea8b18a4ff4ccb56c363b97de1_1200x694.png)
#### 不支持四大组件的新增
Android App Bundle在Manifest文件合并过程中,会将split APKs manifest文件内容合并至base APK中。因此,所有split APKs四大组件信息都是已经声明在base APK中。
Android App Bundle这种处理方式不支持Manifest更新,例如新增四大组件
#### 多进程问题
Android App Bundle所支持的功能特性有部分局限性
多进程问题
依据Qigsaw安装、加载split APKs原则,当游戏APK安装完成后,就会在主进程完成加载。在游戏APK中有两个Activity,他们所处进程不同。当启动GameActivity01时,页面正常启动。但当启动GameActivity02,您的App会出现崩溃。原因是GameActivity02运行在:game进程,游戏APK仅在主进程加载,并未在:game进程加载,因此系统会抛出ClassNotFoundException异常。
为解决这类问题,Qigsaw提供了如下解决方案。
在进程启动之初即Applicatin#attachBaseContext调用时,加载所有已安装splits。
第一种方案解决的场景是:game进程首次启动,即启动GameActivity02之前:game进程从未启动过。
Hook PathClassLoader。
第二种方案解决的场景是:game进程已经启动并正在运行
Hook PathClassLoader具体做了如下事情。
当出现ClassNotFoundException时,判断该类是否为splits四大组件。
当异常类为splits四大组件时,加载所有已安装未加载split APKs。
如加载完所有已安装未加载split APKs后依然出现ClassNotFoundException异常,则返回空四大组件类,防止进程崩溃。
# 爱奇艺动态化框架Qigsaw
## 背景知识
在2018年上半年,我们就进行动态组件化方案的调研。起初方案是基于Instant App方案实现,当整体功能基本实现后,Google于2018年Google IO大会上推出Android App Bundle。在调研Android App Bundle之后,我们发现Android App Bundle完全符合最初的需求。
依据我们最初设计初衷和Android App Bundle特点,总结出Qigsaw应满足以下核心特点。
利用Android App Bundle开发套件,体验原生极速开发体验。
少量私有Api访问,保证框架稳定性。
如果您的应用有出海需求,可无缝切换至Android App Bundle方案。
关于私有Api访问应该是大家比较关心的,最近一段时间某大厂开源了号称零反射插件化框架,但是通过阅读其源码,我们发现它还是做了PathClassLoader的parent ClassLoader反射替换。
另外它也调用了Resources构造方法创建Resources实例,虽然这样做并没有任何私有Api访问,但是通过查看Resources构造方法源码,我们可知该方法属于过时方法,且注释写明第三方应用不应该创建Resources实例。
所以插件化框架不应该仅仅以是否零反射为目标,我们应该从开发流程及产品形态选取合适方案,助力开发效率。
## 比googlePlay更好的打包体验
在发布阶段,Qigsaw提供打包插件让开发者享受一条龙服务,开发者不必关心dynamic feature的上传分发。
Qigsaw打包插件支持内置dynamic feature,所有内置dynamic feature都会被拷贝至base apk的assets目录。对于非内置dynamic feature,Qigsaw打包插件会将其上传至CDN服务器,解决业务方后顾之忧。
## Qigsaw原理还是基于插件化
Qigsaw借助Android App Bundle开发套件完成dynamic feature的打包,大大降低Qigsaw开发维护成本。因此Qigsaw关心的重点落在 如果安装加载dynamic feature生成apk上。
第三方应用利用PackageInstaller安装split APKs体验极其不友好,且某些国产手机对split APKs功能支持不完善,所以我们最终还是按照一般插件化方式安装加载split APKs。
![](https://img.kancloud.cn/2b/f7/2bf75d76d4903021c2d94389483526af_1200x717.png)
依据上图,如果需要动态加载split APKs,需要解决代码、资源以及四大组件的加载
### 代码加载使用打补丁
针对splits代码加载,Qigsaw采用单类加载器方式,即base APK和split APKs采用同一ClassLoader加载。
![](https://img.kancloud.cn/78/bd/78bdb2adbee85d3001790c1f0c5ae9b9_1200x724.png)
在DexPathList中,为每个split创建对应的Element和NativeLibraryElement实例即可。关于单类加载器更多细节,本文不再赘述,相关原理已非常成熟。
### 资源加载基于打补丁的方式同时不会冲突
![](https://img.kancloud.cn/5d/b4/5db4d9d08ba781d7f00cbd7a8793401f_1200x667.png)
Splits资源加载相较于代码加载会复杂,因为不同系统版本或不同手机厂商都会存在一些兼容性问题。
资源id冲突问题
Android Gradle Plugin在资源打包时,会对res目录下资源文件分配一个唯一Id。
* Id前两位PP为Package Id,代表应用类型。是系统应用、第三方应用、Instant App或Dynamic Feature等。
* Id中间两位TT为Type,代表资源类型。是drawable、layout或string等。
* Id后四位EE为Entry,代表该资源顺序。
所有第三方应用base APK资源Package Id均为7F,Android App Bundle对splits资源打包时会基于7F依次递减分配Package Id。因此,即使我们将split APKs资源添加到当前应用Resources实例中,也不会出现资源冲突问题,splits访问base资源也更加方便<.font>
Instant Apps资源打包是基于7F依次递增。
**反射Resource对象**
![](https://img.kancloud.cn/21/f5/21f55cfef3db04348bd6cb25d9192eb1_1200x740.png)
Qigsaw提供loadResources方法加载split APKs资源。为避免开发者写大量模板代码,Qigsaw打包插件采用字节码操作方式自动写入该方法。
### 不支持四大组件的新增
Android App Bundle在Manifest文件合并过程中,会将split APKs manifest文件内容合并至base APK中。因此,所有split APKs四大组件信息都是已经声明在base APK中。
Android App Bundle这种处理方式不支持Manifest更新,例如新增四大组件,所以Qigsaw也不支持新增四大组件。在正常开发迭代过程中,动态新增splits四大组件需求极少,所以Qigsaw与Android App Bundle特性保持一致。
# 参考资料
[AAB 扶正!APK 再见!](https://juejin.cn/post/6984588418554527774)
[# Android App Bundle动态化方案](https://blog.csdn.net/fei20121106/article/details/108034651)
- Android
- 四大组件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介绍
- MessageQueue详细
- 启动流程
- 系统启动流程
- 应用启动流程
- Activity启动流程
- View
- view绘制
- view事件传递
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大数据
- Binder小结
- Android组件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 迁移与修复
- Sqlite内核
- Sqlite优化v2
- sqlite索引
- sqlite之wal
- sqlite之锁机制
- 网络
- 基础
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP进化图
- HTTP小结
- 实践
- 网络优化
- Json
- ProtoBuffer
- 断点续传
- 性能
- 卡顿
- 卡顿监控
- ANR
- ANR监控
- 内存
- 内存问题与优化
- 图片内存优化
- 线下内存监控
- 线上内存监控
- 启动优化
- 死锁监控
- 崩溃监控
- 包体积优化
- UI渲染优化
- UI常规优化
- I/O监控
- 电量监控
- 第三方框架
- 网络框架
- Volley
- Okhttp
- 网络框架n问
- OkHttp原理N问
- 设计模式
- EventBus
- Rxjava
- 图片
- ImageWoker
- Gilde的优化
- APT
- 依赖注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 协程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 运行期Java-hook技术
- 编译期hook
- ASM
- Transform增量编译
- 运行期Native-hook技术
- 热修复
- 插件化
- AAB
- Shadow
- 虚拟机
- 其他
- UI自动化
- JavaParser
- Android Line
- 编译
- 疑难杂症
- Android11滑动异常
- 方案
- 工业化
- 模块化
- 隐私合规
- 动态化
- 项目管理
- 业务启动优化
- 业务架构设计
- 性能优化case
- 性能优化-排查思路
- 性能优化-现有方案
- 登录
- 搜索
- C++
- NDK入门
- 跨平台
- H5
- Flutter
- Flutter 性能优化
- 数据跨平台