⚠️本文为稀土技能社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!

前言

关于 Android 编译加快的文章信任大家都看过不少,但常常要么是好几年前写的,现在看来有些过期;要么介绍了一大堆装备,最后一实践发现并没有多大作用;要么便是大厂黑科技,可是没有开源。

今日咱们就一同来看看,在2022年AGP7.0时代,除了传统的敞开build-cache,翻开并行编译,调整Gradle堆内存大小等常用手段之外,还有哪些能够落地的编译加快实用技巧

运用最新版别编译东西链

既然是2022年编译加快的实用技巧,首要便是要求编译东西链的版别比较新,后面介绍的技巧大部分也是新引进的特性

简直每次更新时,Android 编译东西链都会得到必定功能上的优化或者是引进新的功能,因而咱们应该及时跟进GradleAndroid Gradle PluginKotlin Gradle Plugin等东西的更新,才能及时获得到相应的功能提升

Transform搬迁到AsmClassVisitorFactory

Transform APIAGP1.5就引进的特性,首要用于在Android构建进程中,在ClassDex的进程中修正Class字节码。利用Transform API,咱们能够拿到一切参加构建的Class文件,然后能够凭借ASM 等字节码修正东西进行修正,插入自定义逻辑。

国内很多团队都或多或少的用AGPTransform API来搞点儿黑科技,比方无痕埋点,耗时计算,方法替换等。可是在AGP7.0Transform现已被标记为废弃了,而且将在AGP8.0中移除

AGP7.0之后,能够运用AsmClassVisitorFactory来做插桩,依据官方的说法,AsmClassVisitoFactory会带来约18%的功能提升,一起能够削减约5倍代码

2022年编译加速的8个实用技巧

AsmClassVisitorFactory之所以比Transform在功能上有优势,首要在于节省了IO的时刻。

2022年编译加速的8个实用技巧

如上图所示,多个Transform相互独立,都需求经过IO读取输入,修正字节码后将结果经过IO输出,供下一个Transform运用,假如每个Transform操作IO耗时+10s的话,各个Transform叠在一同,编译耗时就会呈线性增长

2022年编译加速的8个实用技巧

而运用AsmClassVisitorFactory则不需求咱们手动进行IO操作,这是因为AsmInstrumentationManager中现已做了一致处理,只需求进行一次IO操作,然后交给ClassVisitor列表处理,完结后一致输出

经过这种方法,能够有效地削减IO操作,削减耗时。其实国内之前滴滴开源的Booster与字节开源的Bytex,都是经过这种思路来优化Transform功能的,现在官方终于跟进了

总得来说,AsmClassVisitorFactory在功能上与易用性上都有必定的提升,具体用法可拜见:Transform 被废弃,ASM 怎么适配?

KAPT 搬迁到 KSP

注解处理器是Android开发中一种常用的技能,很多常用的结构比方ButterKnifeARouterGlide中都运用到了注解处理器相关技能

可是假如项目比较大的话,会很简略发现KAPT是拖慢编译速度的常见原因,这也是谷歌推出KSP取代KAPT的原因

2022年编译加速的8个实用技巧

从上面这张图其实就能够看出KAPT慢的原因了,KAPT 经过与 Java 注解处理根底架构相结合,让大部分Java言语注解处理器能够在Kotlin中开箱即用。

为此,KAPT 首要需求将 Kotlin 代码编译成 JavaStubs,这些JavaStubs中保留了Java注释处理器关注的信息。

这意味着编译器必须屡次解析程序中的一切符号 (一次生成JavaStubs,另一次完结实际编译),可是生成JavaStubs的进程是十分耗时的,往往生成Java Stubs的时刻比APT真实处理注解的时刻要长

KSP不需求生成JavaStubs,而是作为Kotlin编译器插件运转。它能够直接处理Kotlin符号,而不需求依靠Java注解处理根底架构。

因为KSP比较KAPT少了生成JavaStubs的进程,因而一般能够得到100%以上的速度提升。关于KSP的具体运用方法可拜见:运用 Kotlin Symbol Processing 1.0 缩短 Kotlin 构建时刻

搬迁计划

现在KSP现已发布了稳定版了,像RoomMoshi等库也现已做了适配,关于这些现已适配了的库,咱们能够直接搬迁。

但仍是有一些常用的库比方GlideARouter还没有做适配,这些库是咱们移除KAPT最大的妨碍。

下面给出一些还不支撑KSP的库的过渡搬迁方法

  1. KAPT一般便是用来生成代码,像Glide这种生成的代码比较稳定的库(一般只需几个@GlideModule),能够直接把生成的代码从build目录拷贝到项目中来,然后移除KAPT,后续假如有新的@GlideModule再更新下生成的文件(当然这样或许不太便利,仅仅一种过渡的方法,等候Glide官方更新)
  2. 关于ARouter这种生成代码不断增加的库(不断有新的@ARouter注解),上面的方法就不太适用了。考虑到ARoutr现已很久没有更新了,能够考虑搬迁到一个不运用KAPT的新的路由库

敞开Configuration Cache

咱们知道,Gradle 的生命周期能够分为大的三个部分:初始化阶段(Initialization Phase),装备阶段(Configuration Phase),履行阶段(Execution Phase),如下图所示:

2022年编译加速的8个实用技巧

其中使命履行的部分只需处理恰当,现已能够很好的进行缓存和重用,假如把这个机制运用到其他阶段当然也能带来一些收益。

仅次于履行阶段耗时的一般是装备阶段, AGP现在也支撑了装备阶段缓存 Configuration Cache ,它使得装备阶段的首要产出物:Task Graph 能够被重用

在越大的项目中装备阶段缓存的收益越大,module比较多的项目或许每次履行都要先装备20到30秒。尤其是增量编译时,装备的耗时或许都跟履行的耗时差不多了,而这正是configuration-cache的用武之地

现在Configuration-cache仍是实验特性,假如你想要敞开的话能够在gradle.properties中增加以下代码

# configuration cache
org.gradle.unsafe.configuration-cache=true
org.gradle.unsafe.configuration-cache-problems=warn

敞开了Configuration cache之后作用仍是比较明显的,假如构建脚本没有发生变化能够直接跳过装备阶段

2022年编译加速的8个实用技巧

Android官方给出了一个敞开Configuration cache前后的比照,能够看出在这个benchmark中能够得到约30%的提升(当然是在装备阶段耗时占比越高的时分作用越明显,全量编译时应该不会有这样的份额)

Configuration Cache适配

当然翻开Configuration Cache之后或许会有一些适配问题,假如是第三方插件,发现常用插件呈现不支撑的状况,可先查找是否有相同的问题现已呈现并修正

假如是项目中自定义Task不支撑的话,还需求适配一下Configuration Cache,适配Configuration Cache的中心思路其实很简略:不要在Task履行阶段调用外部不行序列化的目标(比方ProjectVariant)

不过假如你的项目中自定义Task比较多的话,适配Configuration Cache或许是个体力活,比方 AGP 兼容 Configuration Cache 就修了 400 多个 ISSUE

如需具体了解装备缓存,请参阅装备缓存深度解析和有关装备缓存的 Gradle 文档

移除Jetifier

Jetifierandroid support包搬迁到androidX的东西,当你在项目中启动用Jetifier时 ,Gradle插件会在构建时将三方库里的Support转化成AndroidX,因而会对构建速度产生影响。

一起Jetfier也会对sync耗时产生比较大的影响,详情可见B站大佬的剖析:哔哩哔哩 Android 同步优化•Jetifier

JetifierAndroidX刚呈现时是一个十分实用的东西,能够协助咱们快速的搬迁到AndroidX。可是到了2022年,信任绝大多数库都现已搬迁到了AndroidXJetifier的历史使命能够说现已完结了,因而是时分移除Jetifier

检测不支撑Jetifier的库

AGP7.0现已提供了东西供咱们查看每个module能否移除Jetifier,直接运转./gradlew checkJetifier即可,经过以下命令查看一切moduleJetifier运用状况

task checkJetifierAll(group: "verification") { }
subprojects { project ->
    project.tasks.whenTaskAdded { task ->
        if (task.name == "checkJetifier") {
            checkJetifierAll.dependsOn(task)
        }
    }
}

经过运转./gradlew checkJetifierAll就能够打印出一切moduleJetifier运用状况

搬迁计划

在清晰了哪些库还不支撑Jetifier之后,能够一步步开端搬迁了

  1. 检测库有没有现已支撑了androidX的最新版别, 假如有直接晋级即可
  2. 假如有源码,增加android.useAndroidX = true,搬迁到AndroidX,然后晋级一切的依靠,发布个新版别就能够了。
  3. 假如你没有源码,或关于你的项目来说,它太老了。你能够用jetifier-standalone命令行东西把AAR/JAR转成jetified之后的AAR/JAR。这个命令行的转化作用和你在代码里敞开android.enableJetifier的作用是一样的。命令行如下:
// https://developer.android.com/studio/command-line/jetifier
./jetifier-standalone -i <source-library> -o <output-library>  

封闭R文件传递

apk 打包的进程中,module 中的 R 文件采用对依靠库的R进行累计叠加的方法生成。假如咱们的 app 架构如下:

2022年编译加速的8个实用技巧

编译打包时每个模块生成的R文件如下:

1. R_lib1 = R_lib1;
2. R_lib2 = R_lib2;
3. R_lib3 = R_lib3;
4. R_biz1 = R_lib1 + R_lib2 + R_lib3 + R_biz1(biz1自身的R)
5. R_biz2 = R_lib2 + R_lib3 + R_biz2(biz2自身的R)
6. R_app = R_lib1 + R_lib2 + R_lib3 + R_biz1 + R_biz2 + R_app(app自身R)

能够看出各个模块的R文件都会包括上层组件的R文件内容,这不仅会带来包体积问题,也会给编译速度带来必定的影响。比方咱们在R_lib1中增加了资源,一切下流模块的R文件都需求从头编译。

  1. 封闭R文件传递能够经过编译防止的方法获得更快的编译速度
  2. 封闭R文件传递有助于保证每个模块的R类仅包括对其自身资源的引证,防止无意中引证其他模块资源,清晰模块边界。
  3. 封闭R文件传递也能够削减很大一部分包体积与dex数量

搬迁计划

Android Studio Bumblebee 开端,新项目的非传递 R 类默许处于敞开状态。即gradle.properties文件中都敞开了如下标记

android.nonTransitiveRClass=true

关于运用早期版别的 Studio 创建的项目,您能够顺次前往 Refactor > Migrate to Non-transitive R Classes,将项目更新为运用非传递 R 类。

敞开Kotlin跨模块增量编译

运用组件化多模块开发的同学都有经历,当咱们修正底层模块(比方util模块)时,一切依靠于这个模块的上层模块都需求从头编译,Kotlin的增量编译在这种状况往往是不收效的,这种时分的编译往往十分耗时

Kotlin 1.7.0中,Kotlin编译器关于跨模块增量编译也做了支撑,而且与Gradle构建缓存兼容,对编译防止的支撑也得到了改善。这些改善削减了模块和文件从头编译的次数,让全体编译更加敏捷

优化作用

首要来看下Kotlin官方的数据,以下基准测试结果是在Kotlin项目中的kotlin-gradle-plugin模块上测得:

2022年编译加速的8个实用技巧

能够看出,当缓存射中时有86%到96%的加快作用,当缓存没有射中时也有26%的加快作用

我在项目中敞开后实测作用也很不错,修正一个底层模块,在特性敞开前需求耗时4分钟左右,敞开后增量编译耗时削减到30到40s,加快约85%

怎么敞开

gradle.properties 文件中设置以下选项即可运用新方法进行增量编译:

kotlin.incremental.useClasspathSnapshot=true // 敞开跨模块增量编译
kotlin.build.report.output=file // 可选,启用构建陈述

能够看出,敞开步骤仍是十分简略的,关于Kotlin跨模块增量编译的原理可拜见:Kotlin 增量编译的新方法

关于增量编译,稳定性和可靠性至关重要。有时增量编译总会失效,Kotlin 1.7相同支撑为编译使命创建编译陈述,陈述包括不同编译阶段的持续时刻以及无法运用增量编译的原因,能够协助你定位为什么增量编译失效了

关于编译陈述的启用与运用可见:隆重推出 Kotlin 构建陈述

晋级你的电脑装备

除了上述的软件方向的一系列优化,也能够从硬件方向进行优化,也便是晋级你的电脑装备

个人感觉影响编译速度的关键基本装备如下:

  • CPU:2022年了,最好直接上M1吧,的确要快不少,信任大家应该看到过一些说换M1后编译速度变快的帖子
  • 内存:至少要16G,有条件建议上32G,关于一些大型项目,内存甚至比CPU更重要,因为Gradle看护进程占用的内存能够十分大
  • 硬盘:必须是SSD固态硬盘,256G牵强够用,最好是512G,Gradle构建缓存(build-cache)占用的空间也挺大的

从硬件方向入手,有时也能够得到不错的优化作用,充钱是真的能够变强的

总结

本文首要介绍了编译加快的8个实用技巧,有的接入起来十分简略,有的则需求必定的适配成本,但都是能够落地的而且有必定作用的编译加快技巧

总得来说,为了充分利用最新的优化技巧与各种新功能,咱们应该及时跟进android编译东西链的更新

假如本文对你有所协助,欢迎点赞保藏~

更多

Android官方文档 – 优化构建速度
How we reduced our Gradle build times by over 80%
10 ideas to improve your Gradle build times