如何統(tǒng)計(jì)Android App啟動時間

隨著App的邏輯不斷龐大,一不注意就會將耗時的操作放置在應(yīng)用啟動過程之中骨宠,導(dǎo)致應(yīng)用啟動速度越來越慢,用戶體驗(yàn)也越來越差。優(yōu)化啟動速度是幾乎所有大型App應(yīng)用開發(fā)者需要考慮的問題挟憔。優(yōu)化啟動速度之前首先需要準(zhǔn)確測量App啟動時間,這樣有利于我們更準(zhǔn)確可量化地看出優(yōu)化效果烟号,也可以指導(dǎo)我們進(jìn)行持續(xù)優(yōu)化曲楚。轉(zhuǎn)載請注明出處:Lawrence_Shen
同時可以參考2019年的性能分析文章:Android性能分析&啟動優(yōu)化

- 使用命令行方式

使用命令行方式統(tǒng)計(jì)多次啟動某個Activity的平均用時可以在shell中執(zhí)行如下指令:

adb shell am start -S -R 10 -W com.example.app/.MainActivity

其中-S表示每次啟動前先強(qiáng)行停止,-R表示重復(fù)測試次數(shù)褥符。每一次的輸出如下所示信息龙誊。

Stopping: com.example.app
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example.app/.MainActivity }
Status: ok
Activity: com.example.app/.MainActivity
ThisTime: 1059
TotalTime: 1059
WaitTime: 1073
Complete

其中TotalTime代表當(dāng)前Activity啟動時間,將多次TotalTime加起來求平均即可得到啟動這個Activity的時間喷楣。

缺點(diǎn)

  1. 應(yīng)用的啟動過程往往不只一個Activity趟大,有可能是先進(jìn)入一個啟動頁鹤树,然后再從啟動頁打開真正的首頁。某些情況下還有可能中間經(jīng)過更多的Activity逊朽,這個時候需要將多個Activity的時間加起來罕伯。
  2. 將多個Activity啟動時間加起來并不完全等于用戶感知的啟動時間。例如在啟動頁可能是先等待某些初始化完成或者某些動畫播放完畢后再進(jìn)入首頁叽讳。使用命令行統(tǒng)計(jì)的方式只是計(jì)算了Activity的啟動以及初始化時間追他,并不能體現(xiàn)這種等待任務(wù)的時間。
  3. 沒有在AndroidManifest.xml對應(yīng)的Activity聲明中指定<intent-filter>或者屬性沒有android:exported="true"的Activity不能使用這種命令行的形式計(jì)算啟動時間岛蚤。

-思考更準(zhǔn)確的方式

以上基于命令行的方式存在諸多問題邑狸,迫使我們思考怎樣才能得到從用戶角度上觀察更準(zhǔn)確的啟動時間。在嘗試其他方法之前涤妒,我們先定義一下怎樣才是從用戶角度上觀察的啟動時間单雾。

冷啟動、熱啟動(注意不是官方的定義她紫,是我們從用戶角度考慮的定義)

  • 冷啟動時間:冷啟動表示用戶首次打開應(yīng)用硅堆,這時進(jìn)程還沒創(chuàng)建,包含了Application創(chuàng)建的過程贿讹。冷啟動時間指從第一次用戶點(diǎn)擊Launcher中的應(yīng)用圖標(biāo)開始渐逃,到首頁內(nèi)容全部展示出來的時間。
  • 熱啟動時間:熱啟動表示用戶在首頁按了返回民褂,首頁Activity已經(jīng)Destroy茄菊,不過Application仍在內(nèi)存中存在,對應(yīng)的進(jìn)程并沒有被殺掉助赞,不包含Application創(chuàng)建過程买羞。熱啟動時間指在Application仍然存在的情況下,從用戶點(diǎn)擊桌面圖標(biāo)雹食,到首頁內(nèi)容全部展示出來的時間畜普。

App啟動流程

要優(yōu)化以及分析啟動時間,需要先了解App的啟動流程群叶。以冷啟動為例子吃挑,Application以及Activity的啟動流程如下,參考文章[3][4][5][6]

app啟動流程

更為直觀和簡單的流程圖參考Colt McAnlis在Android Performance Patterns Season 6中的表述街立。有興趣的同學(xué)可以點(diǎn)擊鏈接看看(Youtube鏈接)舶衬。

app啟動流程by Colt McAnlis

從流程圖以及參考Colt McAnlis的Android Performance Patterns[6]得知,在冷啟動的過程中赎离,首先會通過AMS在System進(jìn)程展示一個Starting Window(通常情況下是個白屏逛犹,可以通過設(shè)置Application的theme修改),接著AMS會通過Zygote創(chuàng)建應(yīng)用程序的進(jìn)程,并通過一系列的步驟后調(diào)用Application的attachBaseContext()虽画、onCreate()然后最終調(diào)用Activity的onCreate()以及進(jìn)行View相關(guān)的初始化工作舞蔽。在Activity展示出來后會替換掉之前的Starting Window,這樣啟動過程結(jié)束码撰。

如何加log

參考[1]發(fā)現(xiàn)在Activity中onWindowFocusChanged()方法是最好的Activity對用戶可見的標(biāo)志渗柿,因此綜合上一節(jié)的分析,我們可以考慮在Application的attachBaseContext()方法中開始計(jì)算冷啟動計(jì)時脖岛,然后在真正首頁Activity的onWindowFocusChanged()中停止冷啟動計(jì)時朵栖,這樣就可以初步得到應(yīng)用的冷啟動時間。

public void onWindowFocusChanged(boolean hasFocus)

Called when the current android.view.Window of the activity gains or loses focus. This is the best indicator of whether this activity is visible to the user.

為了方便統(tǒng)計(jì)柴梆,設(shè)置一個Util類專門做計(jì)時陨溅,添加的代碼如下:

/**
 * 計(jì)時統(tǒng)計(jì)工具類
 */
public class TimeUtils {
    private static HashMap<String, Long> sCalTimeMap = new HashMap<>();
    public static final String COLD_START = "cold_start";
    public static final String HOT_START = "hot_start";
    public static long sColdStartTime = 0;

    /**
     * 記錄某個事件的開始時間
     * @param key 事件名稱
     */
    public static void beginTimeCalculate(String key) {
        long currentTime = System.currentTimeMillis();
        sCalTimeMap.put(key, currentTime);
    }

    /**
     * 獲取某個事件的運(yùn)行時間
     *
     * @param key 事件名稱
     * @return 返回某個事件的運(yùn)行時間,調(diào)用這個方法之前沒有調(diào)用 {@link #beginTimeCalculate(String)} 則返回-1
     */
    public static long getTimeCalculate(String key) {
        long currentTime = System.currentTimeMillis();
        Long beginTime = sCalTimeMap.get(key);
        if (beginTime == null) {
            return -1;
        } else {
            sCalTimeMap.remove(key);
            return currentTime - beginTime;
        }
    }

    /**
     * 清除某個時間運(yùn)行時間計(jì)時
     *
     * @param key 事件名稱
     */
    public static void clearTimeCalculate(String key) {
        sCalTimeMap.remove(key);
    }

    /**
     * 清除啟動時間計(jì)時
     */
    public static void clearStartTimeCalculate() {
        clearTimeCalculate(HOT_START);
        clearTimeCalculate(COLD_START);
        sColdStartTime = 0;
    }
}

然后在Application的attachBaseContext()方法中添加如下代碼:

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    if (/**如果是主進(jìn)程**/) {
        TimeUtils.beginTimeCalculate(TimeUtils.COLD_START);
    }
}

在第一個Activity的onCreate()方法中添加如下代碼:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    calculateStartTime();
    ....
}

private void calculateStartTime() {
    long coldStartTime = TimeUtils.getTimeCalculate(TimeUtils.COLD_START);
    // 這里記錄的TimeUtils.coldStartTime是指Application啟動的時間轩性,最終的冷啟動時間等于Application啟動時間+熱啟動時間
    TimeUtils.sColdStartTime = coldStartTime > 0 ? coldStartTime : 0;
    TimeUtils.beginTimeCalculate(DictTimeUtil.HOT_START);
}

在真正的首頁Activity的 onWindowFocusChanged()方法中添加如下代碼:

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    if (hasFocus && /**沒有經(jīng)過廣告或者引導(dǎo)頁**/) {
        long hotStartTime = TimeUtils.getTimeCalculate(TimeUtils.HOT_START);
        if (TimeUtils.sColdStartTime > 0 && hotStartTime > 0) {
            // 真正的冷啟動時間 = Application啟動時間 + 熱啟動時間
            long coldStartTime = TimeUtils.sColdStartTime + hotStartTime;
            // 過濾掉異常啟動時間
            if (coldStartTime < 50000) {
                // 上傳冷啟動時間coldStartTime 
            }
        } else if (hotStartTime > 0) {
            // 過濾掉異常啟動時間
            if (hotStartTime < 30000) {
                // 上傳熱啟動時間hotStartTime 
            }
        }
    }
}

避免坑的Checklist

上面的分析給了我們初步的加log的起始和結(jié)束點(diǎn)声登,然而在實(shí)際的統(tǒng)計(jì)中會發(fā)現(xiàn)得到的數(shù)據(jù)有20%左右是不準(zhǔn)確的狠鸳,體現(xiàn)在計(jì)時數(shù)據(jù)非常大揣苏,有些甚至?xí)@示冷啟動時間超過一天。經(jīng)過分析件舵,在計(jì)算啟動計(jì)時的時候需要注意一些問題卸察。以下列舉一下添加log時候需要注意的checklist。

  1. 應(yīng)用在啟動過程可能會有廣告(我們的業(yè)務(wù)是有道詞典)铅祸,第一次啟動會有引導(dǎo)頁坑质,需要根據(jù)業(yè)務(wù)情況標(biāo)記在沒有廣告、沒有引導(dǎo)頁的時候才計(jì)算临梗。這種情況要注意在非正常啟動的時候忽略啟動時間統(tǒng)計(jì)涡扼。

  2. 由于詞典首頁之前還有幾個Activity,在沒到首頁Activity之前如果過早的返回盟庞,會出現(xiàn)冷啟動時間過長的問題吃沪。這是因?yàn)樵~典返回的時候并沒有殺掉進(jìn)程,而時間統(tǒng)計(jì)信息是保存在內(nèi)存中的什猖,而等下次再進(jìn)入的時候因?yàn)槭菬釂硬粫匦麻_始冷啟動計(jì)時票彪。這導(dǎo)致了這次熱啟動實(shí)際上打log的時候發(fā)現(xiàn)有上次冷啟動的開始時間,算成了冷啟動不狮,而且因?yàn)閱訒r間是上一次的降铸,所以這次冷啟動log的時間比實(shí)際時間長。這種情況要注意在首頁Activity之前的其他ActivityonPause()方法中調(diào)用TimeUtils.clearStartTimeCalculate();清除計(jì)時摇零。

  3. 除了正常的啟動流程推掸,應(yīng)用還有很多可能會導(dǎo)致Application的創(chuàng)建的入口,例如點(diǎn)擊桌面小插件、系統(tǒng)賬號同步谅畅、Deep Link跳轉(zhuǎn)俊嗽、直接進(jìn)入設(shè)置了<action android:name="android.intent.action.PROCESS_TEXT" />的Activity、push達(dá)到等铃彰。我們需要檢查所有有可能引起Application創(chuàng)建绍豁,但是不是正常啟動流程的地方,調(diào)用TimeUtils.clearStartTimeCalculate();清除計(jì)時牙捉,避免引起冷啟動時間計(jì)算過長錯誤的問題竹揍。

- 使用第三方工具

為了測試啟動的過程中哪些方法比較耗時,我們可以使用Android Studio中集成的Android Monitor提供的Method Tracering或者Systrace邪铲。不過在實(shí)踐中發(fā)現(xiàn)芬位,有另外一個nimbledroid工具使用更加簡便且能更明確指出耗時的地方。上傳了應(yīng)用之后會自動分析情景如下圖所示带到。其中會自動檢測出首頁的Activity并且給出冷啟動的啟動情況昧碉。

情景分析

點(diǎn)擊進(jìn)入Cold Startup的情景可以看到主要耗時的方法如下圖。

情景詳細(xì)耗時統(tǒng)計(jì)

至于為什么nimbledroid會知道那個是我們首頁的Activity揽惹,官網(wǎng)上解析如下:

We use a heuristic to tell when an app finishes startup by detecting when (1) the main Activity has been displayed and (2) things like animated progress bars in the main Activity have stopped. Based on our experiments, this heuristic works in most cases.

點(diǎn)擊進(jìn)入某個方法被饿,可以看到這個方法具體是由于調(diào)用了哪個子方法導(dǎo)致了耗時的問題。

耗時方法詳細(xì)

通過nimbledroid這個工具搪搏,我們可以比較輕松地發(fā)現(xiàn)一些比較明顯的問題狭握,并可以指導(dǎo)我們進(jìn)行啟動優(yōu)化。同時nimbledroid還支持Memory Leaks疯溺、網(wǎng)絡(luò)監(jiān)測以及結(jié)果分享等一些功能论颅,更多的功能有待讀者繼續(xù)發(fā)現(xiàn)。

- 后記

統(tǒng)計(jì)和分析啟動時間有利于指導(dǎo)我們優(yōu)化啟動時間囱嫩。以上介紹了有道詞典在進(jìn)行啟動優(yōu)化中的分析過程恃疯。通過詳細(xì)了解Android應(yīng)用啟動的流程,進(jìn)行準(zhǔn)確的log記錄墨闲,并且結(jié)合第三方工具今妄,我們最終得到準(zhǔn)確的啟動時間統(tǒng)計(jì)數(shù)據(jù)以及啟動優(yōu)化的一些頭緒。具體優(yōu)化的方法參加下一篇文章《如何優(yōu)化Androd App啟動速度》损俭。

- 參考

【1】單刀土豆蛙奖,2016.Android 開發(fā)之 App 啟動時間統(tǒng)計(jì)
【2】Android DeveloperLaunch-Time Performance
【3】./multi_core_dump杆兵,2010.Android Application Launch
【4】./multi_core_dump雁仲,2010.Android Application Launch Part 2
【5】羅升陽,2012.Android系統(tǒng)源代碼情景分析
【6】Colt McAnlis琐脏,2016.Android Performance Patterns Season 6

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末攒砖,一起剝皮案震驚了整個濱河市缸兔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吹艇,老刑警劉巖惰蜜,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異受神,居然都是意外死亡抛猖,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門鼻听,熙熙樓的掌柜王于貴愁眉苦臉地迎上來财著,“玉大人,你說我怎么就攤上這事撑碴〕沤蹋” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵醉拓,是天一觀的道長伟姐。 經(jīng)常有香客問我,道長亿卤,這世上最難降的妖魔是什么愤兵? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮怠噪,結(jié)果婚禮上恐似,老公的妹妹穿的比我還像新娘杜跷。我一直安慰自己傍念,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布葛闷。 她就那樣靜靜地躺著憋槐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪淑趾。 梳的紋絲不亂的頭發(fā)上阳仔,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機(jī)與錄音扣泊,去河邊找鬼近范。 笑死,一個胖子當(dāng)著我的面吹牛延蟹,可吹牛的內(nèi)容都是我干的评矩。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼阱飘,長吁一口氣:“原來是場噩夢啊……” “哼斥杜!你這毒婦竟也來了虱颗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蔗喂,失蹤者是張志新(化名)和其女友劉穎忘渔,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缰儿,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡畦粮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了乖阵。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锈玉。...
    茶點(diǎn)故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖义起,靈堂內(nèi)的尸體忽然破棺而出拉背,到底是詐尸還是另有隱情,我是刑警寧澤默终,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布椅棺,位于F島的核電站,受9級特大地震影響齐蔽,放射性物質(zhì)發(fā)生泄漏两疚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一含滴、第九天 我趴在偏房一處隱蔽的房頂上張望诱渤。 院中可真熱鬧,春花似錦谈况、人聲如沸勺美。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赡茸。三九已至,卻和暖如春祝闻,著一層夾襖步出監(jiān)牢的瞬間占卧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工联喘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留华蜒,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓豁遭,卻偏偏與公主長得像叭喜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子堤框,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評論 2 348

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