1 應用啟動場景
事實上 Android 中一個 App 的啟動時間可以準確計算的.但是要分場景.也就是說要分開游戲和應用. 大家都知道,在Android中,游戲開發和應用開發是兩碼事.所以我們需要分開來說.
1.1 應用啟動
我們平時在寫應用的時候,一般會指定一個 mainActivity ,用戶在桌面上點擊這個 Activity 的時候,系統會直接起這個
Activity. 我們知道 Activity 在啟動的時候會走 onCreate/onStart/onResume .這幾個回調函數.
許多書里講過,當執行完 onResume 函數之后,應用就顯示出來了…其實這是一種不準確的說法,因為從系統層面來看,一個 Activity
走完 onCreate/onStart/onResume 這幾個生命周期之后,只是完成了應用自身的一些配置,比如 window
的一些屬性的設置/ View 樹的建立(只是建立,并沒有顯示,也就是說只是調用了 inflate 而已) . 后面 ViewRootImpl
還會調用兩次performTraversals ,初始化 Egl 以及 measure/layout/draw. 等.
所以我們定義一個 Android 應用的啟動時間, 肯定不能在 Activity 的回調函數上下手.而是以用戶在手機屏幕上看到你在 onCreate 的 setContentView 中設置的 layout 完全顯示為準,也就是我們常說的應用第一幀.
上面扯得有點遠,不感興趣的話可以不看,下面直接說方法.
題主說的 adb shell am start -w packagename/activity,是可以完全應用的啟動時間的.不過也要分場景.
1.2 應用第一次啟動
也就是我們常說的冷啟動,這時候你的應用程序的進程是沒有創建的. 這也是大部分應用的使用場景.用戶在桌面上點擊你應用的 icon 之后,首先要創建進程,然后才啟動 MainActivity.
這時候adb shell am start -w packagename/MainActivity 返回的結果,就是標準的應用程序的啟動時間(注意 Android 5.0 之前的手機是沒有 WaitTime 這個值的):
? adb shell am start -W com.media.painter/com.media.painter.PainterMainActivity
Starting:
Intent { act=android.intent.action.MAIN
cat=[android.intent.category.LAUNCHER]
cmp=com.media.painter/.PainterMainActivity }
Status: ok
Activity: com.media.painter/.PainterMainActivity
ThisTime: 355
TotalTime: 355
WaitTime: 365
Complete
8?adb shell am start -W com.media.painter/com.media.painter.PainterMainActivity
Starting:Intent{act=android.intent.action.MAINcat=[android.intent.category.LAUNCHER]cmp=com.media.painter/.PainterMainActivity}
Status:ok
Activity:com.media.painter/.PainterMainActivity
ThisTime:355
TotalTime:355
WaitTime:365
Complete
總共返回了三個結果,我們以 WaitTime 為準.
關于ThisTime/TotalTime/WaitTime的區別,下面是其解釋:
“adb shell am start -W ”的實現在
frameworksbasecmdsamsrccomandroidcommandsamAm.java
文件中。其實就是跨Binder調用ActivityManagerService.startActivityAndWait()
接口(后面將ActivityManagerService簡稱為AMS),這個接口返回的結果包含上面打印的ThisTime、TotalTime時間.
startTime記錄的剛準備調用startActivityAndWait()的時間點
endTime記錄的是startActivityAndWait()函數調用返回的時間點
WaitTime = startActivityAndWait()調用耗時。
ThisTime、TotalTime 的計算在 frameworksbaseservicescorejavacomandroidserveramActivityRecord.java 文件的 reportLaunchTimeLocked() 函數中。
我們來解釋下代碼里curTime、displayStartTime、mLaunchStartTime三個時間變量.
curTime表示該函數調用的時間點.
displayStartTime表示一連串啟動Activity中的最后一個Activity的啟動時間點.
mLaunchStartTime表示一連串啟動Activity中第一個Activity的啟動時間點.
正常情況下點擊桌面圖標只啟動一個有界面的 Activity,此時 displayStartTime 與mLaunchStartTime
便指向同一時間點,此時 ThisTime=TotalTime。另一種情況是點擊桌面圖標應用會先啟動一個無界面的 Activity
做邏輯處理,接著又啟動一個有界面的Activity,在這種啟動一連串 Activity
的情況下(知乎的啟動就是屬于這種情況),displayStartTime 便指向最后一個 Activity
的開始啟動時間點,mLaunchStartTime 指向第一個無界面Activity的開始啟動時間點,此時
ThisTime!=TotalTime。這兩種情況如下圖:
在上面的圖中,我用①②③分別標注了三個時間段,在這三個時間段內分別干了什么事呢?
在第①個時間段內,AMS 創建 ActivityRecord 記錄塊和選擇合理的 Task、將當前Resume 的 Activity 進行 pause
在第②個時間段內,啟動進程、調用無界面 Activity 的 onCreate() 等、 pause/finish 無界面的 Activity
在第③個時間段內,調用有界面 Activity 的 onCreate、onResume
看到這里應該清楚 ThisTime、TotalTime、WaitTime 三個時間的關系了吧:
WaitTime 就是總的耗時,包括前一個應用 Activity pause 的時間和新應用啟動的時間;
ThisTime 表示一連串啟動 Activity 的最后一個 Activity 的啟動耗時;
TotalTime 表示新應用啟動的耗時,包括新進程的啟動和 Activity 的啟動,但不包括前
一個應用 Activity pause 的耗時。也就是說,開發者一般只要關心 TotalTime 即可,這個時間才是自己應用真正啟動的耗時。
Event log中 TAG=am_activity_launch_time 中的兩個值分表表示 ThisTime、TotalTime,跟通過 “adb shell am start -W ” 得到的值是一致的。
最后再說下系統根據什么來判斷應用啟動結束。我們知道應用啟動包括進程啟動、走 Activity生命周期 onCreate/onResume
等。在第一次 onResume 時添加窗口到WMS中,然后measure/layout/draw,窗口繪制完成后通知 WMS,WMS
在合適的時機控制界面開始顯示(夾雜了界面切換動畫邏輯)。記住是窗口界面顯示出來后,WMS 才調用reportLaunchTimeLocked()
通知 AMS Activity 啟動完成。
最后總結一下,如果只關心某個應用自身啟動耗時,參考TotalTime;如果關心系統啟動應用耗時,參考WaitTime;如果關心應用有界面Activity啟動耗時,參考ThisTime。
1.2 應用非第一次啟動
如果是你按Back鍵,并沒有將應用進程殺掉的話,那么執行上述命令就會快一些,因為不用創建進程了,只需要啟動一個Activity即可。這也就是我們說的應用熱啟動。