一. 背景
假设我们有这样一个需求:
我们想要知道哪些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: * * <ul> * <li>{@link org.gradle.BuildListener} * <li>{@link org.gradle.api.execution.TaskExecutionGraphListener} * <li>{@link org.gradle.api.ProjectEvaluationListener} * <li>{@link org.gradle.api.execution.TaskExecutionListener} * <li>{@link org.gradle.api.execution.TaskActionListener} * <li>{@link org.gradle.api.logging.StandardOutputListener} * <li>{@link org.gradle.api.tasks.testing.TestListener} * <li>{@link org.gradle.api.tasks.testing.TestOutputListener} * <li>{@link org.gradle.api.artifacts.DependencyResolutionListener} * </ul> * * @param listener The listener to add. Does nothing if this listener has already been added. */ void addListener(Object listener);
可以看到, 其可以添加的类型非常多. 上面我们使用的就是其中的两个.
- 每个项目(父项目和子项目)都有自己的配置, 一般是用项目根目录下的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<String, Long>() //存储所有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<Map.Entry<String, Long>> it) {
for (entry in it) {
if (entry.value >= MIN_COST) {
printf("%-50s %-15s\n", entry.key, entry.value + "ms")
}
}
}
//对task所花费的时间进行排序
List<Map<String, Long>> sortProfileData(Map<String, Long> profileData) {
List<Map.Entry<String, Long>> data = new ArrayList<>()
for (timing in profileData) data.add(timing)
Collections.sort(data, new Comparator<Map.Entry<String, Long>>() {
@Override
int compare(Map.Entry<String, Long> o1, Map.Entry<String, Long> o2) {
if (o1.value > o2.value) return 1
else if (o1.value < o2.value) return -1
return 0
}
})
return data
}
//将收集的数据上传到服务器做分析
//org.codehaus.groovy.modules.http-builder:http-builder:0.7.2
/*
void uploadReport() {
def http = new HTTPBuilder('http://10.249.23.63:8080', "application/json")
try {
http.post(path: '/time', body: ['timings': timings, 'user.name': System.getProperty("user.name"), "total_time": start.timeInMs],
requestContentType: JSON) { resp ->
println "POST Success: ${resp.statusLine}"
}
} catch (Exception e) {
e.printStackTrace()
}
}*/
//将收集的数据上传到服务器做分析
//io.github.http-builder-ng:http-builder-ng-core:1.0.3
void uploadReport() {
HttpBuilder.configure {
request.uri = "http://10.249.23.72:8080"
}.postAsync {
request.uri.path = '/time'
request.body = ['timings': timings, 'user.name': System.getProperty("user.name"), "total_time": start.timeInMs]
request.contentType = 'application/json'
response.success { formServer, body -> //body => groovy.json.internal.LazyMap (服务端相应类型Content-Type为application/json)
println "POST Success: ${formServer.statusCode}, ${formServer.message}, ${body.getClass()}; code=${body.get('code')}, message=${body.get('message')}"
}
response.failure { formServer, errorMessage -> //errorMessage => byte[]
println "POST Failure: ${formServer.statusCode}, ${formServer.message}, errorMessage=${new String(errorMessage)}"
}
}
}
@Override
void buildStarted(Gradle gradle) {}
@Override
void settingsEvaluated(Settings settings) {}
@Override
void projectsLoaded(Gradle gradle) {}
@Override
void projectsEvaluated(Gradle gradle) {}
}
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<String, Long>() //存储所有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<Map.Entry<String, Long>> it) {
for (entry in it) {
if (entry.value >= MIN_COST) {
printf("%-50s %-15s\n", entry.key, entry.value + "ms")
}
}
}
//对task所花费的时间进行排序
List<Map<String, Long>> sortProfileData(Map<String, Long> profileData) {
List<Map.Entry<String, Long>> data = new ArrayList<>()
for (timing in profileData) data.add(timing)
Collections.sort(data, new Comparator<Map.Entry<String, Long>>() {
@Override
int compare(Map.Entry<String, Long> o1, Map.Entry<String, Long> o2) {
if (o1.value > o2.value) return 1
else if (o1.value < o2.value) return -1
return 0
}
})
return data
}
//将收集的数据上传到服务器做分析 (http-builder数据上传代码)
/*
void uploadReport() {
def http = new HTTPBuilder('http://10.249.23.63:8080', "application/json")
try {
http.post(path: '/time', body: ['timings': timings, 'user.name': System.getProperty("user.name"), "total_time": start.timeInMs],
requestContentType: JSON) { resp ->
println "POST Success: ${resp.statusLine}"
}
} catch (Exception e) {
e.printStackTrace()
}
}*/
//将收集的数据上传到服务器做分析 (http-builder-ng)
void uploadReport() {
HttpBuilder.configure {
request.uri = "http://10.249.23.72:8080"
}.postAsync {
request.uri.path = '/time'
request.body = ['timings': timings, 'user.name': System.getProperty("user.name"), "total_time": start.timeInMs]
request.contentType = 'application/json'
response.success { formServer, body -> //body => groovy.json.internal.LazyMap (服务端相应类型Content-Type为application/json)
println "POST Success: ${formServer.statusCode}, ${formServer.message}, ${body.getClass()}; code=${body.get('code')}, message=${body.get('message')}"
}
response.failure { formServer, errorMessage -> //errorMessage => byte[]
println "POST Failure: ${formServer.statusCode}, ${formServer.message}, errorMessage=${new String(errorMessage)}"
}
}
}
@Override
void buildStarted(Gradle gradle) {}
@Override
void settingsEvaluated(Settings settings) {}
@Override
void projectsLoaded(Gradle gradle) {}
@Override
void projectsEvaluated(Gradle gradle) {}
}
//添加自定义的监听
gradle.addListener(new CollectTaskTimeListener())
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/