一. 背景
假设我们有这样一个需求:
我们想要知道哪些task是耗时较多的, 这样就可以针对这些task进行优化, 以此来节省构建时间!
构建, 对于一个开发者来说, 是一个痛苦的等待过程, 相信开发者都深有体会.
当然对于安卓开发者来说, 已经有不少非常优秀的加速构建过程的工具了, 如: Instant Run, Freeline 等.
但是对于其他使用gradle作为构建工具的项目, 可能会缺少这样的工具, 因此会有自行优化构建过程的必要. 至于如后优化, 就留给读者发挥自己的智慧了. 下面主要讲讲如何收集构建过程的各个步骤所花费的时间.
二. 解决思路
1. 收集各个task所花费的时间
- 此过程主要用到两个gradle的关键接口以及一个方法:
-
org.gradle.api.execution.TaskExecutionListener
此接口定义了每个task执行前后的回调:beforeExecute()
和afterExecute()
-
org.gradle.BuildListener
此接口主要定义了构建开始和构建完成的回调 (当然还有一些其他的回: 调配置完成, 所有项目加载完成等):buildStarted()
和buildFinished()
-
org.gradle.api.Project
对象的gradle
属性的addListener()
方法- 每个项目(父项目和子项目)都有自己的配置, 一般是用项目根目录下的build.gradle脚本来进行配置. 每个项目都会创建一个
org.gradle.api.Project
对象来代表该项目.Project
对象中的gradle
属性代表的是build.gradle
脚本.gradle
属性的类型是org.gradle.api.invocation.Gradle
. - 值得一提的是
org.gradle.api.invocation.Gradle
类的addListener()
是一个比较特殊的方法, 它的参数是Object
类型, 此方法的原型如下:
可以看到, 其可以添加的类型非常多. 上面我们使用的就是其中的两个./** * Adds the given listener to this build. The listener may implement any of the given listener interfaces: * *
-
*
- {@link org.gradle.BuildListener} *
- {@link org.gradle.api.execution.TaskExecutionGraphListener} *
- {@link org.gradle.api.ProjectEvaluationListener} *
- {@link org.gradle.api.execution.TaskExecutionListener} *
- {@link org.gradle.api.execution.TaskActionListener} *
- {@link org.gradle.api.logging.StandardOutputListener} *
- {@link org.gradle.api.tasks.testing.TestListener} *
- {@link org.gradle.api.tasks.testing.TestOutputListener} *
- {@link org.gradle.api.artifacts.DependencyResolutionListener} *
- 每个项目(父项目和子项目)都有自己的配置, 一般是用项目根目录下的build.gradle脚本来进行配置. 每个项目都会创建一个
-
2. 上传上一步收集的数据 (用于统计分析)
- 使用groovy的扩展库http-builder或http-builder-ng(最新版本的http-builder)做统计数据的上传工作.
三. 具体实践
1. 在父项目的build.gradle
文件中将http-builder-ng
库加入到classpath中 (脚本文件中用的的类库必须添加到classpath中), 如下:
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
//添加http-builder-ng的依赖
classpath 'io.github.http-builder-ng:http-builder-ng-core:1.0.3'
}
}
如果用http-builder, classpath处改成:
classpath "org.codehaus.groovy.modules.http-builder:http-builder:0.7.2"
2. 在父项目的build.gradle文件顶部导入http-builder-ng
相关的类 (上传数据时用到)
如下:
import groovyx.net.http.HttpBuilder
如果是http-builder, 导入语句如下:
import groovyx.net.http.HTTPBuilder
import static groovyx.net.http.ContentType.*
3. 在父项目的build.gradle内容底部, 自定义监听CollectTaskTimeListener
, 如下:
class CollectTaskTimeListener implements TaskExecutionListener, BuildListener {
private Clock clock //用于记录每个task执行所花的时间
private Clock start = new Clock() //用于记录所有task执行所花的时间
private def timings = new HashMap() //存储所有task和其所发时间的对应关系
private def final MIN_COST = 5 //展示统计数据的下限 (小于此值时不输出统计数据)
//每个task执行之前调用
@Override
void beforeExecute(Task task) {
clock = new Clock()
}
//每个task执行后调用
@Override
void afterExecute(Task task, TaskState state) {
long ms = clock.timeInMs
timings.put(task.path, ms)
task.project.logger.warn "${task.path} took ${ms}ms"
}
//build结束时调用 (所有task结束时调用)
@Override
void buildFinished(BuildResult result) {
//输出统计数据
outputHeader("Task timings(no sort): ")
outputProfile(timings.iterator())
//输出排序后的统计数据
outputHeader("Task timings(sorted): ")
outputProfile(sortProfileData(timings).iterator())
println("\n")
uploadReport()
}
void outputHeader(String headerMessage) {
println("\n======================================================")
println(headerMessage)
}
//输出收集的数据
void outputProfile(Iterator> it) {
for (entry in it) {
if (entry.value >= MIN_COST) {
printf("%-50s %-15s\n", entry.key, entry.value + "ms")
}
}
}
//对task所花费的时间进行排序
List
4. 在上一步定义的CollectTaskTimeListener
的最后面, 将自定义的监听添加到gradle
对象中, 如下:
//添加自定义的监听
gradle.addListener(new CollectTaskTimeListener())
完整build.gradle文件内容如下:
import groovyx.net.http.HttpBuilder
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'io.github.http-builder-ng:http-builder-ng-core:1.0.3'
}
}
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
class CollectTaskTimeListener implements TaskExecutionListener, BuildListener {
private Clock clock //用于记录每个task执行所花的时间
private Clock start = new Clock() //用于记录所有task执行所花的时间
private def timings = new HashMap() //存储所有task和其所发时间的对应关系
private def final MIN_COST = 5 //展示统计数据的下限 (小于此值时不输出统计数据)
//每个task执行之前调用
@Override
void beforeExecute(Task task) {
clock = new Clock()
}
//每个task执行后调用
@Override
void afterExecute(Task task, TaskState state) {
long ms = clock.timeInMs
timings.put(task.path, ms)
//输出当前task的执行时间
task.project.logger.warn "${task.path} took ${ms}ms"
}
//build结束时调用 (所有task结束时调用)
@Override
void buildFinished(BuildResult result) {
//输出统计数据
outputHeader("Task timings(no sort): ")
outputProfile(timings.iterator())
//输出排序后的统计数据
outputHeader("Task timings(sorted): ")
outputProfile(sortProfileData(timings).iterator())
println("\n")
uploadReport()
}
void outputHeader(String headerMessage) {
println("\n======================================================")
println(headerMessage)
}
//输出收集的数据
void outputProfile(Iterator> it) {
for (entry in it) {
if (entry.value >= MIN_COST) {
printf("%-50s %-15s\n", entry.key, entry.value + "ms")
}
}
}
//对task所花费的时间进行排序
List
References:
Gradle:
https://docs.gradle.org/4.3.1/userguide/userguide.html
https://docs.gradle.org/4.3.1/dsl/
https://docs.gradle.org/4.3.1/javadoc/
http-builder-ng:
https://http-builder-ng.github.io/http-builder-ng/