android编译加速

3,569 阅读6分钟

[toc]

一 前言

编译速度优化是大型项目中提升研发效率的重要环节,大部分人都经历过改动一行代码,需等待30秒以上来重新查看运行效果的尴尬局面,本文将从问题认知角度来尝试展开。

要优化编译速度,得知道这么几个最重要的事:

  • 编译流程是什么样的?从点击as的run到代码重新运行经历了哪些流程?
  • 编译耗时输出,如何打印流程中的耗时情况?
  • 单个耗时任务中包含了什么?怎么优化?优化效果如何?

二.编译流程分析

google官方提供的编译流程说明图如下: image

江湖上还流传着二副更加完整的编译流程图: image

image

从流程上看,除了官方给的比较笼统外,另外两副流程基本一致,只是详略不同。

  • aapt 打包资源文件,产生R.java ,resource.arsc , res,manifest.xml
  • aidl 产生java接口文件
  • javac 编译R.java,Aidl产生的java文件以及工程中用到的源码产生.class文件
  • R8混淆和代码压缩.class文件,经过d8 生成.dex文件
  • apk builder 将资源包,.dex打包生成apk文件
  • apksigner /jarSigner 会对apk进行签名

三.编译耗时输出

3.1 命令行查看编译耗时

./gradlew clean app:assembleDebug --profile

# 输出结果如下

Description	Duration
Total Build Time	2m13.22s
Startup	2.711s
Settings and buildSrc	0.794s
Loading Projects	0.090s
Configuring Projects	8.249s
Artifact Transforms	27.957s
Task Execution	2m13.36s

如果想看更加详细的分析说明,可以增加--scan 属性 它会自动发送到scans.gradle.com网址下

./gradlew clean app:assembleDebug --scan

3.2 android studio 查看编译耗时

  • 从菜单栏中依次点击 Build > Build Bundle(s) / APK(s) > Build Bundle(s) 或 Build > Build Bundle(s) / APK(s) > Build APK(s)

  • 从菜单栏中依次选择 View > Tool Windows > Build,打开 Build 窗口

  • 等构建结束之后,查看 Build Analyzer标签

image

四. 编译流程分析

整体上看,编译流程满足gradle的三大流程

graph LR
init-->configure
configure-->execute

image

占比来看,

  • init包含 Startup,setting ,loading 耗时3.6s
  • configure 耗时 8.2s
  • 各种transforms耗时 27.85s
  • 任务执行耗时总耗时2分13s ,这个包含了前面三个耗时。

五.耗时优化分析

从上面的输出可以看出,任务执行耗时最长,我们切换到timeline视图,选择按时长排序,就可以筛选出来执行任务的耗时排序。 image

5.1 maven私有仓库镜像

建立私有仓库镜像常用的mavencenter,jcenter,google和自己公司的maven,减少多次请求查询网络时间。对于首次编译有效,实际优化效果,视情况而定。通过分析发现私服仓库包含了以下四个仓库,因此可以去掉。

//        mavenCentral()
//        google()
//        jcenter()
//        maven { url "https://jitpack.io" }

5.2 模块化处理

将依赖的module改为aar,整体耗时从2分18秒,降低到1分。提升130%,这里比较麻烦的在于各个模块抽象之后的依赖关系梳理。

# 改造前
implementation project(':umeng')
implementation project(':user')
implementation project(':oss')
implementation project(':feedbacks')
implementation project(':videoedit')
implementation project(':ox')
implementation project(':ark')
implementation project(':hyvideo')

# 改造后
implementation 'com.hch:ark:2.0.2'
implementation 'com.hch:ox:2.0.1'
implementation 'com.hch:hyvideo:2.0.1'
implementation 'com.hch:umeng:2.0.0'
implementation 'com.hch:user:2.0.1'
implementation 'com.hch:oss:2.0.1'
implementation 'com.hch:feedbacks:2.0.1'
implementation 'com.hch:videoedit:2.0.0'
  • 优化前 image
  • 优化后 image

5.3 冗余模块移除

分析依赖关系发现dlna,webserver两个模块经过迭代之后,仅剩下少量几个工具类被引用,而实际包含了大量的冗余代码,提取工具类后,移除了两个模块。

收益3s左右

5.4 开启全局缓存

在gradle.properties中添加全局缓存,这个缓存可以跨机器生效,并且在执行clean任务的情况下依旧会生效。提升效果非常明显 从42s下降到20s左右。

org.gradle.caching=true

它与增量编译的优先级是,增量优先 ,其次是全局缓存

> Task :app:dexBuilderDebug UP-TO-DATE
> Task :app:mergeExtDexDebug UP-TO-DATE
> Task :app:mergeLibDexDebug UP-TO-DATE

UP-TO-DATE表示模块没有变化,我们通过Build-clean之后再次运行

> Task :app:dexBuilderDebug FROM-CACHE
> Task :app:mergeExtDexDebug FROM-CACHE
> Task :app:mergeLibDexDebug FROM-CACHE

FROM-CACHE表示是从缓存中读取编译结果。

这部分提升,如果是没有修改的情况下,再次运行只需要5秒,有修改内容时,clean后编译大概是40ms左右。

关于cache的说明:

  • cache有效期最新一次编译后的7天内
  • 本地cache目录$GRADLE_USER_HOME/caches/build-cache-1
  • 使用cachec会有校验与打包的过程,总体来说比直接编译速度要快。
  • 修改代码会导致最慢的流程dexBuilder和mergeDex缓存失效

5.5 升级AGP版本

AGP版本在迭代过程中一直有提升构建的性能,因此在可能的情况下尽量使用新的AGP版本。

5.6 并行编译

org.gradle.parallel=true

由于我们的module都拆解成了aar,因此实际上并行编译的优化效果并不明显。它比较适合在有多个互相不依赖的模块中提升编译速度。例如:部分模块是aar,另外部分是源码依赖的形式。

5.7 开启守护进程

org.gradle.configureondemand=true

当gradle进程没有启动时,会节约大概4秒时间

5.8 加大缓存

org.gradle.jvmargs=-Xmx2048m -XX:+UseParallelGC

带来收益较小,主要是影响垃圾回收的时间,平均提升大概3s。

5.9 png优化处理

这部分对dexBuilder任务的构建时间优化比较明显,减小图片体积,并且提升编译速度。

  • 将png改为webp格式
  • 禁用png的优化
buildTypes {
    debug {
        crunchPngs  false
    }
    release {
        crunchPngs  false
    }
}

实际效果,编译速度在本项目中提升4S左右

5.9 transform优化

transform最大的问题在于它是串行处理的,每个transform的输出会成为下一个transform的输入,因此这里重点的优化手段都是尽可能的将多个transform的处理逻辑合并,减少全量扫描的次数。基本优化思路包括:

  • 合并transform,尽量统一处理
  • 减少多种transform库的引入,统一使用一种。

六.总结

  • 在可能的情况下尽量升级使用新的AGP.
  • 采用私有仓库镜像能大幅降低首次拉取网络资源的时间
  • 模块化转aar依赖,能大幅提升构建效率,项目中提升120%以上,从2分10秒降低到1分5秒
  • 开启全局缓存,能够大幅提升构建效率。项目中提升40%以上,从1分5秒降低到40s,在无修改clean情况下只需17s
  • png转webp,禁用png优化,降低耗时4s。
  • 冗余模块移除提升幅度与模块大小有关,项目中降低3s耗时
  • 增加vm内存效果不明显
  • 并行编译效果不明显
  • 开启守护进程,首次启动降低4秒耗时

整体来看收益 模块化 > 全局缓存 > png优化 > 其他配置 最终的配置如下:

# build opt
org.gradle.caching=true
org.gradle.parallel=true
# 开启守护进程
org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx2048m -XX:+UseParallelGC

# 在app级的.gradle文件中添加
buildTypes {
    debug {
        crunchPngs  false
    }
    release {
        crunchPngs  false
    }
}