一. 背景
假設(shè)我們有這樣一個(gè)需求:
我們想要知道哪些task是耗時(shí)較多的, 這樣就可以針對這些task進(jìn)行優(yōu)化, 以此來節(jié)省構(gòu)建時(shí)間!
構(gòu)建, 對于一個(gè)開發(fā)者來說, 是一個(gè)痛苦的等待過程, 相信開發(fā)者都深有體會.
當(dāng)然對于安卓開發(fā)者來說, 已經(jīng)有不少非常優(yōu)秀的加速構(gòu)建過程的工具了, 如: Instant Run, Freeline 等.
但是對于其他使用gradle作為構(gòu)建工具的項(xiàng)目, 可能會缺少這樣的工具, 因此會有自行優(yōu)化構(gòu)建過程的必要. 至于如后優(yōu)化, 就留給讀者發(fā)揮自己的智慧了. 下面主要講講如何收集構(gòu)建過程的各個(gè)步驟所花費(fèi)的時(shí)間.
二. 解決思路
1. 收集各個(gè)task所花費(fèi)的時(shí)間
- 此過程主要用到兩個(gè)gradle的關(guān)鍵接口以及一個(gè)方法:
-
org.gradle.api.execution.TaskExecutionListener
此接口定義了每個(gè)task執(zhí)行前后的回調(diào):beforeExecute()和afterExecute() -
org.gradle.BuildListener
此接口主要定義了構(gòu)建開始和構(gòu)建完成的回調(diào) (當(dāng)然還有一些其他的回: 調(diào)配置完成, 所有項(xiàng)目加載完成等):buildStarted()和buildFinished() -
org.gradle.api.Project對象的gradle屬性的addListener()方法- 每個(gè)項(xiàng)目(父項(xiàng)目和子項(xiàng)目)都有自己的配置, 一般是用項(xiàng)目根目錄下的build.gradle腳本來進(jìn)行配置. 每個(gè)項(xiàng)目都會創(chuàng)建一個(gè)
org.gradle.api.Project對象來代表該項(xiàng)目.Project對象中的gradle屬性代表的是build.gradle腳本.gradle屬性的類型是org.gradle.api.invocation.Gradle. - 值得一提的是
org.gradle.api.invocation.Gradle類的addListener()是一個(gè)比較特殊的方法, 它的參數(shù)是Object類型, 此方法的原型如下:
可以看到, 其可以添加的類型非常多. 上面我們使用的就是其中的兩個(gè)./** * 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); - 每個(gè)項(xiàng)目(父項(xiàng)目和子項(xiàng)目)都有自己的配置, 一般是用項(xiàng)目根目錄下的build.gradle腳本來進(jìn)行配置. 每個(gè)項(xiàng)目都會創(chuàng)建一個(gè)
-
2. 上傳上一步收集的數(shù)據(jù) (用于統(tǒng)計(jì)分析)
- 使用groovy的擴(kuò)展庫http-builder或http-builder-ng(最新版本的http-builder)做統(tǒng)計(jì)數(shù)據(jù)的上傳工作.
三. 具體實(shí)踐
1. 在父項(xiàng)目的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. 在父項(xiàng)目的build.gradle文件頂部導(dǎo)入http-builder-ng相關(guān)的類 (上傳數(shù)據(jù)時(shí)用到)
如下:
import groovyx.net.http.HttpBuilder
如果是http-builder, 導(dǎo)入語句如下:
import groovyx.net.http.HTTPBuilder
import static groovyx.net.http.ContentType.*
3. 在父項(xiàng)目的build.gradle內(nèi)容底部, 自定義監(jiān)聽CollectTaskTimeListener, 如下:
class CollectTaskTimeListener implements TaskExecutionListener, BuildListener {
private Clock clock //用于記錄每個(gè)task執(zhí)行所花的時(shí)間
private Clock start = new Clock() //用于記錄所有task執(zhí)行所花的時(shí)間
private def timings = new HashMap<String, Long>() //存儲所有task和其所發(fā)時(shí)間的對應(yīng)關(guān)系
private def final MIN_COST = 5 //展示統(tǒng)計(jì)數(shù)據(jù)的下限 (小于此值時(shí)不輸出統(tǒng)計(jì)數(shù)據(jù))
//每個(gè)task執(zhí)行之前調(diào)用
@Override
void beforeExecute(Task task) {
clock = new Clock()
}
//每個(gè)task執(zhí)行后調(diào)用
@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結(jié)束時(shí)調(diào)用 (所有task結(jié)束時(shí)調(diào)用)
@Override
void buildFinished(BuildResult result) {
//輸出統(tǒng)計(jì)數(shù)據(jù)
outputHeader("Task timings(no sort): ")
outputProfile(timings.iterator())
//輸出排序后的統(tǒng)計(jì)數(shù)據(jù)
outputHeader("Task timings(sorted): ")
outputProfile(sortProfileData(timings).iterator())
println("\n")
uploadReport()
}
void outputHeader(String headerMessage) {
println("\n======================================================")
println(headerMessage)
}
//輸出收集的數(shù)據(jù)
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所花費(fèi)的時(shí)間進(jìn)行排序
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
}
//將收集的數(shù)據(jù)上傳到服務(wù)器做分析
//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()
}
}*/
//將收集的數(shù)據(jù)上傳到服務(wù)器做分析
//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 (服務(wù)端相應(yīng)類型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的最后面, 將自定義的監(jiān)聽添加到gradle對象中, 如下:
//添加自定義的監(jiān)聽
gradle.addListener(new CollectTaskTimeListener())
完整build.gradle文件內(nèi)容如下:
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 //用于記錄每個(gè)task執(zhí)行所花的時(shí)間
private Clock start = new Clock() //用于記錄所有task執(zhí)行所花的時(shí)間
private def timings = new HashMap<String, Long>() //存儲所有task和其所發(fā)時(shí)間的對應(yīng)關(guān)系
private def final MIN_COST = 5 //展示統(tǒng)計(jì)數(shù)據(jù)的下限 (小于此值時(shí)不輸出統(tǒng)計(jì)數(shù)據(jù))
//每個(gè)task執(zhí)行之前調(diào)用
@Override
void beforeExecute(Task task) {
clock = new Clock()
}
//每個(gè)task執(zhí)行后調(diào)用
@Override
void afterExecute(Task task, TaskState state) {
long ms = clock.timeInMs
timings.put(task.path, ms)
//輸出當(dāng)前task的執(zhí)行時(shí)間
task.project.logger.warn "${task.path} took ${ms}ms"
}
//build結(jié)束時(shí)調(diào)用 (所有task結(jié)束時(shí)調(diào)用)
@Override
void buildFinished(BuildResult result) {
//輸出統(tǒng)計(jì)數(shù)據(jù)
outputHeader("Task timings(no sort): ")
outputProfile(timings.iterator())
//輸出排序后的統(tǒng)計(jì)數(shù)據(jù)
outputHeader("Task timings(sorted): ")
outputProfile(sortProfileData(timings).iterator())
println("\n")
uploadReport()
}
void outputHeader(String headerMessage) {
println("\n======================================================")
println(headerMessage)
}
//輸出收集的數(shù)據(jù)
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所花費(fèi)的時(shí)間進(jìn)行排序
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
}
//將收集的數(shù)據(jù)上傳到服務(wù)器做分析 (http-builder數(shù)據(jù)上傳代碼)
/*
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()
}
}*/
//將收集的數(shù)據(jù)上傳到服務(wù)器做分析 (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 (服務(wù)端相應(yīng)類型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) {}
}
//添加自定義的監(jiān)聽
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/