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

作者:程序员江同学
转载地址:https://juejin.cn/post/7153250843905654798

前言

关于 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倍代码

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开发中一种常用的技术,很多常用的框架比如ButterKnifeARouterGlide中都使用到了注解处理器相关技术

但是如果项目比较大的话,会很容易发现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已经发布了稳定版了,像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),如下图所示:

其中任务执行的部分只要处理恰当,已经能够很好的进行缓存和重用,如果把这个机制运用到其他阶段当然也能带来一些收益。

仅次于执行阶段耗时的一般是配置阶段, 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执行阶段调用外部不可序列化的对象(比如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 架构如下:

编译打包时每个模块生成的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模块上测得:

可以看出,当缓存命中时有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 相关知识点的整理

有需要的可以复制下方链接,传送直达!!!
https://qr21.cn/CaZQLo?BIZ=ECOMMERCE

原文地址:https://blog.csdn.net/weixin_61845324

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


更新Android SDK到3.0版本时,遇到Failed to rename directory E:\android\tools to E:\android\temp\ToolPackage.old01问题,导致无法更新,出现该问题的原因是由于3.0版本与较早的sdk版本之间文件结构有冲突,解决
Android 如何解决dialog弹出时无法捕捉Activity的back事件 在一些情况下,我们需要捕捉back键事件,然后在捕捉到的事件里写入我们需要进行的处理,通常可以采用下面三种办法捕捉到back事件: 1)重写onKeyDown或者onKeyUp方法 2)重写onBackPressed方
Android实现自定义带文字和图片的Button 在Android开发中经常会需要用到带文字和图片的button,下面来讲解一下常用的实现办法。一.用系统自带的Button实现 最简单的一种办法就是利用系统自带的Button来实现,这种方式代码量最小。在Button的属性中有一个是drawable
Android中的&quot;Unable to start activity ComponentInfo&quot;的错误 最近在做一款音乐播放器的时候,然后在调试的过程中发现一直报这个错误&quot;Unable to start activity ComponentInfo&quot;,从字面
Android 关于长按back键退出应用程序的实现最近在做一个Android上的应用,碰到一个问题就是如何实现长按back键退出应用程序。在网上查找了很多资料,发现几乎没有这样的实现,大部分在处理时是双击back键来退出应用程序。参考了一下双击back键退出应用程序的代码,网上主流的一种方法是下面
android自带的时间选择器只能精确到分,但是对于某些应用要求选择的时间精确到秒级,此时只有自定义去实现这样的时间选择器了。下面介绍一个可以精确到秒级的时间选择器。 先上效果图: 下面是工程目录: 这个控件我也是用的别人的,好像是一个老外写的,com.wheel中的WheelView是滑动控件的主
Android平台下利用zxing实现二维码开发 现在走在大街小巷都能看到二维码,而且最近由于项目需要,所以研究了下二维码开发的东西,开源的二维码扫描库主要有zxing和zbar,zbar在iPos平台上应用比较成熟,而在Android平台上主流还是用zxing库,因此这里主要讲述如何利用zxing
Android ListView的item背景色设置以及item点击无响应等相关问题 在Android开发中,listview控件是非常常用的控件,在大多数情况下,大家都会改掉listview的item默认的外观,下面讲解以下在使用listview时最常见的几个问题。1.如何改变item的背景色和按
如何向Android模拟器中导入含有中文名称的文件在进行Android开发的时候,如果需要向Android模拟器中导入文件进行测试,通过DDMS下手动导入或者在命令行下通过adb push命令是无法导入含有中文文件名的文件的。后来发现借用其他工具可以向模拟器中导入中文名称的文件,这个工具就是Ultr
Windows 下搭建Android开发环境一.下载并安装JDK版本要求JDK1.6+,下载JDK成功后进行安装,安装好后进行环境变量的配置【我的电脑】-——&gt;【属性】——&gt;【高级】 ——&gt;【环境变量】——&gt;【系统变量】中点击【新建】:变量名:CLASSPATH变量值:……
如何利用PopupWindow实现弹出菜单并解决焦点获取以及与软键盘冲突问题 在android中有时候可能要实现一个底部弹出菜单,此时可以考虑用PopupWindow来实现。下面就来介绍一下如何使用PopupWindow实现一个弹出窗。 主Activity代码:public void onCreat
解决Android中的ERROR: the user data image is used by another emulator. aborting的方法 今天调试代码的时候,突然出现这个错误,折腾了很久没有解决。最后在google上找到了大家给出的两种解决方案,下面给出这两种方法的链接博客:ht
AdvserView.java package com.earen.viewflipper; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory;
ImageView的scaleType的属性有好几种,分别是matrix(默认)、center、centerCrop、centerInside、fitCenter、fitEnd、fitStart、fitXY。 |值|说明| |:--:|:--| |center|保持原图的大小,显示在ImageVie
文章浏览阅读8.8k次,点赞9次,收藏20次。本文操作环境:win10/Android studio 3.21.环境配置 在SDK Tools里选择 CMAKE/LLDB/NDK点击OK 安装这些插件. 2.创建CMakeLists.txt文件 在Project 目录下,右键app,点击新建File文件,命名为CMakeLists.txt点击OK,创建完毕! 3.配置文件 在CMa..._link c++ project with gradle
文章浏览阅读1.2w次,点赞15次,收藏69次。实现目的:由mainActivity界面跳转到otherActivity界面1.写好两个layout文件,activity_main.xml和otherxml.xmlactivity_main.xml&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;RelativeLayout ="http://schemas..._android studio 界面跳转
文章浏览阅读3.8w次。前言:最近在找Android上的全局代理软件来用,然后发现了这两款神作,都是外国的软件,而且都是开源的软件,因此把源码下载了下来,给有需要研究代理这方面的童鞋看看。不得不说,国外的开源精神十分浓,大家相互使用当前基础的开源软件,然后组合成一个更大更强的大开源软件。好吧,废话不多说,下面简单介绍一下这两款开源项目。一、ProxyDroid:ProxyDroid功能比较强大,用到的技术也比较多,源码也_proxydroid
文章浏览阅读2.5w次,点赞17次,收藏6次。创建项目后,运行项目时Gradle Build 窗口却显示错误:程序包R不存在通常情况下是不会出现这个错误的。我是怎么遇到这个错误的呢?第一次创建项目,company Domain我使用的是:aven.com,但是创建过程在卡在了Building 'Calculator' Gradle Project info这个过程中,于是我选择了“Cancel”第二次创建项目,我还是使用相同的项目名称和项目路_r不存在
文章浏览阅读8.9w次,点赞4次,收藏43次。前言:在Android上使用系统自带的代理,限制灰常大,仅支持系统自带的浏览器。这样像QQ、飞信、微博等这些单独的App都不能使用系统的代理。如何让所有软件都能正常代理呢?ProxyDroid这个软件能帮你解决!使用方法及步骤如下:一、推荐从Google Play下载ProxyDroid,目前最新版本是v2.6.6。二、对ProxyDroid进行配置(基本配置:) (1) Auto S_proxydroid使用教程
文章浏览阅读1.1w次,点赞4次,收藏17次。Android Studio提供了一个很实用的工具Android设备监视器(Android device monitor),该监视器中最常用的一个工具就是DDMS(Dalvik Debug Monitor Service),是 Android 开发环境中的Dalvik虚拟机调试监控服务。可以进行的操作有:为测试设备截屏,查看特定进程中正在运行的线程以及堆栈信息、Logcat、广播状态信息、模拟电话_安卓摄像头调试工具