⚠️本文为稀土技能社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!
前言
关于 Android
编译加快的文章信任大家都看过不少,但常常要么是好几年前写的,现在看来有些过期;要么介绍了一大堆装备,最后一实践发现并没有多大作用;要么便是大厂黑科技,可是没有开源。
今日咱们就一同来看看,在2022年AGP7.0
时代,除了传统的敞开build-cache
,翻开并行编译,调整Gradle
堆内存大小等常用手段之外,还有哪些能够落地的编译加快实用技巧
运用最新版别编译东西链
既然是2022年编译加快的实用技巧,首要便是要求编译东西链的版别比较新,后面介绍的技巧大部分也是新引进的特性
简直每次更新时,Android
编译东西链都会得到必定功能上的优化或者是引进新的功能,因而咱们应该及时跟进Gradle
,Android Gradle Plugin
和Kotlin Gradle Plugin
等东西的更新,才能及时获得到相应的功能提升
Transform
搬迁到AsmClassVisitorFactory
Transform API
是AGP1.5
就引进的特性,首要用于在Android
构建进程中,在Class
转Dex
的进程中修正Class
字节码。利用Transform API
,咱们能够拿到一切参加构建的Class
文件,然后能够凭借ASM
等字节码修正东西进行修正,插入自定义逻辑。
国内很多团队都或多或少的用AGP
的Transform API
来搞点儿黑科技,比方无痕埋点,耗时计算,方法替换等。可是在AGP7.0
中Transform
现已被标记为废弃了,而且将在AGP8.0
中移除
在AGP7.0
之后,能够运用AsmClassVisitorFactory
来做插桩,依据官方的说法,AsmClassVisitoFactory
会带来约18%的功能提升,一起能够削减约5倍代码
AsmClassVisitorFactory
之所以比Transform
在功能上有优势,首要在于节省了IO
的时刻。
如上图所示,多个Transform
相互独立,都需求经过IO
读取输入,修正字节码后将结果经过IO
输出,供下一个Transform
运用,假如每个Transform
操作IO
耗时+10s的话,各个Transform
叠在一同,编译耗时就会呈线性增长
而运用AsmClassVisitorFactory
则不需求咱们手动进行IO
操作,这是因为AsmInstrumentationManager
中现已做了一致处理,只需求进行一次IO
操作,然后交给ClassVisitor
列表处理,完结后一致输出
经过这种方法,能够有效地削减IO
操作,削减耗时。其实国内之前滴滴开源的Booster
与字节开源的Bytex
,都是经过这种思路来优化Transform
功能的,现在官方终于跟进了
总得来说,AsmClassVisitorFactory
在功能上与易用性上都有必定的提升,具体用法可拜见:Transform 被废弃,ASM 怎么适配?
KAPT 搬迁到 KSP
注解处理器是Android
开发中一种常用的技能,很多常用的结构比方ButterKnife
,ARouter
,Glide
中都运用到了注解处理器相关技能
可是假如项目比较大的话,会很简略发现KAPT
是拖慢编译速度的常见原因,这也是谷歌推出KSP
取代KAPT
的原因
从上面这张图其实就能够看出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
现已发布了稳定版了,像Room
,Moshi
等库也现已做了适配,关于这些现已适配了的库,咱们能够直接搬迁。
但仍是有一些常用的库比方Glide
,ARouter
还没有做适配,这些库是咱们移除KAPT
最大的妨碍。
下面给出一些还不支撑KSP
的库的过渡搬迁方法
-
KAPT
一般便是用来生成代码,像Glide
这种生成的代码比较稳定的库(一般只需几个@GlideModule
),能够直接把生成的代码从build
目录拷贝到项目中来,然后移除KAPT
,后续假如有新的@GlideModule
再更新下生成的文件(当然这样或许不太便利,仅仅一种过渡的方法,等候Glide
官方更新) - 关于
ARouter
这种生成代码不断增加的库(不断有新的@ARouter
注解),上面的方法就不太适用了。考虑到ARoutr
现已很久没有更新了,能够考虑搬迁到一个不运用KAPT
的新的路由库
敞开Configuration Cache
咱们知道,Gradle
的生命周期能够分为大的三个部分:初始化阶段(Initialization Phase
),装备阶段(Configuration Phase
),履行阶段(Execution Phase
),如下图所示:
其中使命履行的部分只需处理恰当,现已能够很好的进行缓存和重用,假如把这个机制运用到其他阶段当然也能带来一些收益。
仅次于履行阶段耗时的一般是装备阶段, 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
之后作用仍是比较明显的,假如构建脚本没有发生变化能够直接跳过装备阶段
Android
官方给出了一个敞开Configuration cache
前后的比照,能够看出在这个benchmark
中能够得到约30%的提升(当然是在装备阶段耗时占比越高的时分作用越明显,全量编译时应该不会有这样的份额)
Configuration Cache
适配
当然翻开Configuration Cache
之后或许会有一些适配问题,假如是第三方插件,发现常用插件呈现不支撑的状况,可先查找是否有相同的问题现已呈现并修正
假如是项目中自定义Task
不支撑的话,还需求适配一下Configuration Cache
,适配Configuration Cache
的中心思路其实很简略:不要在Task
履行阶段调用外部不行序列化的目标(比方Project
与Variant
)
不过假如你的项目中自定义Task
比较多的话,适配Configuration Cache
或许是个体力活,比方 AGP
兼容 Configuration Cache
就修了 400 多个 ISSUE
如需具体了解装备缓存,请参阅装备缓存深度解析和有关装备缓存的 Gradle 文档
移除Jetifier
Jetifier
是android support
包搬迁到androidX
的东西,当你在项目中启动用Jetifier
时 ,Gradle
插件会在构建时将三方库里的Support
转化成AndroidX
,因而会对构建速度产生影响。
一起Jetfier
也会对sync
耗时产生比较大的影响,详情可见B站大佬的剖析:哔哩哔哩 Android 同步优化•Jetifier
Jetifier
在AndroidX
刚呈现时是一个十分实用的东西,能够协助咱们快速的搬迁到AndroidX
。可是到了2022年,信任绝大多数库都现已搬迁到了AndroidX
,Jetifier
的历史使命能够说现已完结了,因而是时分移除Jetifier
了
检测不支撑Jetifier
的库
AGP7.0
现已提供了东西供咱们查看每个module
能否移除Jetifier
,直接运转./gradlew checkJetifier
即可,经过以下命令查看一切module
的Jetifier
运用状况
task checkJetifierAll(group: "verification") { }
subprojects { project ->
project.tasks.whenTaskAdded { task ->
if (task.name == "checkJetifier") {
checkJetifierAll.dependsOn(task)
}
}
}
经过运转./gradlew checkJetifierAll
就能够打印出一切module
的Jetifier
运用状况
搬迁计划
在清晰了哪些库还不支撑Jetifier
之后,能够一步步开端搬迁了
- 检测库有没有现已支撑了
androidX
的最新版别, 假如有直接晋级即可 - 假如有源码,增加
android.useAndroidX = true
,搬迁到AndroidX
,然后晋级一切的依靠,发布个新版别就能够了。 - 假如你没有源码,或关于你的项目来说,它太老了。你能够用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
架构如下:
编译打包时每个模块生成的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
文件都需求从头编译。
- 封闭
R
文件传递能够经过编译防止的方法获得更快的编译速度 - 封闭
R
文件传递有助于保证每个模块的R
类仅包括对其自身资源的引证,防止无意中引证其他模块资源,清晰模块边界。 - 封闭
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
模块上测得:
能够看出,当缓存射中时有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