Android踩坑經驗--App啓動時間正確統計姿勢

在這裏討論的是指冷啓動,熱啓動不在討論範圍內。如何正確衡量App的啓動時間,一般有以下幾種方法:AMS日誌分析法,錄屏分幀法,代碼埋點法,logicat分析法等,本文重點闡述最常用的AMS日誌分析法,錄屏分幀法,代碼埋點法,重點闡述代碼埋點法的正確姿勢。
1:AMS日誌分析法
adb shell am start -w package_name/activity_name
輸出結果如下:
$ adb shell am start -W com.xxx.xxx/com.xxx.xxx.HomeActivity
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.xxx.xxx/.HomeActivity }
Status: ok
Activity: com.xxx.xxx/.HomeActivity
ThisTime: 496
TotalTime: 496
WaitTime: 503
Complete
WaitTime 返回從 startActivity 到應用第一幀完全顯示這段時間. 就是總的耗時,包括前一個應用 Activity pause 的時間和新應用啓動的時間;
ThisTime 表示一連串啓動 Activity 的最後一個 Activity 的啓動耗時;
TotalTime 表示新應用啓動的耗時,包括新進程的啓動和 Activity 的啓動,但不包括前一個應用Activity pause的耗時。
開發者一般只要關心 TotalTime 即可,這個時間纔是自己應用真正啓動的耗時。
優缺點:這個方法簡單快捷,可以作爲程序員日常優化時使用,如日常有優化項可以直接運行即可看到效果,缺點是無法追蹤線上的啓動時間。
2:錄屏分幀法
adb shell screenrecord –bugreport /sdcard/xxx.mp4
activity啓動後,使用ctrl+c結束視頻錄製,使用adb shell screenrecord –bugreport /sdcard/xxx.mp4導出視頻到電腦,使用可以按幀播放的視頻軟件打開(mac上quicktime就可以,win下可以用kmplayer),並按幀播放。
按幀播放視頻,視頻左上角會顯示每一幀的時間(精確到ms)和幀數。
在這裏插入圖片描述
在視頻中會看到icon會變暗然後高亮,高亮時就是系統開始處理本次icon點擊事件了。可以把這裏作爲點擊時間,然後根據體驗要求,看到app啓動頁完全繪製完作爲終止時間,這個時間減去點擊時間就是app的啓動時間。
優缺點:可以測試真實的APP啓動時間,但這個方法較爲複雜,需要人工計算App的啓動時間,費時費力,也無法追蹤線上的啓動時間,只可用於線下驗證測試。
目前有些公司用此方法測試,但沒有采用人工測試的方式,而是採用自動化測試,通過圖像識別算法,自動識別啓動時間的開始和結束爲止,然後計算啓動時間,參考鏈接:https://www.testwo.com/article/1252
3:代碼埋點
啓動時間的代碼埋點,主要是在代碼中埋下時間戳,通過開始和結束時間戳計算差值,從而得出啓動時間。看下目前比較流行的幾個點位:
啓動時間點:Application init開始,Application attachBaseContext開始,Application onCreate開始,Activity onCreate開始
結束時間點:Activity onResume結束,Activity onWindowFocusChanged結束,View dispatchDraw結束,Looper.myQueue.addIdleHandler方法開始
先拿目前手上project來看下這些點位的執行情況(裏面有view post邏輯,等會會):
03-25 11:58:01.215 5559 5559 I System.out: lwl Application init
03-25 11:58:01.215 5559 5559 I System.out: lwl Application attachBaseContext
03-25 11:58:01.237 5559 5559 I System.out: lwl Application onCreate
03-25 11:58:01.395 5559 5559 I System.out: lwl Activity onCreate
03-25 11:58:01.619 5559 5559 I System.out: lwl Activity onResume
03-25 11:58:01.753 5559 5559 I System.out: lwl activity view.post
03-25 11:58:01.855 5559 5559 I System.out: lwl Activity onWindowFocusChanged
03-25 11:58:01.861 5559 5559 I System.out: lwl view dispatchDraw
03-25 11:58:02.054 5559 5559 I System.out: lwl view post
03-25 11:58:02.155 5559 5559 I System.out: lwl activity view.postDelayed
03-25 11:58:02.508 5559 5559 I System.out: lwl Looper.myQueue().addIdleHandler
啓動時間點:
目前業界比較認可的是attachBaseContext方法,Application onCreate和Activity onCreate方法執行稍顯不準確,至少不能在Activity onCreate時打點,很多App會在Application創建時初始化全局。
從執行時機看,在Application init方法中打點與從attachBaseContext打點基本一致。
創建應用進程時,會makeApplication,在新建Application對象時會attach context,因此調用時機Application init方法和Application attachBaseContext幾乎無差的,規範性來講,個人比較認可attachBaseContext方法
在這裏插入圖片描述
結束時間點:
Activity onResume結束:這個點位不合適,爲什麼呢?onResume完了,但首頁view還沒有上屏呢,還沒有經過measure,layout,draw呢,因此此方法不合適。
Activity onWindowFocusChanged結束:這個方法在網上有很多人推薦,因爲此方法是窗口得到或失去焦點的時候調用的藉口,在源碼中有一段說明:This is the best indicator of whether this activity is visible to the user.意思是是否是用戶可見的最好標準。但有一個疑問,Activity可見了,但View就可見了嗎?從代碼中可以打斷點去看,你會發現View沒有上屏,Activity可見是沒錯,但看到的是背景而已。因此個人認爲此方法還不大準確。
在這裏插入圖片描述
如果在覈心View上打log看View的生命週期執行,會發現:
03-25 20:16:48.660 19578 19578 I System.out: lwl onMeasure
03-25 20:16:48.681 19578 19578 I System.out: lwl onMeasure
03-25 20:16:48.697 19578 19578 I System.out: lwl onLayout
03-25 20:16:48.699 19578 19578 I System.out: lwl onMeasure
03-25 20:16:48.699 19578 19578 I System.out: lwl onLayout
03-25 20:16:48.724 19578 19578 I System.out: lwl activity view.post
03-25 20:16:48.825 19578 19578 I System.out: lwl Activity onWindowFocusChanged
03-25 20:16:48.829 19578 19578 I System.out: lwl onMeasure
03-25 20:16:48.829 19578 19578 I System.out: lwl onLayout
03-25 20:16:48.834 19578 19578 I System.out: lwl view dispatchDraw
很明顯View還是沒上屏的(這個地方有疑問:onDraw方法沒執行,但不影響分析)。
View dispatchDraw結束: 如onWindowFocusChanged中所說,如果在此方法結束的地方打斷點,會發現也是白屏的。熟悉View繪製過程的都知道,先繪製根View,執行onDraw方法,再執行dispatchDraw繪製子View,子View執行onDraw,再執行子View 的dispatchDraw,繼續執行孫子View聲明週期,通過updateDisplayListIfDirty把View繪製相關的數據結構準備好,然後發消息給RenderThread,通知底層繪製(完全憑知識印象,感興趣的可以找資料覈實),因此這個方法算是比較接近界面可見的方法了。
Looper.myQueue.addIdleHandler方法開始:這個方法比較神奇,Android系統是基於Looper消息循環系統,通過Handler向MessageQueue投遞Message,而這個方法是Looper裏面的消息處理完後,會回調此方法,如果在此方法中打斷點,會發現此方法執行的時候,界面可見了,看似非常符合啓動時間的測試場景,但看個細節:如果我主動postDelay了一個任務,這時此方法會在postDelay的任務執行完後,再執行此回調。但其實首頁早就已經可見了。因此此方法不合適。
綜上所述:
推薦選擇打點位置:開始:Application attachBaseContext,結束:核心View的dispatchDraw結束。
注意:打點時需注意無效時間戳的處理,比如是否加載了廣告頁,是否加載了引導頁的等,是否是熱啓動等等,無效啓動時間計算結果的處理,比如:計算出來啓動時間結果大於5分鐘則丟棄,但需上報到單獨的打點中,供RD分析無效數據出現的場景。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章