构建优化基础
影响构建速度的因素
主要有以下几个方面:
- 硬件性能:CPU、RAM 等;
- 构建配置:缓存、增量编译等;
- 项目:项目的大小和复杂度,代码量、模块化、依赖管理等;
- 其他:网络速度,下载慢或者找不到等
构建耗时分析工具
Build Analyzer
Build Analyzer
,是 Android Studio 提供的构建分析器,可以在 build 窗口中打开查看。
--profile
Profile
是 Gradle 提供的构建检测工具,通过命令行执行 --profile
可以在本地生产一份 HTML 报告。
1
./gradlew assembleDebug --profile
在构建完成后,在 project-root/build/reports/profile/
目录会生成构建报告。
如何查看?
Summary
默认是概览界面,包括 Total Build Time
、Configuring Projects
、Task Execution
等基本信息。
 |
Configuration
Configuration 就是配置阶段耗时,包括 All projects、app 以及其它模块的配置时间。
Dependency Resolution
依赖解析耗时。
Artifact Transforms
Gradle 在新版本报告中还加入了 Transform 耗时,比如做些产物转换啊啥的
Task Execution
各个 Task 的执行时长。
 |
Total Build Time 才 3s 呢,这个 Task Execution 怎么就 4s 了?
这是因为这里统计的是 Task 执行的总时长,实际编译过程中 Task 是 并行
在跑的,所以会出现大于总时长的情况。
--scan
scan 介绍
Build Scan
也是 Gradle 提供的构建分析工具,相比于 Profile 方式,Build Scan 提供的信息更加丰富,且是在线报告,而且还可以用来排查编译错误。
构建扫描(build scan)是一个中心化并且可以共享的构建记录,构建完成后会将报告发到:[Getting started with Build Scan® for Gradle, Apache Maven™, and sbt | Gradle Inc.](https://scans.gradle.com/) |
使用 scan
命令
使用非常简单,命令加上 --scan
即可:
1
2
3
4
./gradlew build --scan
# 如果你是首次构建,报告上传Gradle服务器需要同意相关条款,yes即可
# 执行完毕,会随机生成一个链接
# 如果你是首次使用Build Scan,还需要输入邮箱激活一下
为项目默认添加 scan
除了每次在命令行中输入 --scan
以外, 还可以在项目中配置默认开启 scan. 需要在 Root Build Script(根项目目录下 build.gradle.kts ) 中加入:
1
2
3
4
5
6
7
8
9
// Root Build Script
plugins {
`build-scan`
}
buildScan {
publishAlways()
termsOfServiceUrl = "https://gradle.com/terms-of-service"
termsOfServiceAgree = "yes"
}
buildScan
是 BuildScanExtension
类型的 extension:
- 其中 publishAlways 设置每次
gradle
构建都自动上传构建结果 - termsOfServiceUrl 与 termsOfServiceAgree 的组合表示自动接受条款 (类似于软件安装中的 “ 下一步 “… 上传构建结果也需要同意一个 xxx 条款), 这样就能避免在命令行中回答 “yes” 同意条款了
为所有项目添加 scan
还可以把 scan 的设置放在 Init Script(init.gradle.kts)
中, 这样既不改动项目的脚本, 又能为所有项目开启 scan, 这在 CI 上尤为实用
查看 scan 报告
Summary
这个概览信息就比 Profile 详细多了,其中包括你的 Gradle 版本
、total build time
、projects
、构建配置的开关
、编译的环境
等等。
 |
Performance
Performance 的二级菜单要比 Profile 更丰富一些,除了 Build、Configuration、Dependency resolution、Task execution 这四项外,还有 Build Cache、Daemon、Network activity。
Task execution
这里除了展示所有 Task 的执行时间之外,还展示了有多少个 Task 是命中缓存的,有多少个 Task 是增量编译的等等。
 |
Timeline
 |
在 Timeline
里我们可以看到所有 Task 执行的时间线。
点击具体的 Task 可以看到执行的具体信息,点击「Focus on task in timeline」可以看到该 Task 在整个时间线的具体位置。
Switches
 |
switches 里面会展示一些 Gradle 配置的开关状态,可以很好的帮助你查漏补缺。
比如我这里 Cache 都没开,并行编译也没开,离线也没开,如果这些都开了,构建性能又会有大大的提升。
gradle-doctor
三方插件发现构建慢问题
Gradle-doctor 插件 (构建时后输出可能导致构建慢的点)
https://runningcode.github.io/gradle-doctor 构建过程中,发现构建速度慢的,会打印 log 出来  |
Gradle 生命周期统计
Gradle 生命周期 hook 统计各个阶段耗时情况
构建优化思路?
- Gradle configuration 耗时?
- Kapt 耗时大户,迁移 ksp?
- Transform 优化
- Jetifier 移除?
- 升级到最新编译工具链 (Gradle 和 AGP 7. X 提升很大)
单仓还是多仓
多仓
优点
- 仓库独立,安全性,代码权限更好管理
- 工程同步和编译的速度会更快,因为大部分仓库都已经被编译成 aar 产物了,所以对于分仓模式来说,他们的同步和编译都只需要对于当前工程负责就可以了,不需要编译与当前工程无关的东西,所以速度上来说会更快。
存在的问题
- 分支管理不对齐,打包经常失败,耗费开发精力排查问题;其他团队的改动,可能导致依赖该业务未及时同步,编译失败等问题
- 代码复用率低,多仓隔离了,抽取公共涉及其他团队代码改到,测试等问题,经常都是自己的业务线各自搞一套
- AAR 和源码依赖切换的不稳定性,源码依赖优先级比 AAR 依赖高,同一个资源存在于 AAR 和源码,导致源码覆盖掉 AAR
大仓 MonoRepo
什么是 MonoRepo?
将所有代码放在一个 Git 仓库中,通过分层来划分不同业务
MonoRepo
的缺点
- 编译速度更慢
相比较于分仓模式,MonoRepo
的编译速度会更慢,同步的时间也会更长。因为每个工程都需要重新 Configuration
策略,将 aar 依赖方式切换成源代码依赖。同时不同于 aar 依赖的情况,源代码依赖的情况下每个工程的 build.gradle
还有全局配置以及插件等都需要被执行到,所以消耗的时间会更长一点点。也就是正常的 gradle 相关的生命周期,对于源码编译的工程都是需要执行一次的
- 安全性,权限管理更差
- 同时工程体量会变得非常巨大,也会造成编码过程中需要频繁的 rebase 主干的代码,可能每天都会有巨量的代码落后的情况。但是这个个人觉得是在可预期范围内的。
MonoRepo 优点
- 开发状况下你的仓库状态是稳定的,不会出现多仓分支没对齐,编译失败的问题
- 代码复用性高:代码都可见,减少重复造轮子
- 更有效的依赖检查:不会出现多仓依赖环的问题
- 更简便的代码升级操作:如升级 AGP 版本
- 单一检查工具:避免重复性建设的工作了,因为仓库单一所以只要对当前仓库进行一份静态检查就行了,避免重复造轮子的风险。
Ref
- 抖音单仓 +monorepo
GitHub - blundell/monorepo: An android monorepo example [Make a monorepo for your Android projects Blundell](https://blog.blundellapps.co.uk/make-a-monorepo-for-your-android-projects/)