分享篇 - 基于 Android APP Bundle 开发的全新编译模式 (编译速度提升 70%)

Sachi ·
更新时间:2024-09-21
· 554 次阅读

目录

1. Wafers 项目背景

2. 效果展示

3. 实现方案

4. 改造期间遇到的问题

5. 如何接入使用

6. 总结

1. Wafers项目背景

Wafers [ˈweɪfəz] 威化饼。以甜品命名更贴近生活,贴近 Google 的命名风格。同时威化饼象征着一种分层设计,可拆装的动态化设计理念。

随着 58 App 融合的功能越来越多,包体积越来越大,带来如下问题:

开发期间的编译速度比较慢; 厂商限制内置包的大小,厂商基础包的改造成本越来越大; 推广转换率下降。

Wafers 项目正是在这个大背景下产生的,整个项目分为 2 个里程碑:

里程碑 

 进度

 基于 Android App Bundle (AAB) 改造所有业务线 lib 库,提升开发期的编译速度

 已完成

 基于一期改造成果,实现业务模块动态下载运行

 进行中

目前一期已顺利完成,产出了一种全新的开发模式。这种开发模式是业内首创,除了大幅提升了开发期的编译速度外,还具有以下优点:

无侵入性。通过开发期增加工程壳,使用自研插件修改字节码适配 AAB; 改造成本低。只在开发期间生效,不影响线上包。同时保留原有的开发模式,支持两种开发模式的自由切换; 迎接大势。目前 Google Play 已全面支持 AAB 格式,国内市场未来两年也将逐步迎上。

为此我们将其开源,贡献给更多的业务线,更多的团队,一起为一线的开发兄弟姐妹造福!

 

 

2. 效果展示

目前 Wafers 已经应用在58同城 APP 中,我们将主业务,房产,黄页,拼车,招聘,二手,金融这几个业务线改造成 AAB 开发模式。同时,我们保留了之前的开发模式,支持一键切换。

首次编译:第一次编译,AAB 模式下会构建 base APK 和 dynamic feature APKs。每次编译前执行 gradle clean task,并且释放 Android Studio 内存。 增量编译:对开发的 lib 改动一行代码,AAB 模式下只会构建当前业务线的 dynamic feature APK,base APK 无需重新构建。

2.1 整体报告

首次编译的速度根据依赖方式的不同,分别有 32% 和 33% 的提升; 增量编译的速度根据依赖方式的不同,分别有 67% 和 70% 的提升; 库越小速度提升越明显,因为在 debug 开发期间,会向 AAB 工程壳注入插件,修改字节码,以达到无侵入的 AAB 开发模式切换。所以库越小,修改的时间越短; 所有业务线都打开 AAB 方式比只打开开发的业务线 AAB 方式更快一些,根据官方文档说明:AAB 采用了并行构建,以前的模式是串行构建一个大的 APK,现在的模式是并行构建多个 APK。

2.2 详细报告

OS     内存:Macbook pro 16 GB 1600 MHz DDR3   处理器:2.2 GHz Intel Core i7 IDE     Android studio 3.5 取样   每个场景执行 3 次,取平均值

开发主业务

开发方式 

首次编译 

 增量编译

 对比全 library 依赖提速

 全 library 依赖

 3m 20s

 2m 45s

 -

 主业务 AAB 打开,其他业务线 AAB 关闭

 2m 27s

 1m 8s

 首次编译 26.5%, 增量编译 60%

 主业务 AAB 与其他业务线 AAB 都打开

 2m 23s

 1m 3s 

 首次编译 28.5%, 增量编译 62%

开发房产业务

开发方式 

首次编译 

 增量编译

 对比全 library 依赖提速

 全 library 依赖

 3m 20s

 2m 45s

 -

 房产业务 AAB 打开,其他业务线 AAB 关闭

 2m 16s

 1m 3s

 首次编译 32%, 增量编译 62%

 房产业务 AAB 与其他业务线 AAB 都打开

 2m 14s

 1m 6s 

 首次编译 33%, 增量编译 61%

开发黄页业务

开发方式 

首次编译 

 增量编译

 对比全 library 依赖提速

 全 library 依赖

 3m 20s

 2m 45s

 -

 黄页业务 AAB 打开,其他业务线 AAB 关闭

 2m 9s

 43s

 首次编译 35%, 增量编译 73%

 黄页业务 AAB 与其他业务线 AAB 都打开

 1m 48s

 42s 

 首次编译 46%, 增量编译 73.5%

开发拼车业务

开发方式 

首次编译 

 增量编译

 对比全 library 依赖提速

 全 library 依赖

 3m 20s

 2m 45s

 -

 拼车业务 AAB 打开,其他业务线 AAB 关闭

 2m 15s

 48s

 首次编译 32.5%, 增量编译 70%

 拼车业务 AAB 与其他业务线 AAB 都打开

 2m 20s

 43s 

 首次编译 30%, 增量编译 73%

开发招聘业务

开发方式 

首次编译 

 增量编译

 对比全 library 依赖提速

 全 library 依赖

 3m 20s

 2m 45s

 -

 招聘业务 AAB 打开,其他业务线 AAB 关闭

 2m 14s

 59s

 首次编译 33%, 增量编译 64%

 招聘业务 AAB 与其他业务线 AAB 都打开

 2m 20s

 47s 

 首次编译 30%, 增量编译 71%

开发二手业务

开发方式 

首次编译 

 增量编译

 对比全 library 依赖提速

 全 library 依赖

 3m 20s

 2m 45s

 -

 二手业务 AAB 打开,其他业务线 AAB 关闭

 2m 15s

 46s

 首次编译 32.5%, 增量编译 72%

 二手业务 AAB 与其他业务线 AAB 都打开

 2m 25s

 49s 

 首次编译 27.5%, 增量编译 70%

开发金融业务

开发方式 

首次编译 

 增量编译

 对比全 library 依赖提速

 全 library 依赖

 3m 20s

 2m 45s

 -

 金融业务 AAB 打开,其他业务线 AAB 关闭

 2m 14s

 48s

 首次编译 33%, 增量编译 70%

 金融业务 AAB 与其他业务线 AAB 都打开

 2m 10s

 42s 

 首次编译 35%, 增量编译 74.5%

3. 实现方案

3.1 AAB 简介

Android App Bundle 是 Android 新推出的一种官方发布格式 (.aab)。通过使用 Android App Bundle 你可以减少应用的包大小,从而提升安装成功率并减少卸载量。

Google Play 就是基于对 aab 文件处理,将 App Bundle 在资源维度,ABI 维度和 Language 等维度进行了拆分。你只要按需组装你的 APK 然后安装即可。如果你的手机是一个 x86,xhdpi 的手机,你在 Google Play 应用市场下载 APK 时,Google Play 会获取手机的信息,然后根据 App Bundle 会帮你拼装好一个 APK,这个 APK 的资源只有 xhdpi 的,so 库只有 x86,其他无关的都会剔除,从而减少了 APK 的大小。

 

3.2 我们的奇思妙想

看完 AAB 的简介,丝毫没有看到提升编译速度的点,不过 Google 在 AAB 的特点中提到了一点:

缩短构建时间:构建系统(例如使用 Gradle 的 Android Studio 构建系统)针对组织为模块的项目进行了优化。 例如,如果您在具有多核处理器的工作站上启用 Gradle 的并行项目执行优化,则构建系统能够并行构建多个模块,并显着减少构建时间。项目的模块化程度越高,构建性能的提高就越显着。

这一点从上面的数据报告中也可以得到结论,我们将所有业务线都改成了 AAB 模式后,即使是全量构建(所有的 APK 都需要重新构建一遍),编译速度也提升了 30%,这就是得益于 AAB 支持的并行构建特性。但是这一点,还不足以令我们兴奋。AAB 可以构建出一个 base APK 和多个 dynamic feature APK,最后使用 adb install-multiple 命令安装到手机上。

这一点让我们想到了:

58 APP 业务线众多,每个业务线在开发时,大部分需求都是在自己的业务线代码中进行开发。如果我们将所有业务线都改造成AAB 模式,除了可以利用并行构建的优势,还可以在业务线开发时,只构建它自身的 APK。base APK 和其他业务线的 APK 在全量构建后,如果没有修改,后续本地开发无需重新构建。最后直接使用 adb install-multiple 命令一起安装即可,这将大幅减少开发期的编译速度。

 

3.3 设计方案

有了这个振奋人心的发现,我们小组几个人立马开始设计改造方案,58 APP 的原有的架构图如下:

可以看到,工程结构非常复杂,为此我们定下了几个目标:

降低改造影响范围,不能影响全量正式包; 降低改造成本,实现无侵入地改造; 提高体验,保留之前旧的开发模式,支持快速切换。

经过各种测试,实验,我们最终落地的工程结构图如下:

我们在本地调试期间打开 AAB 开关,对需要被改造的 lib 库加上一个 dynamic feature 工程壳,这层壳用来打出业务 APK,内部的依赖是这个待改造的业务库 aar/源码。这样业务线在没有某个 dynamic feature 库的代码时,也可以通过它们的工程壳打出对应的 APK 文件。

 

3.4 打包流程

本地调试期间,当 dynamic feature 工程壳编译时会注入自研插件,修改字节码文件,实现无侵入地修改。而 release 全量包则会关闭 AAB 开关,工程结构会和改造之前一模一样,从而不影响全量正式包。

Debug 打包:

Release 打包:

 

 

4. 改造期间遇到的问题

虽然最终的方案很完美,但是在改造期间还是遇到了很多问题,这边简单地描述一下:

 

4.1 关于 AAB 的拆分规则

官方并没有给出 dynamic feature 的拆分规则,为此我们做了一个测试,有如下几个场景:

场景1:

lib1 是否会添加到 dynamic feature APK A 中? 

答案:lib1 会添加到 dynamic feature apk 中。

 

场景2:

构建出 dynamic feature APK A 与 dynamic feature APK B 的时候 lib1 的代码会在哪一个库中呢?都存在? 还是在 base APK 中?

答案:Dynamic feature APK 不能打包相同的 library 依赖,需要将改 library 放到 base APK 中。

 

场景3:

base APK 中包含 app lib1 与 common 库,dynamic feature APK 只包含自己的库的代码和资源。

规则总结:

与 app 库有直接或间接依赖的都会打包到 base APK 中; Dynamic feature moudle 直接或间接依赖的库而不被 app 依赖的库会打到 dynamic feature APK 中; Dynamic feature moudle 间不能同时依赖相同的库,需要将其放到 base APK 中; 只有 dynamic feature moudle 可以依赖 app 库。

以上几点结论,为我们开发设计 Wafers 一期,奠定了重要的基础。

 

4.2 Dynamic feature R 文件规则

我们开发的插件,解决的主要问题就是 R 文件。之前旧的开发模式,最终所有的库会打包到一个 APK 文件中,所以很多资源访问的问题不会存在,或者说暴露出来,包括开发者引用错误的 ID。

首先我们调研了下 dynamic feature R 文件的引用规则:

Dynamic feature moudle 如果引用 base moudle 的 R 文件会自动替换为数值; 不是 dynamic feature 而被打入 dynamic feature apk 中的库,依然是 pkgName.R.id 的形式存在,不会被替换成数值。

改造过程中遇到了如下几个 R 相关的问题:

(1) Dynamic feature APK 中引用 base APK 的资源

解决方案:增加一个 dependences 中间库用作 R 资源收敛,使用插件排查注入包名前缀。使用 Wafers 开发模式,都需要新增一个用作 R 资源收拢的中间库。

(2) Dynamic feature APK 中反射获取资源

解决方案:插件中拦截反射资源函数,使用代理 Resources 方式替换:优先从自身库查找 (pkg 需要加上 .featureName),找不到再从 base APK 中查找。

(3) Dynamic feature APK 中和 base APK 中有同名 id,但编码时错误引用了 base APK 中的 id 值 

解决方案:插件拦截 findViewById 函数,替换成自定义实现的 findViewById:针对 Activity、View、Dialog、Widow 分别从当前库,base APK 查找,最后使用反射方式处理。

(4) Base APK 中使用了 id 查找,但其引用的 layout 被 dynamic feature 替换了

解决方案:通过插件修改 base APK,加白名单列表,修改指定的类。

 

4.3 国产 VIVO, OPPO 不支持 adb install-multiple

adb install-multiple 在国产的 VIVO, OPPO 手机上会安装失败,接入方可以参考 Demo 关闭所有 AAB 开关,在 Wafers 二期我们将把 dynamic feature APK 内置到 assets 解决此问题。

 

  5.如何接入使用

Wafers 目前仅在 58 内部进行了开源,后期会逐步向外界开放。58 的同学如果有兴趣对自身的项目做改造,可以在公司 gitlab 仓库中搜索 Wafers, 里面有详细的接入文档,使用文档和 demo。在改造期间遇到问题,可以在 Wafers gitlab 的 issues 反馈问题,我们会及时进行跟进处理, 同时也欢迎提交 MergeRequest,为Wafers 贡献一份力量。

    6.总结

Wafers 是种全新的开发模式,在大型,复杂,多模块的项目中,可以有很大的发挥空间。

同时改造成本较低,只要遵循 Google 的 AAB 文档进行改造,再应用 Wafers 的插件,即可实现少量代码快速无侵入的模式切换。但是 AAB 本身有一些限制,如 dynamic feature 库之间不能互相依赖,dynamic feature 的 manifest 中使用的资源需要提前放在 base apk 中 (因为会提前进行 manifest 合并) 等,所以接入改造的项目可以根据自身业务的复杂度选择性地修改某些模块。每个开发模式都有它需要遵循的规则,AAB 是 Google 在模块化,动态化领域更深入,更彻底的一个产物。也是大势所趋,所以朝前走肯定需要顺应一些规则,正如当年的我们从 Eclipse 切换到 Android Studio,从 Ant 切换到 Gradle,从 Java 切换到 Kotlin。

Wafers 2 期,我们将实现动态包的产出,如各种厂商包,业务推广包,这将大大减少包大小。同时可以脱离Google Play 的束缚,在国内的应用市场实现 AAB 模式的动态包下载和装载,也为迎接 AAB 模式在国内应用市场的普及做好准备。

 

参考文献

https://developer.android.com/platform/technology/app-bundle

https://zhuanlan.zhihu.com/p/86995941

 

况众文 原创文章 130获赞 227访问量 11万+ 关注 私信 展开阅读全文
作者:况众文



bundle app Android

需要 登录 后方可回复, 如果你还没有账号请 注册新账号