Android Gradle學習(八):統計Task執行時長

關于 Gradle 的基本知識,前面章節已經講的差不多了。那么,我們現在來牛刀小試一下,看看 Gradle 有什么用武之地。

我們在將 Android 應用程序打包成 apk 包時,有時會發現整個 build 過程特別長,短則 1、2 分鐘,長則大幾分鐘甚至更長,特別是你要進行調試時,漫長的等待會讓人很焦躁。我們在控制臺可以看到整個打包過程包含很多個 task ,那么到底是哪些 task 的執行花費了大量時間內?

Gradle 提供了很多構建生命周期鉤子函數,我們可以用 TaskExecutionListener 來監聽整個構建過程中 task 的執行:

public interface TaskExecutionListener {
    
    void beforeExecute(Task task);
    
    void afterExecute(Task task, TaskState taskState);
}

在每個 task 執行前先搜集其相關信息,記錄該 task 執行的開始時間等,在 task 執行完成后,記錄其執行結束時間,這樣就能統計出該 task 的執行時長。

接著,我們可以用 BuildListener 來監聽整個構建是否完成,在構建完成后,輸出所有執行過的 task 信息,以及每個 task 的執行時長:

public interface BuildListener {
    void buildStarted(Gradle gradle);

    void settingsEvaluated(Settings settings);

    void projectsLoaded(Gradle gradle);

    void projectsEvaluated(Gradle gradle);

    void buildFinished(BuildResult buildResult);
}

在 buildFinished 方法中,監聽構建完成以及成功與否。為了方便使用,考慮做成一個 Gradle 插件,關于插件的制作,這里不贅述了,網上有很多關于 Gradle 插件制作的教程。

不多說了,直接上代碼,核心只有一個插件類:

class BuildTimeCostPlugin implements Plugin<Project>{

    //用來記錄 task 的執行時長等信息
    Map<String, TaskExecTimeInfo> timeCostMap = new HashMap<>()
    //用來按順序記錄執行的 task 名稱
    List<String> taskPathList = new ArrayList<>()

    @Override
    void apply(Project project) {
        //監聽每個task的執行
        project.getGradle().addListener(new TaskExecutionListener() {
            @Override
            void beforeExecute(Task task) {
                //task開始執行之前搜集task的信息
                TaskExecTimeInfo timeInfo = new TaskExecTimeInfo()
                //記錄開始時間
                timeInfo.start = System.currentTimeMillis()
                timeInfo.path = task.getPath()
                timeCostMap.put(task.getPath(), timeInfo)
                taskPathList.add(task.getPath())
            }

            @Override
            void afterExecute(Task task, TaskState taskState) {
                //task執行完之后,記錄結束時的時間
                TaskExecTimeInfo timeInfo = timeCostMap.get(task.getPath())
                timeInfo.end = System.currentTimeMillis()
                //計算該 task 的執行時長
                timeInfo.total = timeInfo.end - timeInfo.start
            }
        })

        //編譯結束之后:
        project.getGradle().addBuildListener(new BuildListener() {
            @Override
            void buildStarted(Gradle gradle) {

            }

            @Override
            void settingsEvaluated(Settings settings) {

            }

            @Override
            void projectsLoaded(Gradle gradle) {

            }

            @Override
            void projectsEvaluated(Gradle gradle) {

            }

            @Override
            void buildFinished(BuildResult buildResult) {
                println "---------------------------------------"
                println "---------------------------------------"
                println "build finished, now println all task execution time:"
                //按 task 執行順序打印出執行時長信息
                for (String path : taskPathList) {
                    long t = timeCostMap.get(path).total
                    if (t >= timeCostExt.threshold) {
                        println("${path}  [${t}ms]")
                    }
                }                println "---------------------------------------"
                println "---------------------------------------"
            }
        })

    }

    //關于 task 的執行信息
    class TaskExecTimeInfo {

        long total      //task執行總時長

        String path
        long start      //task 執行開始時間
        long end        //task 結束時間

    }

}

接下來,我們創建一個測試工程,使用這個插件,執行 “assembleDebug” 這個構建任務,打一個 debug 的測試包出來,構建完成之后,可以在 Gradle Console 里看到本次構建里所有 task 的執行時長信息:

---------------------------------------
---------------------------------------
build finished, now println all task execution time:
:app:preBuild  [1ms]
:app:preDebugBuild  [72ms]
:app:compileDebugAidl  [8ms]
:app:compileDebugRenderscript  [9ms]
:app:checkDebugManifest  [2ms]
:app:generateDebugBuildConfig  [3ms]
:app:prepareLintJar  [2ms]
:app:generateDebugResValues  [1ms]
:app:generateDebugResources  [0ms]
:app:mergeDebugResources  [60ms]
:app:createDebugCompatibleScreenManifests  [2ms]
:app:processDebugManifest  [9ms]
:app:splitsDiscoveryTaskDebug  [1ms]
:app:processDebugResources  [15ms]
:app:generateDebugSources  [1ms]
:app:javaPreCompileDebug  [55ms]
:app:compileDebugJavaWithJavac  [2038ms]
:app:compileDebugNdk  [23ms]
:app:compileDebugSources  [1ms]
:app:mergeDebugShaders  [21ms]
:app:compileDebugShaders  [12ms]
:app:generateDebugAssets  [0ms]
:app:mergeDebugAssets  [55ms]
:app:transformClassesWithDexBuilderForDebug  [1216ms]
:app:transformDexArchiveWithExternalLibsDexMergerForDebug  [1871ms]
:app:transformDexArchiveWithDexMergerForDebug  [273ms]
:app:mergeDebugJniLibFolders  [10ms]
:app:transformNativeLibsWithMergeJniLibsForDebug  [451ms]
:app:transformNativeLibsWithStripDebugSymbolForDebug  [9ms]
:app:processDebugJavaRes  [10ms]
:app:transformResourcesWithMergeJavaResForDebug  [266ms]
:app:validateSigningDebug  [12ms]
:app:packageDebug  [590ms]
:app:assembleDebug  [1ms]
---------------------------------------
---------------------------------------

從上面可以看到 compileDebugJavaWithJavac 這個 task 執行時長為 2038ms,將近有2秒鐘,是這里面執行時長最長的一個。由于 Gradle 支持增量構建,再次構建的時間可能就不一樣了。

上面這個插件,能不能只打印出執行時長超過 1000ms 的任務呢?結果能不能排序后輸出呢?我們繼續做點優化,這就需要前面介紹的 Extension 相關知識了。

先創建一個 Extension 類,代碼如下:

class BuildTimeCostExtension {

    //task執行時間超過該值才會統計
    int threshold

    //是否按照task執行時長進行排序,true-表示從大到小進行排序,false-表示不排序
    boolean sorted

    void threshold(int threshold) {
        this.threshold = threshold
    }

    void sorted(boolean sorted) {
        this.sorted = sorted
    }

}

修改插件類,在插件里創建自定義 Extension:

@Override
void apply(Project project) {
    //創建一個 Extension,配置輸出結果
    final BuildTimeCostExtension timeCostExt = project.getExtensions().create("taskExecTime", BuildTimeCostExtension)
    
    ......
    ......
}

修改 task 執行時長輸出結果的代碼,根據配置來輸出不同的結果:

@Override
void buildFinished(BuildResult buildResult) {
    println "---------------------------------------"
    println "---------------------------------------"
    println "build finished, now println all task execution time:"
    if (timeCostExt.sorted) {
        //進行排序
        List<TaskExecTimeInfo> list = new ArrayList<>()
        for (Map.Entry<String, TaskExecTimeInfo> entry : timeCostMap) {
            list.add(entry.value)
        }
        Collections.sort(list, new Comparator<TaskExecTimeInfo>() {
            @Override
            int compare(TaskExecTimeInfo t1, TaskExecTimeInfo t2) {
                return t2.total - t1.total
            }
        })
        for (TaskExecTimeInfo timeInfo : list) {
            long t = timeInfo.total
            if (t >= timeCostExt.threshold) {
                println("${timeInfo.path}  [${t}ms]")
            }
        }
    } else {
        //按 task 執行順序打印出執行時長信息
        for (String path : taskPathList) {
            long t = timeCostMap.get(path).total
            if (t >= timeCostExt.threshold) {
                println("${path}  [${t}ms]")
            }
        }
    }
    println "---------------------------------------"
    println "---------------------------------------"
}

在 build.gradle 里增加配置:

taskExecTime {
    threshold 100
    sorted true
}

再來看看輸出結果:

---------------------------------------
---------------------------------------
build finished, now println all task execution time:
:app:mergeDebugResources  [1109ms]
:app:transformDexArchiveWithExternalLibsDexMergerForDebug  [644ms]
:app:processDebugResources  [503ms]
:app:transformClassesWithDexBuilderForDebug  [464ms]
:app:packageDebug  [341ms]
:app:compileDebugJavaWithJavac  [329ms]
:app:transformDexArchiveWithDexMergerForDebug  [170ms]
:app:transformResourcesWithMergeJavaResForDebug  [122ms]
:app:transformNativeLibsWithMergeJniLibsForDebug  [122ms]
---------------------------------------
---------------------------------------
系列文章

Android Gradle學習(一):Gradle基礎入門
Android Gradle學習(二):如何創建Task
Android Gradle學習(三):Task進階學習
Android Gradle學習(四):Project詳解
Android Gradle學習(五):Extension詳解
Android Gradle學習(六):NamedDomainObjectContainer詳解
Android Gradle學習(七):Gradle構建生命周期
Android Gradle學習(八):統計Task執行時長
Android Gradle學習(九):一些有用的小技巧

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,412評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,514評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,373評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,975評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,743評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,199評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,262評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,414評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,951評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,780評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,527評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,218評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,649評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,889評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,673評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容

  • 這篇文章講給大家帶來gradle打包系列中的高級用法-自己動手編寫gradle插件。我們平常在做安卓開發時,都會在...
    呆萌狗和求疵喵閱讀 16,001評論 22 80
  • 說明 本文主要介紹和Gradle關系密切、相對不容易理解的配置,偏重概念介紹。部分內容是Android特有的(例如...
    jzj1993閱讀 15,680評論 1 62
  • 介紹 到目前為止,我們已經看到了很多Gradle構建的屬性,并且知道了怎么去執行Tasks。這一章,會更多的了解這...
    None_Ling閱讀 1,669評論 0 0
  • *本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家發布 一、為什么要學gradle Android ...
    very_mrq閱讀 79,793評論 15 96
  • 也許是人在南方,天生親水,所以小時候最喜歡的活動就是抓小魚小蝦。 第一個方法很傳統,釣。 那個時候大人不讓動鋤頭,...
    妖瑤杳閱讀 476評論 1 1