Android 中如何計算 App 的啟動時間?

Gracker發表於2016-01-01

之前有人在知乎提問: “怎麼計算apk的啟動時間?” :

利用python或者直接用adb命令怎麼計算apk的啟動時間呢?就是計算從點選圖示到apk完全啟動所花費的時間。比如,對遊戲來說就是點選遊戲圖示到進入到登入介面的這段時間。已知的兩種方法貌似可以獲取,但是感覺結果不準確:一種是,adb shell am start -w packagename/activity,這個可以得到兩個值,ThisTime和TotalTime,不知道兩個有什麼區別,而且與實際啟動時間不匹配,兩者相加都可能比實際啟動時間小(測試遊戲的時候差別更大);另外一種是通過adb logcat的方式,感覺獲取的結果也與實際有差別。

我和另外一個同事 啟發 針對兩個方面進行了回答,不過畢竟知乎上看的人會比較少,所以我在徵得他的同意之後,將這兩個答案整理了一下,記錄到部落格中,一來算是一個小的總結,之後自己看得時候比較方便,二來給需要的同學一個更加方便的途徑。

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.meizu.media.painter/com.meizu.media.painter.PainterMainActivity
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.meizu.media.painter/.PainterMainActivity }
Status: ok
Activity: com.meizu.media.painter/.PainterMainActivity
ThisTime: 355
TotalTime: 355
WaitTime: 365
Complete

總共返回了三個結果,我們以 WaitTime 為準.

關於ThisTime/TotalTime/WaitTime的區別,下面是其解釋:

“adb shell am start -W ”的實現在 frameworks/base/cmds/am/src/com/android/commands/am/Am.java 檔案中。其實就是跨Binder呼叫ActivityManagerService.startActivityAndWait() 介面(後面將ActivityManagerService簡稱為AMS),這個介面返回的結果包含上面列印的ThisTime、TotalTime時間.

Android 中如何計算 App 的啟動時間?

  • startTime記錄的剛準備呼叫startActivityAndWait()的時間點
  • endTime記錄的是startActivityAndWait()函式呼叫返回的時間點
  • WaitTime = startActivityAndWait()呼叫耗時。

ThisTime、TotalTime 的計算在 frameworks/base/services/core/java/com/android/server/am/ActivityRecord.java 檔案的 reportLaunchTimeLocked() 函式中。

Android 中如何計算 App 的啟動時間?

我們來解釋下程式碼裡curTime、displayStartTime、mLaunchStartTime三個時間變數.

  • curTime表示該函式呼叫的時間點.
  • displayStartTime表示一連串啟動Activity中的最後一個Activity的啟動時間點.
  • mLaunchStartTime表示一連串啟動Activity中第一個Activity的啟動時間點.

正常情況下點選桌面圖示只啟動一個有介面的 Activity,此時 displayStartTime 與mLaunchStartTime 便指向同一時間點,此時 ThisTime=TotalTime。另一種情況是點選桌面圖示應用會先啟動一個無介面的 Activity 做邏輯處理,接著又啟動一個有介面的Activity,在這種啟動一連串 Activity 的情況下(知乎的啟動就是屬於這種情況),displayStartTime 便指向最後一個 Activity 的開始啟動時間點,mLaunchStartTime 指向第一個無介面Activity的開始啟動時間點,此時 ThisTime!=TotalTime。這兩種情況如下圖:

Android 中如何計算 App 的啟動時間?

在上面的圖中,我用①②③分別標註了三個時間段,在這三個時間段內分別幹了什麼事呢?

  • 在第①個時間段內,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即可。這也就是我們說的應用熱啟動。

2 遊戲啟動場景

遊戲啟動的話,就不適用用命令列的方法來啟動了,因為從使用者點選桌面圖示到登入介面,既有系統的部分也有遊戲自己的部分。

2.1 系統部分

遊戲也有一個Activity,所以啟動的時候還是會去啟動這個Activity,所以系統啟動部分也就是使用者點選桌面桌面響應到這個Activity啟動。

2.2 遊戲部分

一般遊戲的主Activity啟動後,還會做一些比較耗時的事情,這時候你看到的介面是不能操作的,比如:載入遊戲資料、聯網更新資料、讀取和更新配置檔案、遊戲引擎初始化等操作。從遊戲開發的角度來看,到了真正使用者能操作的介面才算是一個遊戲真正載入完成的時間。那麼這個時間,就得使用Log來記錄了,因為載入遊戲資料、聯網更新資料、讀取和更新配置檔案、遊戲引擎初始化這些操作,都是遊戲自己的邏輯,與系統無關,所以得由遊戲自己定義載入完成的點。

對於遊戲的啟動時間,我們更傾向於計算從 點選桌面圖示使用者可以與遊戲進行互動 這個時間段作為一個遊戲的啟動時間。

3 總結

計算機最讓人著迷的一點就是其準確性,1+1永遠等於2,啟動耗時多久就是多久,每一次可能不一樣,但每一次的時間都是這一次的準確時間。

不過每個公司由於對應用的定位不同,所以對應用啟動的要求也不一樣。比如有的做 ROM 的公司,其內建應用的啟動時間一定是要非常快的,這樣給使用者的第一感覺就是快、流暢;網際網路公司的 App 則不是很關心啟動速度,大部分網際網路公司的應用都有一個啟動頁,用來展示廣告或者功能介紹之類的,然後才會進入到主介面。需求不一樣,這麼做也無可厚非,不過從消費者的角度來看,越早見到主介面當然越好。

所以在做一個 Android App 的時候,一定要記得將應用的啟動時間作為一個效能指標,畢竟

天下武功,唯快不破!

4 關於作者

文章裡提到的我的同事郭啟發,其

他對 Android Framework 研究很深,感興趣的可以和他聊聊。

相關文章