一、性能优化:编译速度、产物大小与并行编译

说到 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_amodule_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”部分,找出耗时最长的几个任务,然后针对性地优化。

核心总结:编译速度优化靠增量,产物大小优化靠压缩和裁剪,并行编译靠合理配置。这三板斧砍下去,你的构建体验会有质的飞跃。

知识体系图:性能优化全景

Android 构建性能优化 编译速度优化 模块化 + 增量编译 构建缓存(本地/远程) 优化 Annotation Processor 产物大小优化 资源瘦身(WebP/语言) R8 全量优化 + 混淆 动态特性模块 并行编译配置 守护进程 + 并行任务 配置按需编译 构建分析工具 核心目标:减少等待 + 缩小体积 + 压榨硬件 三者相辅相成,缺一不可

这张图把性能优化的三个维度串起来了。你从任何一个分支切入都可以,但我建议先做编译速度优化,因为见效最快。等你把编译时间降下来,再慢慢抠产物大小。并行编译配置是基础,一开始就设好,后面省很多事。


公众号:蓝海资料掘金营,微信 deep3321