💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[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)