Android Gradle學(xué)習(xí)(八):統(tǒng)計(jì)Task執(zhí)行時(shí)長(zhǎng)

關(guān)于 Gradle 的基本知識(shí)渗稍,前面章節(jié)已經(jīng)講的差不多了。那么,我們現(xiàn)在來(lái)牛刀小試一下诈嘿,看看 Gradle 有什么用武之地。

我們?cè)趯?Android 應(yīng)用程序打包成 apk 包時(shí)削葱,有時(shí)會(huì)發(fā)現(xiàn)整個(gè) build 過(guò)程特別長(zhǎng)奖亚,短則 1、2 分鐘析砸,長(zhǎng)則大幾分鐘甚至更長(zhǎng)昔字,特別是你要進(jìn)行調(diào)試時(shí),漫長(zhǎng)的等待會(huì)讓人很焦躁。我們?cè)诳刂婆_(tái)可以看到整個(gè)打包過(guò)程包含很多個(gè) task 作郭,那么到底是哪些 task 的執(zhí)行花費(fèi)了大量時(shí)間內(nèi)陨囊?

Gradle 提供了很多構(gòu)建生命周期鉤子函數(shù),我們可以用 TaskExecutionListener 來(lái)監(jiān)聽(tīng)整個(gè)構(gòu)建過(guò)程中 task 的執(zhí)行:

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

在每個(gè) task 執(zhí)行前先搜集其相關(guān)信息夹攒,記錄該 task 執(zhí)行的開(kāi)始時(shí)間等蜘醋,在 task 執(zhí)行完成后,記錄其執(zhí)行結(jié)束時(shí)間咏尝,這樣就能統(tǒng)計(jì)出該 task 的執(zhí)行時(shí)長(zhǎng)压语。

接著,我們可以用 BuildListener 來(lái)監(jiān)聽(tīng)整個(gè)構(gòu)建是否完成编检,在構(gòu)建完成后胎食,輸出所有執(zhí)行過(guò)的 task 信息,以及每個(gè) task 的執(zhí)行時(shí)長(zhǎng):

public interface BuildListener {
    void buildStarted(Gradle gradle);

    void settingsEvaluated(Settings settings);

    void projectsLoaded(Gradle gradle);

    void projectsEvaluated(Gradle gradle);

    void buildFinished(BuildResult buildResult);
}

在 buildFinished 方法中允懂,監(jiān)聽(tīng)構(gòu)建完成以及成功與否厕怜。為了方便使用,考慮做成一個(gè) Gradle 插件蕾总,關(guān)于插件的制作粥航,這里不贅述了,網(wǎng)上有很多關(guān)于 Gradle 插件制作的教程谤专。

不多說(shuō)了躁锡,直接上代碼,核心只有一個(gè)插件類:

class BuildTimeCostPlugin implements Plugin<Project>{

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

    @Override
    void apply(Project project) {
        //監(jiān)聽(tīng)每個(gè)task的執(zhí)行
        project.getGradle().addListener(new TaskExecutionListener() {
            @Override
            void beforeExecute(Task task) {
                //task開(kāi)始執(zhí)行之前搜集task的信息
                TaskExecTimeInfo timeInfo = new TaskExecTimeInfo()
                //記錄開(kāi)始時(shí)間
                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執(zhí)行完之后置侍,記錄結(jié)束時(shí)的時(shí)間
                TaskExecTimeInfo timeInfo = timeCostMap.get(task.getPath())
                timeInfo.end = System.currentTimeMillis()
                //計(jì)算該 task 的執(zhí)行時(shí)長(zhǎng)
                timeInfo.total = timeInfo.end - timeInfo.start
            }
        })

        //編譯結(jié)束之后:
        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 執(zhí)行順序打印出執(zhí)行時(shí)長(zhǎng)信息
                for (String path : taskPathList) {
                    long t = timeCostMap.get(path).total
                    if (t >= timeCostExt.threshold) {
                        println("${path}  [${t}ms]")
                    }
                }                println "---------------------------------------"
                println "---------------------------------------"
            }
        })

    }

    //關(guān)于 task 的執(zhí)行信息
    class TaskExecTimeInfo {

        long total      //task執(zhí)行總時(shí)長(zhǎng)

        String path
        long start      //task 執(zhí)行開(kāi)始時(shí)間
        long end        //task 結(jié)束時(shí)間

    }

}

接下來(lái)映之,我們創(chuàng)建一個(gè)測(cè)試工程,使用這個(gè)插件蜡坊,執(zhí)行 “assembleDebug” 這個(gè)構(gòu)建任務(wù)杠输,打一個(gè) debug 的測(cè)試包出來(lái),構(gòu)建完成之后秕衙,可以在 Gradle Console 里看到本次構(gòu)建里所有 task 的執(zhí)行時(shí)長(zhǎng)信息:

---------------------------------------
---------------------------------------
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 這個(gè) task 執(zhí)行時(shí)長(zhǎng)為 2038ms蠢甲,將近有2秒鐘,是這里面執(zhí)行時(shí)長(zhǎng)最長(zhǎng)的一個(gè)据忘。由于 Gradle 支持增量構(gòu)建鹦牛,再次構(gòu)建的時(shí)間可能就不一樣了。

上面這個(gè)插件勇吊,能不能只打印出執(zhí)行時(shí)長(zhǎng)超過(guò) 1000ms 的任務(wù)呢曼追?結(jié)果能不能排序后輸出呢?我們繼續(xù)做點(diǎn)優(yōu)化汉规,這就需要前面介紹的 Extension 相關(guān)知識(shí)了礼殊。

先創(chuàng)建一個(gè) Extension 類,代碼如下:

class BuildTimeCostExtension {

    //task執(zhí)行時(shí)間超過(guò)該值才會(huì)統(tǒng)計(jì)
    int threshold

    //是否按照task執(zhí)行時(shí)長(zhǎng)進(jìn)行排序,true-表示從大到小進(jìn)行排序晶伦,false-表示不排序
    boolean sorted

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

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

}

修改插件類碟狞,在插件里創(chuàng)建自定義 Extension:

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

修改 task 執(zhí)行時(shí)長(zhǎng)輸出結(jié)果的代碼婚陪,根據(jù)配置來(lái)輸出不同的結(jié)果:

@Override
void buildFinished(BuildResult buildResult) {
    println "---------------------------------------"
    println "---------------------------------------"
    println "build finished, now println all task execution time:"
    if (timeCostExt.sorted) {
        //進(jìn)行排序
        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 執(zhí)行順序打印出執(zhí)行時(shí)長(zhǎng)信息
        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
}

再來(lái)看看輸出結(jié)果:

---------------------------------------
---------------------------------------
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學(xué)習(xí)(一):Gradle基礎(chǔ)入門(mén)
Android Gradle學(xué)習(xí)(二):如何創(chuàng)建Task
Android Gradle學(xué)習(xí)(三):Task進(jìn)階學(xué)習(xí)
Android Gradle學(xué)習(xí)(四):Project詳解
Android Gradle學(xué)習(xí)(五):Extension詳解
Android Gradle學(xué)習(xí)(六):NamedDomainObjectContainer詳解
Android Gradle學(xué)習(xí)(七):Gradle構(gòu)建生命周期
Android Gradle學(xué)習(xí)(八):統(tǒng)計(jì)Task執(zhí)行時(shí)長(zhǎng)
Android Gradle學(xué)習(xí)(九):一些有用的小技巧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末族沃,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子泌参,更是在濱河造成了極大的恐慌竭业,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件及舍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡窟绷,警方通過(guò)查閱死者的電腦和手機(jī)锯玛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)兼蜈,“玉大人攘残,你說(shuō)我怎么就攤上這事∥辏” “怎么了歼郭?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)辐棒。 經(jīng)常有香客問(wèn)我病曾,道長(zhǎng),這世上最難降的妖魔是什么漾根? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任泰涂,我火速辦了婚禮,結(jié)果婚禮上辐怕,老公的妹妹穿的比我還像新娘逼蒙。我一直安慰自己,他們只是感情好寄疏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布是牢。 她就那樣靜靜地躺著,像睡著了一般陕截。 火紅的嫁衣襯著肌膚如雪驳棱。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,624評(píng)論 1 305
  • 那天艘策,我揣著相機(jī)與錄音蹈胡,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛罚渐,可吹牛的內(nèi)容都是我干的却汉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼荷并,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼合砂!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起源织,我...
    開(kāi)封第一講書(shū)人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤翩伪,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后谈息,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體缘屹,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年侠仇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了轻姿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逻炊,死狀恐怖互亮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情余素,我是刑警寧澤豹休,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站桨吊,受9級(jí)特大地震影響威根,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屏积,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一医窿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧炊林,春花似錦姥卢、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至奕枝,卻和暖如春棺榔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背隘道。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工症歇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留郎笆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓忘晤,卻偏偏與公主長(zhǎng)得像宛蚓,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子设塔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 這篇文章講給大家?guī)?lái)gradle打包系列中的高級(jí)用法-自己動(dòng)手編寫(xiě)gradle插件凄吏。我們平常在做安卓開(kāi)發(fā)時(shí),都會(huì)在...
    呆萌狗和求疵喵閱讀 15,985評(píng)論 22 80
  • 說(shuō)明 本文主要介紹和Gradle關(guān)系密切闰蛔、相對(duì)不容易理解的配置痕钢,偏重概念介紹。部分內(nèi)容是Android特有的(例如...
    jzj1993閱讀 15,631評(píng)論 1 62
  • 介紹 到目前為止序六,我們已經(jīng)看到了很多Gradle構(gòu)建的屬性任连,并且知道了怎么去執(zhí)行Tasks。這一章例诀,會(huì)更多的了解這...
    None_Ling閱讀 1,656評(píng)論 0 0
  • *本篇文章已授權(quán)微信公眾號(hào) guolin_blog (郭霖)獨(dú)家發(fā)布 一课梳、為什么要學(xué)gradle Android ...
    very_mrq閱讀 79,762評(píng)論 15 96
  • 也許是人在南方,天生親水余佃,所以小時(shí)候最喜歡的活動(dòng)就是抓小魚(yú)小蝦。 第一個(gè)方法很傳統(tǒng)跨算,釣爆土。 那個(gè)時(shí)候大人不讓動(dòng)鋤頭,...
    妖瑤杳閱讀 469評(píng)論 1 1