一、性能优化:编译速度、产物大小与并行编译
说到 Android 构建系统的性能优化,我脑子里立刻浮现出几年前的一个项目。那时候我们团队维护一个超级 App,单次全量编译要 40 分钟。你想想看,改一行代码,等 40 分钟才能验证,这谁受得了?后来我们花了整整两周时间,把编译时间压到了 12 分钟以内,产物大小也砍掉了 30%。今天我就把那些实战经验掰开揉碎了讲给你听。
1.1 编译速度优化:从“等”到“不等”
编译慢,说白了就是 CPU 在空转、磁盘在排队、或者任务在串行。我个人的习惯是,先抓大放小,优先解决最耗时的环节。
核心原则:减少不必要的编译,让增量编译尽可能生效。
1.1.1 模块化与增量编译
Android Gradle 插件默认支持增量编译,但前提是你的模块划分要合理。我曾经见过一个项目,所有代码都塞在一个 module 里,改一行 Java 就得重新编译整个模块。这其实违背了 Gradle 的设计初衷。
我建议你把业务模块拆细一点。比如按功能拆成:
module_common(基础库)module_login(登录模块)module_home(首页模块)module_profile(个人中心)
每个模块独立编译,互不干扰。这样你改登录模块的代码,首页模块根本不需要重新编译。嗯,这里要注意:模块之间的依赖关系不要搞成循环依赖,否则 Gradle 会直接报错。
1.1.2 开启 Gradle 构建缓存
构建缓存是 Gradle 自带的功能,但很多人没开对。你需要在 gradle.properties 里加上:
org.gradle.caching=true
org.gradle.caching.debug=false
我个人习惯把缓存目录指向一个 SSD 分区,速度会快很多。如果你用的是 CI 服务器,还可以配置远程缓存,比如用 Redis 或者 NFS 共享。我在项目中遇到过,本地缓存和远程缓存同时开启时,偶尔会出现缓存冲突。解决办法是给每个构建任务加一个唯一的 buildId。
1.1.3 优化 Annotation Processor
注解处理器是编译速度的隐形杀手。像 Dagger、ButterKnife、Room 这些库,都会在编译期生成大量代码。我建议你:
- 尽量用 KSP 替代 KAPT(Kotlin 项目)
- 如果必须用 KAPT,可以开启增量处理:
kapt.incremental.apt=true - 避免在同一个模块里堆太多注解处理器
我记得有一次,一个模块里同时用了 Dagger、AutoValue 和 Room,编译时间直接翻倍。后来我们把 Room 的数据库定义单独抽成一个模块,速度就恢复正常了。
1.2 产物大小优化:给 APK 瘦身
产物大小直接影响用户下载转化率和留存率。我见过一个 App,APK 从 30MB 优化到 15MB 后,下载转化率提升了 12%。这可不是玄学,是实打实的收益。
我的经验:产物优化要分三步走——砍资源、压缩代码、混淆。
1.2.1 资源瘦身:图片与语言
图片是 APK 体积的大头。我建议你用 WebP 格式替代 PNG/JPEG,无损压缩率能到 30% 左右。在 build.gradle 里配置:
android {
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
crunchPngs true
}
}
}
另外,语言资源也要精简。很多 App 只支持中文和英文,但默认会打包几十种语言。你可以用 resConfigs 来限制:
defaultConfig {
resConfigs "zh", "en"
}
这一行代码,就能把多语言资源从 5MB 砍到 500KB。嗯,这里要注意:如果你的 App 有国际化需求,别一刀切,先看看实际支持哪些语言。
1.2.2 代码混淆与 R8 全量优化
R8 是 Android 官方推荐的代码压缩工具,它比 ProGuard 更激进。我建议你在 release 构建中开启:
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
shrinkResources 会移除未使用的资源文件。我曾经在一个项目里发现,有 200 多张图片在代码里根本没引用,但一直被打包进 APK。开启这个选项后,APK 直接小了 4MB。
但要注意,R8 有时候会误删反射调用的类。我建议你在 proguard-rules.pro 里保留这些规则:
-keep class com.yourpackage.** { *; }
-keepclassmembers class * {
@android.webkit.JavascriptInterface <methods>;
}
1.2.3 动态特性模块
如果你的 App 功能很多,可以考虑用 Android App Bundle 和动态特性模块。这样用户只下载当前需要的功能,而不是一次性下载整个 App。我参与过的一个电商项目,把“直播”和“社区”两个模块做成动态特性,APK 基础包从 40MB 降到了 18MB。
避坑指南:动态特性模块需要 Google Play 支持,国内应用商店不一定兼容。如果你主要面向国内市场,建议用插件化方案或者按需下载。
1.3 并行编译配置:让 CPU 满负荷运转
现在的开发机至少是 8 核 16 线程,但很多人的 Gradle 配置只用了 2 个线程。这就像开着法拉利却挂一档跑。我个人的习惯是,把并行编译开到 CPU 核心数的 75% 左右。
1.3.1 Gradle 守护进程与并行任务
在 gradle.properties 里配置:
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.workers.max=8
org.gradle.parallel=true 会让 Gradle 并行执行独立模块的任务。比如你同时编译 module_a 和 module_b,它们互不依赖,就可以同时进行。我建议你把 workers.max 设成 CPU 线程数减 2,留两个线程给系统和其他应用。
1.3.2 配置按需编译
Gradle 4.6 之后引入了配置按需(Configuration On Demand)功能。它只配置当前需要的模块,而不是整个项目。开启方式:
org.gradle.configureondemand=true
不过要注意,这个功能在某些多模块项目中会出问题。我记得有一次,开启后某个模块的依赖死活解析不了。后来发现是模块间的依赖声明不规范。如果你遇到类似问题,可以先关掉这个选项,排查依赖关系。
1.3.3 使用构建分析工具
优化不能靠猜。我建议你用 Gradle 自带的 --profile 参数生成构建报告:
./gradlew assembleDebug --profile
报告会生成在 build/reports/profile/ 目录下。你可以看到每个任务的耗时、GC 次数、线程利用率。我一般先看“Task Execution”部分,找出耗时最长的几个任务,然后针对性地优化。
核心总结:编译速度优化靠增量,产物大小优化靠压缩和裁剪,并行编译靠合理配置。这三板斧砍下去,你的构建体验会有质的飞跃。
知识体系图:性能优化全景
这张图把性能优化的三个维度串起来了。你从任何一个分支切入都可以,但我建议先做编译速度优化,因为见效最快。等你把编译时间降下来,再慢慢抠产物大小。并行编译配置是基础,一开始就设好,后面省很多事。