前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android 大型工程 App Bundle 模块化实践

Android 大型工程 App Bundle 模块化实践

作者头像
微信终端开发团队
发布2021-05-26 11:41:20
4.4K0
发布2021-05-26 11:41:20
举报

导言 Android App Bundle 是 Android 新推出的一种官方发布格式,可让您以更高效的方式开发和发布应用。企业微信基于 App Bundle 采用低入侵、业务代码基本零重构的技术方案,实现了全业务模块采用动态模块(dynamic feature)开发。最后展示并行编译方案,进一步加速持续集成。

一、项目背景

Android App Bundle

是 Android 新推出的一种官方发布格式,可让您以更高效的方式开发和发布应用。和国内开发者已经熟知的 Kotlin 开发语言、Android Studio IDE 工具、Android JetPack API 最佳实践一起,组成了现代 Modern Android Development,值得我们研究和结合项目实践。

它的核心是 Google Play 应用分发渠道和 Android Split APKs 运行时分包加载机制,以更小的应用提供优质的使用体验,从而提升安装成功率并减少卸载量。发布包从 .apk 转换为 .aab 过程轻松便捷,无需重构代码即可开始获享较小应用的优势。

Android-App-Bundle-Delivery

从 2021 年下半年开始,Google 要求新应用需要使用 Android App Bundle 才能在 Google Play 中发布。大小超过 150 MB 的新应用必须使用 Play Feature Delivery 或 Play Asset Delivery。可查看官方文档[1]了解更多。

企业微信

经过5年的高速发展,已经成为远程办公不可或缺的通信工具,目前服务超过550万企业客户,并与超过4亿微信用户连接。企业微信存在多行业(教育版、金融版、医疗版、零售版、政务版等等)、多角色(管理员、服务商、开发者、员工)、多身份(企业、团队)等大量复杂因素,形成了一套超复杂的大型软件系统。随着版本快速迭代,Android 客户端迅速膨胀为超大型 App。

我们也遇到了超大型 App 通常会存在的问题:

  • 业务模块多,代码、资源隔离度低,依赖关系复杂
  • 编译效率低
  • 包体积大,国内外应用商店渠道包代码分化
  • 历史代码量大,难于重构
  • 代码工程结构不适应人员组织结构发展

企业微信采用多种技术结合的模块化开发,逐步解决这些问题。在 2019 年,我们调研和使用 Android App Bundle 解决 Google Play 渠道包 64bit 版本发布问题后,又对模块化开发流程做了进一步改进。

企业微信模块化开发演进

先给出动态的演进过程,让大家有一个宏观的概念,再对不同阶段的技术方案做一些概述。

企业微信模块化开发演进

阶段一:基础库模块复用 Gradle 构建工具引入,改变了模块的组织形式,从包依赖管理变成模块依赖管理,从单 Project 结构变成多 Project 结构。实现了库模块代码边界隔离。

阶段二:模块分层重构 强调模块化开发职责,定义出 app / module / api / library 分层依赖结构,通过 api 通信和控制反转,将 app 拆小为业务 module,app 改为壳工程用于集成。我们参考了《微信 Android 模块化架构重构实践》 经验,实现了业务代码边界隔离。

阶段三:模块分组重构 Android App Bundle 和动态模块 feature 引入,改变了发布形式,从单体式应用 app.apk 变为 base.apk + split.apk 分包式应用。更小的初始化安装包,更严格的依赖边界、代码边界、资源边界隔离,更灵活的部署方案。

模块化开发解决什么?

Android App Bundle 描述非常恰当:

提升工程速度 将应用功能作为独立模块进行设计、构建、调试和测试,并在准备就绪后将其添加到主应用中。您不再需要一整支工程团队来处理具有大量复杂代码的同一个单体式应用,因而可以减少合并冲突和中断。 缩短编译时间 使用 Gradle 的 Android Studio 编译系统针对模块化应用进行了优化,因此编译速度比较大的单体式应用要快得多。

相似的两个描述,都是加快速度、减少时间,含义却不一样。

提升工程速度

在旧的模块化开发中,工程类型只有应用(application)和库模块(library)2种类型,在新的模块化开发中,增加了第3种动态模块(dynamic feature)类型。动态模块依赖基础模块(base),能独立、更快地研发:

speed-up-engineering-velocity

动态模块有2个难以平衡的问题:

  1. base 工程如果很大,那么依赖项构建就已经很耗时了。
  2. 如果尽量地将 base 工程代码迁移到各业务的动态模块,使得 base 工程很小,那么使用到其他业务功能,都需要提供测试代码进行 mock。也就是说缺少独立的集成测试环境。

要充分发挥动态模块独立、快速的优势,这要求企业微信模块化实现:

  1. 编译时插件化 把 base 拆分成最小工程,并预分配动态模块业务名、资源分区号段。
  2. 集成测试环境 提供 .apk 或 .aab 的其他业务编译缓存。
缩短编译时间

Gradle 编译系统在效率上的提升,主要体现在3个方面:

  • 并行编译 在编译任务关系依赖图中,同类多工程更易构造并行编译关系,充分利用多线程编译优势。
  • 增量编译 单个编译任务和它采用的工具,内部控制输入、输出的增量关系和产物。
  • 编译缓存 如官方的 build-cache,或国内模块化惯用的 .aar,或插件化使用的 .apk 等,都是通过配置版本管理、跳过编译任务。区别在缓存粒度和命中率不同。

编译加速在实践中,通常是这2类思路:

  • 增量编译改进 减少改动的代码、资源,提高本地缓存命中率。代表如 Google 官方的 Instant Run、阿里开源的 freeline 和腾讯内部在用的 Lightning 等,它们对开发者透明、对编译入侵性比较大。
  • 工程结构改进 减少参与的代码、资源,提高服务器缓存命中率。代表如微信模块化、淘宝插件化等,企业微信也选择了适合自己的模块化方案。人力投入、老代码重构都是它的痛点。

在模块化后,会带来并行和缓存效率的提升:

faster-build-times

但是根据开发的朴素经验,并行会产生额外调度开销、并发甚至死锁问题,而缓存有命中率和时效性问题。超过阈值后,效率反而会降低。这在模块化开发中同样适用:缓存大量不命中时,并行数剧增、大量消耗 CPU 和内存资源,当资源耗尽时性能急剧降低。

多工程改造为支持动态模块,分包式多 .apk 更能充分发挥并行、缓存的优势,但这更要求企业微信模块化解决计算资源消耗的问题:

  1. 计算迁移 增加可计算资源,开发机、构建机分布式编译,充分利用构建机群。开发机只关注自己的业务模块。
  2. 减少依赖数 提高缓存命中率,只编译自己的业务模块。

为什么启动阶段三重构?

团队规模处在不同时期,采用不同版本周期和迭代方式会对模块化效用产生比较大影响。我们对比这2种迭代模式:

  • 主干开发模式 所有开发者都在版本主干上开发。因为没有长期分离的功能开发分支,任何代码变更持续地更新到主干上,在一定程度上避免了 merge 代码带来的困扰。而每次代码提交都会触发集成验证,这就要求每次代码的变更在主干上都能快速地验证。
  • Git-Flow 模式 解决多个不同功能之间并行开发需要的一种工作方式,开发人员、开发任务对于主线是隔离的。任何版本迭代、功能需求总是从基线拉出新分支开发,完成后回流。功能分支的生命周期过长会不断加剧合并冲突。

上述2个模式,可明显发现主干开发模式有利于 merge、提升工程速度,Git-Flow 模式有利于缓存、缩短编译时间。

企业微信迭代周期快,采用的是主干开发模式。由于缺少 Git-Flow 的隔离,并行开发会导致:

  • 单个编译错误也可能造成集成失败,影响全员开发、测试
  • 模块并发修改、缓存大量失效,在拉取代码更新后造成效率急剧下降

阶段三重构目的就是,通过增强模块隔离性、分包动态加载,从模块化设计改进和实现支持,提升并行、缓存效率,降低模块依赖数、减少并发修改影响。

二、思路和挑战

  • 全部业务模块改造为支持动态模块,把 base 拆分成最小工程,并预分配动态模块业务名、资源分区号段:
  • 减少依赖数,提高缓存命中率,只编译自己的业务模块。构建机快速提供其他业务编译缓存:

Android App Bundle 具有无需重构代码、转换过程轻松便捷的优点,因此要求我们在实现转换原模块化开发模式过程中,同样也要保持这样的优点:

  • 低入侵、业务代码基本零重构 不变更模块间编译依赖关系,基本不重构代码类或资源 R 旧的引用方式,少量 .gradle 和 AndroidManifest 配置修改。
  • 低成本切换 保留原有的开发模式,支持 .aab 和 .apk 两种发布模式的自由切换。只在开发期间生效,不影响发布。

低入侵、业务代码基本零重构

编译关键任务分析

先分析 Android App Bundle 相对于 APK 编译,在开发阶段的最重要区别:

编译关键任务

依赖处理

  • 1.统计 base 依赖 所有 base 的依赖,都会被打包进 base.apk,如果 feature 依赖了同样的库,在打包时会被忽略。
  • 2.统计 feature 依赖 统计出各个 feature 的依赖,都会被打包进 feature.apk
  • 3.检查依赖是否合法 必须满足所有依赖打包唯一性,如果某个库同时被多个 feature 依赖,则报错。大量动态模块在这里产生依赖冲突。

资源编译

  • 4.收集 feature 依赖资源 包括依赖库模块中的 AndroidManifest 配置文件。
  • 5.收集 base 依赖资源 包括基础依赖库模块中的 AndroidManifest 配置文件。
  • 6.合并 AndroidManifest 在 base 中合并处理 AndroidManifest 配置文件,包括 base、所有 feature 中注册的全部组件、配置等。
  • 7.base 资源编译 编译 base 的资源,生成基础资源包 .ap_ 提供给 feature 作为外部资源包。如果 feature 的 AndroidManifest 里有对于本模块私有资源的访问,由于合并后的 AndroidManifest 位于 base 模块,又引用了 feature 的资源,于是合并出错。

代码编译

  • 8.base 代码编译 生成代码包 .jar 提供给 base 作为基础依赖,其中包含资源 R 引用
  • 9.feature 代码编译 由于和 base 分包后,包名变化导致大量的原资源 R 引用编译出错。

模块依赖冲突

Android App Bundle 会在 base 检查依赖打包是否冲突:

通过编译时预检查,避免了运行时加载重复 .dex,确保逻辑一致性。

Gradle 通过依赖配置 Configuration [2]管理一组类型的依赖关系,比如开发者常见的,阻断向上传递依赖的 implementation、只编译不打包的 compileOnly 等:

代码语言:javascript
复制
dependencies {    implementation(Deps.Lib.kotlin_stdlib_jdk8)    compileOnly(Deps.Mockito.wechat_media_codec)}

它们可通过组合方式进行多继承,分组和复用依赖配置资源:

代码语言:javascript
复制
configurations {    create("modularImplementation").apply {        getByName("implementation")?.extendsFrom(this)    }}
fun DependencyHandler.modularImplementation(dependencyNotation: Any): Dependency? = add("modularImplementation", dependencyNotation)

比如在 Android Gradle Plugin 中的 Release 版本变种配置,可明显观察区分了编译时和运行时分类:

Android App Bundle 收集和检查依赖冲突的就是 runtimeClasspath,可参看 FeatureSplitTransitiveDepsWriterTask.kt 代码片段:

代码语言:javascript
复制
override fun configure(task: FeatureSplitTransitiveDepsWriterTask) {    ...    task.runtimeJars = variantScope.getArtifactCollection(            AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH,    ...

compileClasspath 会决定代码编译是否通过,而 runtimeClasspath 会决定是否加入打包。我们通过在 gradle 全局的依赖管理里,按业务规则集中过滤处理依赖冲突:

代码语言:javascript
复制
subprojects {    configurations.all {        resolutionStrategy.eachDependency {            if(Env.isBuildBundle){                skipDependencyIfNeeded(...) // useTarget(Skipped)                                            // because(reason)            }        ...

原项目工程结构和依赖配置无需修改,对业务开发和其他插件透明,达到我们解决依赖的目的,同时在 gradle scan 里可以查看到裁减依赖的原因,方便 debug:

AndroidManifest 合并错误

Android App Bundle 在打包的时候会把 feature AndroidManifest.xml 文件合并到 base,但是却不会把 AndroidManifest.xml 依赖的资源一起合并到 base。这样就会导致编译时出现 base AndroidManifest.xml 依赖的 feature 资源找不到的错误:

经过对系统包管理相关代码的分析,其实 Android App Bundle 运行时 feature 的组件配置也是会生效的,并且优先级要高于 base 中的 AndroidManifest.xml 配置。只有 application 标签的属性和 application 标签外面的配置是以 base 中的配置生效。

  • 源码:android.content.pm.PackageParser#parsePackage[3] 会分别处理 mono .apk 单包文件路径或 split .apks 多包目录路径
  • 源码:android.content.pm.PackageParser#parseSplitApplication[4] 替换 base 的四大组件

具体的生效情况如下图:

也就是说,只要 feature 中有组件的清单配置,base 中有无组件的清单配置并不会影响apk的运行效果。而 application 和 uses-permission 的配置比较固定、修改本来就很少,可以把 application 和 uses-permission 的配置复制到 base 的 AndroidManifest.xml,这样就能够解决问题了。采用复制另一个重要原因是 feature 可能由于命中缓存、不会转换为 Project 参与编译,所以没采用编译任务来合并。

base manifest 要合并的 feature manifest 信息保存在 processDebugManifest 任务的 featureManifests 中,可参看 ProcessApplicationManifest.java 代码片段:

代码语言:javascript
复制
task.featureManifests =        variantScope.getArtifactCollection(                METADATA_VALUES, MODULE, METADATA_FEATURE_MANIFEST);			...if (featureManifests != null) {    providers.addAll(            computeProviders(                    featureManifests.getArtifacts(),                    InternalArtifactType.METADATA_FEATURE_MANIFEST));}

我们只需要调用 featureManifests.getArtifacts().clear() 把其要合并的信息清空即可阻断合并 feature 的 AndroidManifest.xml。

资源编译错误

Android 的资源编译会经历资源收集、分配资源id、编译链接几个重要流程:

资源编译流程

资源编译错误主要集中编译链接 (Link) 过程:

造成链接失败原因是:

  1. 缺少资源编译隔离 从编译流程可以看到,单体式 apk 会收集所有的资源文件添加到 ResourceTable 中。影响后果是,不同模块在 xml 里可任意引用其他资源,即使不存在依赖关系。打包发布库模块有提供 verifyReleaseResources 任务做轻量的链接探测,预防运行时因为缺少引用的资源导致异常。遗憾的是,在大型 app 重构过程中可能会关掉它以加速进度,遗留部分未彻底解耦的资源引用,它们可能在这时报错。
  2. 动态模块分区隔离 会添加 android 和 base 资源包作为外部引用,所以在 base 中的公共资源仍可继续使用。但是引用了其他动态模块的资源就会出错,这是我们期望出现的。它不但提供了类似 verifyReleaseResources 的检查能力,而且保持了高效编译速度,在日常重构和开发中防微杜渐。

“不重构代码或资源”在这里失效,所以我们是“基本不重构”。不过这里的重构是正向和有益的,我们提供了快速处理的一般方法:

1.通过 mock 各种类型资源、快速重构为新模块化开发,并统计资源被引用范围

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?><resources>
    <item name="title" type="id"/>    <item name="video_clip_slider_selected" type="raw"/>    <attr format="reference|color" name="textColor"/>    <item name="back_icon_normal" type="drawable"/>    <item name="has_send" type="string"/>
</resources>

2.高频引用的公共资源,按照官方推荐下沉到 base 使用

3.低频引用的业务资源,按照业务归属重构、解耦

代码编译错误

而代码和资源密切相关的文件就是 R.java,它在每个模块中广泛引用,通常是 <模块包名>.R.tt.nnnn 格式。

在处理完 Android App Bundle 依赖和资源编译改造后,由于模块包名发生了变化,代码编译会有大片大片对 R.java 引用报错:

R.java 演进历史

为了使读者对问题认识有个延续性,我们先概要介绍下 R.java 在 Android 开发中的发展历史。

R.java 发展历史

工程规模扩大

编译工具成熟、Google 对开发生态控制力增强,促进应用生产方式转变和更易扩大规模。

  • ADT + Eclipse 时代 更多以包进行组织,主要解决 SDK 和 App 的 ID 冲突问题。在代码中使用资源,是需要声明全包名引用的。这带来的问题是,开发过程中你需要判断资源来源。
  • Gradle AGP + Android Studio 时代 以工程进行组织,大量模块化开发方案涌现。由于 id 是自动随机分配的、对开发者无意义,AGP 通过 generated/not_namespaced_r_class_sources/../R.java 简化了开发认知过程,你不必在意资源来源,只要依赖存在就能在代码中通过 R 引用它。

not_namespaced_r_ 实现思路有2种:组合或继承。但模块可以有多个依赖:

而 Java 不支持多继承:

Java 多继承语法错误

R.java 最终方案采用了组合,final 常量还可以内联优化运行时性能。但递归的方式引起了代码行数剧增,编译性能骤降。

编译性能优化

  • 方向一:阻断递归,代码生成更小的 R.java 逆时代发展,开发者已经习惯了不引用全包名。不过,它也有防止扩散引用 R 的好处。
  • 方向二:直接生成字节码,更快的 R.jar 大获成功,后续还进一步减少冗余的 R.class

动态模块编译的 R.java 和这2个优化方向各有相似之处:

R.java 编译方案

文件数

字段重复

隔离

全包名引用

方向一:阻断递归

最少

不重复

隔离

方向二:字节码

重复

不隔离

动态模块

重复

平级隔离

部分

  • 平级隔离 feature 和 feature 之间,资源和 R 在编译时无法直接引用。
  • 部分引用 在 feature 中使用 base 资源,R 需要全包名引用。
编译时零重构 import R

回顾了 R.java 演进历史,我们想要集中优点、消除缺点:

  • 通过自己模块的 R 直接引用 现在开发的优点,不需要全包名才能引用其他模块资源,即便是 feature 引用 base。当下不用大面积重构动态模块中的现有代码,将来仍在开发过程保持对资源使用透明。
  • 隔离非依赖模块的 R 字段 使用动态模块开发的优点,降低扩散效应。解决现在开发资源改动时可能引起的大面积重编译。

因为每个 feature 仅依赖一个 base,以前不适用的继承方案,这时完美的适用了。 R 文件的产生都是在 processDebugResources,在任务结束后再做简单处理:

  1. 对于 base,将 R.string、R.id 等 final 类通过字符串替换为非 final 类
  2. 对于 feature,将所有not_namespaced_r_class_sources 下的 R.string、R.id 等类通过字符串替换增加 base 对应R文件的继承关系。

资源、代码对称覆写

  • 补全代码缺失字段 采用继承的方式后,feature 的 R 文件作为 base 的 R 文件的子类,子类可以直接访问父类 static 字段,因此在 feature 中引用 base 的资源无需添加 base 的包名就可直接访问。
  • 资源、代码对称覆写 同时资源字段覆盖关系对称,feature 自己的资源具有高优先级,feature 可在资源、代码里对称正确使用。

到此,我们完成了全部业务模块改造为动态模块所需的编译工作。依次解决了依赖冲突、AndroidManifest.xml 合并失败、资源、代码编译失败等问题。

运行时一致性 R.id

新的模块化开发在运行时还存在2类问题:

  • 资源 id 错乱 编译时所有资源的 id 都是随机分配的,以前使用 .aar 缓存总会再分配不会出现错乱,但使用 .apk 缓存就会出错了。
  • 交叉引用报 NPE 编译时不可访问的资源在运行时是可访问的,id 不一致导致错误。

资源 id 错乱容易理解,举例说明交叉引用报 NPE。在代码中使用 findViewById() 获取视图对象,假设 feature A,feature B 和 base 3个模块都各自在不同的 layout.xml 资源里定义了相同 @+id/title, 组成了这3类运行时调用关系:

  • 内部 代码和资源都在相同模块调用正常
  • 上下 feature / base 在运行时提供 layoutId 相互调用异常
  • 左右 A / B 在运行时提供 layoutId 相互调用异常

可能出现的结果:

findViewById

A 资源

B 资源

base 资源

A 代码

正常

NPE

NPE

B 代码

NPE

正常

NPE

base 代码

NPE

NPE

正常

这2类运行时问题我们通过替代 aapt2 解决:

  • 固定 base 资源 id 由于资源是分区的,动态模块不论是不是缓存都不会出现错乱,只有共享的 base 模块才会在不同次编译的缓存中错乱。官方 aapt2 link 已提供 --stable-ids[5] 支持参数。
  • 修改 aapt2 统一 R.id 仅针对 id、attr 这类不需要覆写关系的资源类型,在 aapt2 生成索引表和 R.java 时进行全局统一。id 类型值是随机的、无实体资源含义。attr 按设计规范和避免样式属性定义冲突,统一也是极有利的。

aapt2 透明替换

从 Android studio 3.2 开始,AAPT2 的来源为 google()[6]  Maven 库里的发布包:com.android.tools.build:aapt2

我们基于开源主干增加所需要的特性修改,重新发布替换掉 Maven 依赖即可:

代码语言:javascript
复制
subprojects {    configurations.all {        resolutionStrategy.eachDependency {            if(Env.isBuildBundle && requested.name == "aapt2"){               useTarget("com.tencent.wework.tools.build:aapt2:2.3.3.1-wecomponent-6051327")            }        ...

aapt2 修改兼容性

  • 开源库 aosp-android-9.0.0-r59-aapt2[7]

官方已经提供了一个较佳的 hack 点:

  • FeatureSplitSymbolTableDelegate[8]
代码语言:javascript
复制
// A custom delegate that generates compatible pre-O IDs for use with feature splits.// Feature splits use package IDs > 7f, which in Java (since Java doesn't have unsigned ints)// is interpreted as a negative number. Some verification was wrongly assuming negative values// were invalid.//// This delegate will attempt to masquerade any '@id/' references with ID 0xPPTTEEEE,// where PP > 7f, as 0x7fPPEEEE. Any potential overlapping is verified and an error occurs if such// an overlap exists.//// See b/37498913.class FeatureSplitSymbolTableDelegate : public DefaultSymbolTableDelegate

低成本切换

相对简单,篇幅有限不多说明。

  • 编译切换 动态模块在 AndroidManifest.xml 中新增的 dist:module 配置并不影响普通 apk 编译,在按编译环境使用不同插件、倒置依赖关系即可。base 需要特别判断 dynamicModules
  • 运行切换 在 Android Studio 中提供带 VCS 的运行配置,调用 Gradle 任务执行即可

三、全景图

研发模式

面向不同人群和场景,我们支持4种研发模式:

  • 国内 app bundle 应用市场生态还不成熟,缺少应用渠道、只能使用插件化框架来弥补。我们仍沿用稳定的 .apk 发布方式。
  • 企业微信内部开发,从去年应用 app bundle 来,已全面使用更高效的 .aab 开发方式。不同场景编译提速300%,除少数大型模块(如消息)外,增量编译稳定在30s内。
  • 国外 google play 市场,我们对小程序超大型 SDK 做了动态化处理,近期也会做渠道发布。
  • 合作团队有很多引擎开发或 SDK 开发,他们能更聚焦第三方库。利用 .aab 缓存和预分配 feature 构建,不必在企业微信工程编译后集成测试,仍保持了真实用户运行环境。

4种研发模式

研发流程

不同团队的发布形式和研发流程不同,这里仅重点描述下企业微信已使用成熟的 Debug 开发流程:

企业微信 Debug 开发流程

  • 缓存管理 不同类型(.aar / .apk / .zip / .aab / .txt)缓存数据都通过 maven 来管理,有利于借助 gradle 的依赖管理和本地缓存策略。
  • 元数据管理 为了加速 gradle 依赖查询,离线、增量版本控制,我们在主仓库外,建立元数据仓库、并映射 commit,高效管理不同类型(.version / .feature)版本数据。多仓库的方式还能保持 git commit 时间线干净。
  • 并行编译 编译逻辑相同,但部署类型有2种:本地和远程。它们是有区分的,远程可分布式、在不同构建机上编译,极大利用了机群的计算资源。

国内生态

Android App Bundle 技术生态在国内仍不完善,国内外对比:

生态位

国外

国内

问题

解决方案

开发体系

MAD 体系

自研体系

需要重构、改造不断适应官方技术栈

超大型项目一般都具备模块化开发,本文提出一种轻量级重构方案,在企业微信实践

应用渠道

Google Play Delivery

华为/应用宝等应用市场

开发者不能控制用户获取应用的方式,市场提供的系统安装支持不完整

自建发布系统 CDN 下发

运行环境

原生 ROM

厂商 ROM

系统缺少对 splitapk 分包运行的统一支持

插件化框架,如基于 App Bundle 的开源方案 iqiyi/Qigsaw

资源优化

split 配置

resguard

大型项目在使用 App Bundle 时重复资源才是重灾区

.aab 中间件提供了二次修改的可能,如基于 resgaurd 的开源方案 bytedance/AabResGuard

代码优化

d8 / r8

自研

大型项目 release 编译速度到混淆、dex merge 阶段就成瓶颈了

本文提出一种并行编译方案,为 proguard 配置预分配、进一步加速提供可能

四、写在最后

回顾超大型 App 通常会存在的问题,企业微信采用多种技术结合的模块化开发,逐步解决:

问题

解决方案

业务模块多,代码、资源隔离度低,依赖关系复杂

模块分层,梳理了职责和依赖关系;模块分组,强制不相关依赖、资源、类编译隔离

编译效率低

多种缓存(.aar / .apk)加速,本地、远程并行加速

包体积大,国内外应用商店渠道包代码分化

技术栈更新,低成本切换、使用基于 App Bundle 的 APK 合成方案

历史代码量大,难于重构

低入侵、业务代码基本零重构方案

代码工程结构不适应人员组织结构发展

动态模块独立开发,4种研发方式满足团队内外合作

团队介绍

harrisonwu(吴洪春) / 腾讯 Android 工程师

renpengtian(田仁鹏) / 腾讯 Android 高级工程师

waylonhuang(黄玮) / 腾讯 Android 高级工程师

tagorewang(王涛) / 腾讯 Android 高级工程师、企业微信 Android 模块化负责人

均来自企业微信 Android 团队。Android 模块化开发仍在优化,欢迎加入我们一起补充国内生态位缺少的解决方案。

企业微信客户端团队,包括 iOS、Andrroid、Windows、Mac、Web 五大平台。我们重视跨平台技术框架的研发,各类原创技术专利,截止去年,仅数十人的技术团队在近3年内提交技术专利百余项。团队招聘优秀技术人才,岗位分布在成都、广州、深圳。欢迎在官网投递简历。

可在 hr.tencent.com 搜索企业微信相关岗位,或者扫码联系 HR

参考资料

[1]

官方文档: https://developer.android.com/platform/technology/app-bundle

[2]

依赖配置 Configuration : https://docs.gradle.org/current/userguide/declaring_dependencies.html

[3]

android.content.pm.PackageParser#parsePackage: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/content/pm/PackageParser.java;bpv=1;bpt=1;l=1057?gsn=parsePackage&gs=kythe%3A%2F%2Fandroid.googlesource.com%2Fplatform%2Fsuperproject%3Flang%3Djava%3Fpath%3Dandroid.content.pm.PackageParser%239bdc30a4a5ef088db38c1edea5644b6fb075ec3fd6224d4372620ef9ca96cb29

[4]

android.content.pm.PackageParser#parseSplitApplication: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/content/pm/PackageParser.java;drc=master;bpv=1;bpt=1;l=3966?gsn=parseSplitApplication&gs=kythe%3A%2F%2Fandroid.googlesource.com%2Fplatform%2Fsuperproject%3Flang%3Djava%3Fpath%3Dandroid.content.pm.PackageParser%2374922f0b4c87441d4a27c24d2d68e6a3c269a12dbd4d9bc585ce84714ef09852

[5]

--stable-ids: https://developer.android.com/studio/command-line/aapt2

[6]

google(): https://dl.google.com/dl/android/maven2/

[7]

aosp-android-9.0.0-r59-aapt2: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-9.0.0_r59/tools/aapt2/

[8]

FeatureSplitSymbolTableDelegate: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/tools/aapt2/cmd/Link.cpp;bpv=1;bpt=1;l=175?q=FeatureSplitSymbolTableDelegate&ss=android%2Fplatform%2Fsuperproject&gsn=FeatureSplitSymbolTableDelegate&gs=kythe%3A%2F%2Fandroid.googlesource.com%2Fplatform%2Fsuperproject%3Flang%3Dc%252B%252B%3Fpath%3Dframeworks%2Fbase%2Ftools%2Faapt2%2Fcmd%2FLink.cpp%23X7zI0WZo9YoEo05MFLqlo2cE4VK5wJRKMPJR6SrWUsg

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-05-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WeMobileDev 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、项目背景
    • Android App Bundle
      • 企业微信
      • 企业微信模块化开发演进
        • 模块化开发解决什么?
          • 为什么启动阶段三重构?
          • 二、思路和挑战
            • 低入侵、业务代码基本零重构
              • 编译关键任务分析
              • 模块依赖冲突
              • AndroidManifest 合并错误
              • 资源编译错误
              • 代码编译错误
            • 低成本切换
              • 研发模式
              • 研发流程
              • 国内生态
          • 三、全景图
          • 四、写在最后
            • 团队介绍
              • 参考资料
          相关产品与服务
          云开发 CloudBase
          云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档