构建速度衡量标准
在我们开启速度提升调优之前,来了解本次三个性能指标的说明:
- 全量构建,也就是重新开始编译整个工程的 debug 版;
- 代码增量构建,指的是我们修改了工程的 Java / Kotlin 代码;
- 资源增量构建,指的是我们对资源文件的修改,增加减少了图片和字符串资源等。
最后,我们会对比如上三个场景的构建时间以作为我们的量化标准。请注意,由于工程规模大小不一、开发环境各异,在实际的操作中的结果可能会与本文的结果有所不同。 或者可以直接生成profile文档查看
./gradlew clean app:assembleDebug --profile
我们使用这个命令执行full-build(从头创建)的过程,来衡量build的性能。
硬件升级
大部分的Gradle编译卡和慢,都是因为电脑配置的原因,个人感觉关键基本配置如下
CPU:i5系列就够了
内存:重要!至少要8G,有条件建议上16G
硬盘:重要!必须是SSD固态硬盘
IDE 配置升级
IDE 内存
电脑配置差不多了,接下来就要调整下AS的可使用内存,打开AS在右下角可以看到使用内存和分配内存,如图所示
如何修改?
1.AS设置中
2.还可以通过 Android Studio 的 Help 菜单访问下面这两个配置文件:
studio.vmoptions:自定义 Studio 的 Java 虚拟机 (JVM) 选项,例如堆大小和缓存大小。请注意,在 Linux 计算机上,此文件可能会命名为 :studio64.vmoptions,具体取决于您的 Android Studio 版本。idea.properties:自定义 Android Studio 的属性,例如插件文件夹路径或支持的文件大小上限。
参考官方文档 developer.android.com/studio/intr…
# custom Android Studio VM options, see https://developer.android.com/studio/intro/studio-config.html
#JVM启动的起始堆内存,堆内存是分配给对象的内存
-Xms256m
#Java虚拟机启动时的参数,用于限制最大堆内存
-Xmx1280m
-XX:ReservedCodeCacheSize=240m //JIT java compiler在compile的时候的最大代码缓存
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-Djna.nosys=true
-Djna.boot.library.path=
修改完后保存,然后回到AS中,依次点击菜单栏:「File」->「Ivalidate Caches/Restart...」,点击后出现如图对话框,点击「Ivalidate Caches/Restart...」。
增加Gradle的堆大小并启用dex-in-process
利用 Dex In Process 使 Android Studio 编译更快 Dex-in-process允许你dex和gradle构建在同一个进程进行,以此加快增量构建和干净构建的速度。 要使用dex-in-process非常简单:确保 grale 进程有足够的内存分配给 dex 步骤来完成整个操作。 建议的公式
Gradle memory >= Dex memory + 1Gb
就是分配给gradle的内存比dex的内存多1g,dex进程内存设置在开发module的 build.gradle里面控制。添加如下内容
dexOptions {
//最大堆内存
javaMaxHeapSize "4g"
//使用增量模式构建
incremental true //最好屏蔽 incremental true,在编译的时候可能会有一个警告信息WARNING: DSL element 'DexOptions.incremental' is obsolete and will be removed at the end of 2018.因为在 AS 3.0这个已经是默认实现了.
//是否支持大工程模式
jumboMode = true
//预编译
preDexLibraries = true
//线程数
threadCount = 8
}
除此之外,dexOptions中还可以添加其他配置 如上 配置完dex,接着要配置gradle,打开「gradle.properties」修改org.gradle.jvmargs 的大小,按照公式,多1g,所以这里配置成5g:
org.gradle.jvmargs=-Xmx5g
除此之外设置下JVM最大允许分配的非堆内存,以及堆内存移除时输出堆的内存快照。
org.gradle.jvmargs=-Xmx5g -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
一般dex设置为1-2G,gradle设置为2-4G就可以了,分配更多的内存并不会使构建加快多少
注:默认的构建环境里,Android 会给 Gradle 分配 1.5G 的内存,但这个并非适用于所有的项目,您需要通过对这个数字对调优来得到适合您工程的最佳 Gradle 内存分配。 与此同时,从 Android Gradle 插件 2.1 版本之后,dex 已经默认在进程里了,所以如果您之前设定过 javaMaxHeapSize 值,可以选择删掉它了。
构建速度衡量标准
其他gradle.properties配置
除了配置给gradle分配的内存外,还可以通过添加下述配置使得构建加快:
#开启守护线程
org.gradle.daemon=true
#开启并行编译任务
org.gradle.parallel=true
#启用新的孵化模式
org.gradle.configureondemand=true
#开启 Gradle 缓存
#gradle3.5版本之后,推出了新的cache机制,和Android studio2.3版本引入的BuildCache不一样,
#这个新的cache机制会缓存每个任务的输出,而build cache只会pre-dexed的外部库。
org.gradle.caching = true
#使用构建缓存加快整洁构建的速度
# https://developer.android.com/studio/build/build-cache.html
android.enableBuildCache=true
另外,你还可以在下述目录中创建一个gradle.properties文件,全局生效,就不用每个项目都另外配置了
- /home//.gradle/ (Linux)
- /Users//.gradle/ (Mac)
- C:\Users.gradle (Windows)
配置依赖包下载地址
就是使用阿国内阿里云的依赖下载地址替换Google依赖包下载地址,打开Project级别的build.gradle文件,添加阿里云的地址
repositories {
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'http://maven.aliyun.com/nexus/content/repositories/jcenter' }
...
}
allprojects {
repositories {
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'http://maven.aliyun.com/nexus/content/repositories/jcenter' }
...
}
}
升级Android Gradle Plugin 至3.0.0/3.3.0以上
每次 Android Gradle 插件的更新都会修复大量的 bug 及提升性能等新特性,因此保持最新的 Android Gradle 插件版本有非常大的必要。 因此有更高的稳定版本,我们应该是本着是能上则上的原则。 eg:
- AndroidStudio3.0以后,能使用implementation依赖。
- AndroidStudio3.0里有D8 dex编译引擎,而这一引擎将在AndroidStudio 3.1里成为默认的引擎。
- Adroid Studio 3.3 中可以使用R8的下一代代码
shrinker。它将减少无用的代码和资源,并缩小您的源代码。因此 Android Studio 声称构建时间和 APK 大小会更小。 - Android Studio 3.5 版开始,可以使用 Apply Changes 功能来提高构建性能。
- ...
使用新的依赖和配置
| 新的配置
| New Configuration | 弃用配置 Deprecated configuration | 行为 Behavior |
|---|---|---|
| implementation 执行 | compile 编译 | 当你的模块配置一个implementation 依赖项时,让Gradle知道这个模块在编译的时候不需要把依赖项泄露给其他模块。也就是说,只有在运行时,依赖才可用于其他模块。使用这种依赖配置代替 api或compile可以导致显着的构建时间改进,因为它减少了构建系统需要重新编译的项目的数量。例如,如果一个 implementation依赖关系改变了它的API,Gradle只重新编译这个依赖项以及直接依赖它的模块。大多数应用程序和测试模块应使用此配置。 |
| api 美国石油学会 | compile 编译 | 当一个模块包含api依赖关系时,让Gradle知道模块想要将该依赖关系可传递地导出到其他模块,以便在运行时和编译时使用它们。这个配置的行为就像compile(现在已经被弃用了),你通常应该只在库模块中使用它。这是因为,如果api依赖项更改了外部API,Gradle会在编译时重新编译所有有权访问该依赖项的模块。所以,拥有大量的api依赖会增加构建时间。除非你想将一个依赖的API暴露给一个单独的测试模块,否则使用implementation 依赖。 |
也就是说在AndroidStudio3.0以后,能使用implementation的就使用implementation,implementation可以提高编译速度
开启D8编译
相比之前的DX编译引擎,谷歌的下一代编译引擎D8 dex编译引擎不仅仅提高了编译效率(体现在编译时间减少上),同时也减少了生成的.dex文件的大小。目前D8 dex编译引擎已经在android studio 3.0 里有,而这一引擎将在android studio 3.1里成为默认的引擎。可以在gradle.properties 文件里的android.enableD8设置成true即可使用D8 dex编译引擎。 使用 Java 8 语言特性会导致需要执行去语法糖操作,这将影响构建时间。然而,我们已经用 D8 降低了去语法糖操作的影响。
android.enableD8 = true
离线编译
新版AS中设置已经去掉了,在下图的地方配置打开AS的Gradle的offline work选项
或者使用命令行
gradlew build --offline
AS设置菜单中 Gradle配置
模块build.gradle
使用静态依赖项版本
在引用依赖库的时候,尽量避免使用+这种动态版本号,而尽量使用静态编码编码版本号, 如果用动态版本号,每次编译的时候Gradle会去检查是否有更新,比如
# 动态版本号
com.android.tools.build:gradle:3.2.+
# 静态版本号
com.android.tools.build:gradle:3.2.1
禁用动态BuildConfig
eg项目中有个变量是每次打包都需要实时计算的
buildConfigField "long", "BUILD_TIMESTAMP", getTimeStamp() + "L"
我们就需要通过手段,在开发阶段这个值就写死就好了 比如
project.hasProperty("devBuild")?"000000000":getTimeStamp() + "L"
我们可以通过使用命令行加参数的方式来按需执行,当然还有其他很多方式,这里只举一个例子
devBuild这个project属性需要我们在build的时候传进去 我们可以通过命令行输入或者AS中一次性配置好
./gradlew clean app:assembleDevelopmentDebug -PdevBuild --profile
或
禁用其他动态参数 包括但不限于 versionCode/apk-name等等
设置同上,通过参数控制
禁用耗时但在Debug时不需要的Task
如何查看耗时任务呢
- 使用开头的那个命令行,最后会在本地生成profile文件,直接浏览器打开即可查看
通过开头的那个命令 我们查看输出的profile文件中看到这项耗时高达半分钟,经调查,这是听云的插件,我们debug阶段并不需要,因此可以去掉
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
if (!project.hasProperty("devBuild")) {
apply plugin: 'newlens'
}
- 使用build scan(构建审视)
Gradle 官方推出的一个可视化诊断工具,官网地址:scans.gradle.com
gradlew build --scan
编译完会问你是否Push到gradle.com,键入: yes即可。
打开链接,输入接受邮箱,然后打开收到的链接
在timeline中我们能直观的看到各个任务花费的时间长短
禁用Lint Test
- 临时禁用
在 gradle 里有这样的指令 -x lint 可以临时禁掉 lint 任务,-x test 可以禁掉 test 任务,事实上对于一个稍微大一点的项目,lint 也是很耗时的
gradle assembleDebug -x lint -x lintVitalRelease -x test
- 永久禁用
当然也可以通过 gradle 脚本彻底禁用 lint 和 test 任务,但是不太建议这么做,因为有时候 lint 和 test 也是挺有用的
// 方式一 注意放置的位置
tasks.whenTaskAdded { task ->
if (task.name.contains("lint") || task.name.contains("lintVitalRelease")) {
task.enabled = false
}
}
apply plugin: 'com.android.application' // 在模块级别的build.gradle中这行之前
// 方式二
app/build.gradle
android{
...
buildTypes{
...
debug{
...
project.gradle.startParameter.excludedTaskNames.add('lint') // 屏蔽lint耗时检查
}
}
...
}
禁用aapt检查
aapt 全称为 Android Asset Packaging Tool,即为Android资源打包工具。可以通过其来进行配置打包的细节。 详解 aaptOptions——安卓gradle AaptOptions
android{
aaptOptions{
cruncherEnabled = false
}
}
模块build.gradle
禁用多APK构建
禁用Multiple APK构建
默认情况下,Android Studio 将会生成一个包含所有屏幕密度的通用 APK。multi-apk 是为了根据配置生成不同的APK,以达到减少APK体积大小的问题。但是这个配置没有必要在开发的时候开启。
在此技能中,你能专门排除或包含你想要在app/build.gradle支持的屏幕密度,Android Studio 将会为你生成多个 APK。
android {
splits {
density {
enable true
// Specify a list of screen densities which Gradle won't create multiple APKs for
exclude 'ldpi', 'mdpi'
// Specify a list of compatible screen size for the manifest
compatibleScreens 'small', 'normal', 'large', 'xlarge'
}
}
}
为ABI构建多个APK
这个技巧和前一个技巧相似,但是此技巧是用于支持Application Binary Interfaces(ABIs)。目前 Android 市场中有7个 CPU 框架,其中3个很难找到(mips,mips64,armeabi),以此你可以在app/build.gradle指定你想要支持的 ABI,Android Studio 将会为你生成多个 APK。
android {
splits {
abi {
enable true
reset()
// Specify a list of ABIs that Gradle should create APKs for
include 'x86', 'x86_64', 'arm64-v8a', 'armeabi-v7a'
// If you don’t want to generate a universal APK that includes all ABIs.
universalApk false
}
}
}
以上2个我们如果用到,在开发阶段可以关闭以增加构建速度
buildTypes {
...
debug {
splits.abi.enable = false
splits.density.enable = false
}
}
避免Legacy Multidex
这个小技巧大家应该比较熟悉——避免激活旧版的 MultiDex。当您的应用配置方法数超过 64K 的时候,您需要启用 multidex。当您启用了 multidex,且工程的最低 API 级别在 21 之前时,旧版的 multidex 就会被激活,这将严重拖慢您的构建速度,原因是 21 之前的 API 级别并没有原生的支持 multidex。 如果您是通过 Android Studio 的运行/调试按钮来执行构建,那么无需考虑这个问题,新版本的 Android Studio 会自动检测连接的设备和模拟器,如果系统的 API 级别大于 21 则进行原生的 multidex 支持,同时会忽略工程里对最低 API 级别 (minSdkVersion) 的设置。 在api21以上,因为Android采用了新的运行时ART,会在安装的时候将所有的classesN.dex合并成一个.oat包。你需要做的就是在编译脚本中加一行 multiDexEnabled true。但是在api21以下,你需要引入multidex support library(因为21 之前的 API 级别并没有原生的支持 MultiDex). 而且在编译过程中,编译脚本要话很长时间决定哪些class要放入primary dex中,哪些放入secondary dex中。在api21以下,这叫做legacy multidex,这一步会严重拖慢构建速度。 开发中,如果你不需要兼容低版本的设备的话,可以把minSdkVersion改为21以上(Android 5.0),使用ART,在Build时只做class to dex, 而不做mergeing dex,这样就可以避免legacy multidex带来的编译性能消耗,同样会节省一点的时间。 习惯通过命令行窗口构建工程的开发者们则需要试着避免这个问题: 配置一个新的 productFlavor,设定工程的最低 API 级别为 21 或者以上,在命令行里调用 assembleDevelopmentDebug 即可避免这个问题。 eg:Development
flavorDimensions "default"
productFlavors {
development {
dimension "default"
minSdkVersion 21
}
}
最小化使用资源文件
flavorDimensions "default"
productFlavors {
development {
dimension "default"
resConfigs("zh-rCN","xxhdpi")
}
}
禁用 PNG 压缩
有效减少图片文件大小,不必执行构建时压缩,从而加快构建速度,如果你的APP用到大量图片资源的话,效果明显。可以通过将图片转换为WebP,如果不想转换,可以在开发的时候禁止png cruncher
aapt会对你的png做处理,让他们的size变得更小,这一步叫做png-cruncher,减小apk的体积。但是在开发模式下,这些并不重要,因此在开发模式下我们可以禁止png cruncher
buildTypes {
debug {
crunchPngs false //default
}
}
按需编译
直接**gradlew build和执行gradlew assemble** 会同时编译生成Debug和Release的包,在调试阶段其实 我们可以使用:
gradlew assembleDebug
来只编译Debug版本的包,除此之外还可以用另一个命令编译完直接安装到设备上:
gradlew installDebug
开启多线程支持和增量编译
在gradle.properties文件中增加如下所示代码: 表示开启Gradle的多线程和多核心支持.
#开启守护线程
org.gradle.daemon=true
#开启并行编译任务
org.gradle.parallel=true
#启用新的孵化模式
org.gradle.configureondemand=true
在app/build.gradle **android{}**闭包中添加如下代码:表示开启Gradle的增量编译,增加编译的内存资源到4g
dexOptions{
javaMaxHeapSize "4g"
}
使用 Apply Changes
从 Android Studio 3.5 版开始,开发者们可以使用 Apply Changes 功能来提高构建性能,它可以让代码和资源的改动直接生效而无需重启应用,有时候甚至无需重启当前的 Activity。与 Instant Run 的实现方式不一样,Apply Changes 充分利用了 Android 8.0 以上版本操作系统的特性进行运行时检测,从而动态的对类进行重新定义。因此,如果您希望使用 Apply Changes,则需要让您的工程运行在 Android 8.0 (API级别26) 以上的真机或者模拟器上。
一些其他的小点
减少本地库依赖
Gadle在编译时会执行大量Task,生成很多中间文件,会导致磁盘IO拥堵,造成编译变慢, 可以减少本地库依赖,多使用aar进行依赖。 组件化之后可以实行
组件化
对于大型的项目,可能上面这些优化建议有一定的效果,但是构建速度还是有些慢,那么就可以考虑组建化了,将项目拆分成一个个单独的组件,开发环境每个module 都是一个APK,发布的时候,每个module都是一个lib 给主工程使用。篇幅有效,这里就不再详细介绍组件化,现在组件化是一个趋势,如果有精力或者有实力,组件化是一个很不错的选择。