應用啟動介紹
我們平時在寫應用的時候,一般會指定一個
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 完全顯示為準,也就是我們常說的應用第一幀.
1. 啟動的類型
參考自 – Android 開發之 App 啟動時間統計
- 冷啟動 ------- 當啟動應用時,背景沒有該應用的程序,這時系統會重新建立一個新的程序配置設定給該應用,這個啟動方式就是冷啟動。冷啟動因為系統會重新建立一個新的程序配置設定給它,是以會先建立和初始化 Application 類,再建立和初始化 MainActivity 類,最後顯示在界面上。
- 熱啟動 ------- 當啟動應用時,背景已有該應用的程序(例:按back鍵、home鍵,應用雖然會退出,但是該應用的程序是依然會保留在背景,可進入任務清單檢視),是以在已有程序的情況下,這種啟動會從已有的程序中來啟動應用,這個方式叫熱啟動。熱啟動因為會從已有的程序中來啟動,是以熱啟動就不會走 Application 這步了,而是直接走 MainActivity,是以熱啟動的過程不必建立和初始化 Application,因為一個應用從新程序的建立到程序的銷毀,Application 隻會初始化一次。
- 首次啟動 ------- 首次啟動嚴格來說也是冷啟動,之是以把首次啟動單獨列出來,一般來說,首次啟動時間會比非首次啟動要久,首次啟動會做一些系統初始化工作,如緩存目錄的生産,資料庫的建立,SharedPreference的初始化,如果存在多 dex 和插件的情況下,首次啟動會有一些特殊需要處理的邏輯,而且對啟動速度有很大的影響,是以首次啟動的速度非常重要,畢竟影響使用者對 App 的第一映像。
2. 統計啟動時間
adb 統計及其參數介紹
具體細節請參考: https://www.androidperformance.com/2015/12/31/How-to-calculation-android-app-lunch-time/
adb shell am start -w pageage/activityname
//通過這條指令啟動,可以獲得啟動時間。
$ adb shell am start -W com.abc.test/com.abc.test.MainActivity
Starting: Intent { act=android.intent.action.MAIN cat=
Status: ok
Activity: com.speed.test/.HomeActivity
ThisTime: 496
TotalTime: 496
WaitTime: 503
Complete
adb shell am start -W
的實作在
frameworks\base\cmds\am\src\com\android\commands\am\Am.java
檔案中。其實就是跨
Binder
調用
ActivityManagerService.startActivityAndWait()
接口(後面将
ActivityManagerService
簡稱為
AMS
),這個接口傳回的結果包含上面列印的
ThisTime
、
TotalTime
時間.
-
記錄的剛準備調用 startTime
的時間點startActivityAndWait()
-
記錄的是 endTime
函數調用傳回的時間點startActivityAndWait()
-
調用耗時。WaitTime = startActivityAndWait()
-
就是總的耗時,包括前一個應用 Activity pause 的時間和新應用啟動的時間;WaitTime
-
表示一連串啟動 Activity 的最後一個 Activity 的啟動耗時;ThisTime
-
表示新應用啟動的耗時,包括新程序的啟動和 Activity 的啟動,但不包括前一個應用 Activity pause 的耗時。也就是說,開發者一般隻要關心 TotalTime 即可,這個時間才是自己應用真正啟動的耗時。TotalTime
總結下:
- 如果隻關心某個應用自身啟動耗時,參考
;TotalTime
- 如果關心系統啟動應用耗時,參考
;WaitTime
- 如果關心應用界面
啟動耗時,參考 Activity
。ThisTime
3. 起始時間點
- 冷啟動啟動時間一般可以在
開始的位置記錄起始時間點,因為在這之前 Context 還沒有初始化,一般也幹不了什麼事情,當然這個是要視具體情況來定,其實隻要保證在 App 的具體業務邏輯開始執行之前記錄起始時間點即可。Application.attachBaseContext()
- 熱啟動啟動時間點可以在
中記錄起始時間點。Activity.onRestart()
4. 結束時間點
結束時間點理論上要選在 App 顯示出第一屏界面的時候,但是在什麼位置 App 顯示出第一屏界面呢?網上很多文章說在 Activity 的 onResume 方法執行完成之後,Activity 就對使用者可見了,實際上并不是,一個 Activity 走完onCreate onStart onResume 這幾個生命周期之後,隻是完成了應用自身的一些配置,比如 Activity 主題設定 window 屬性的設定 View 樹的建立,但是其實後面還需要各個 View 執行 measure layout draw等。是以在 OnResume 中記錄結束時間點的 Log 并不準确,大家可以注意一下上面流程中最後一個函數
Activity.onWindowFocusChanged
,下面是它的注釋:
/**
*Called when the current {@link Window} of the activity gains or loses
* focus. This is the best indicator of whether this activity is visible
* to the user. The default implementation clears the key tracking
* state, so should always be called.
...
*/
通過注釋我們可以看到,這個函數是判斷
activity
是否可見的最佳位置,是以我們可以在
Activity.onWindowFocusChanged
記錄應用啟動的結束時間點,不過需要注意的是 該函數在
Activity
焦點發生變化時就會觸發,是以要做好判斷,去掉不需要的情況。
5. 應用的主要啟動流程
- 通過
啟動應用時,點選應用圖示後,Launcher
調用 Launcher
startActivity
啟動應用。
最終調用 Launcher Activity
的 Instrumentation
來啟動應用。execStartActivity
-
調用 Instrumentation
(ActivityManagerProxy
在應用程序的一個代理對象) 對象的 ActivityManagerService
方法啟動 startActivity
。Activity
- 到目前為止所有過程都在
程序裡面執行,接下來 Launcher
對象跨程序調用 ActivityManagerProxy
(運作在 ActivityManagerService
程序)的 system_server
方法啟動應用。startActivity
-
的 ActivityManagerService
方法經過一系列調用,最後調用 startActivity
通過 zygoteSendArgsAndGetResult
發送給 socket
程序,zygote
程序會孵化出新的應用程序。zygote
-
程序孵化出新的應用程序後,會執行 zygote
類的 ActivityThread
方法。在該方法裡會先準備好 main
和消息隊列,然後調用 Looper
方法将應用程序綁定到 attach
,然後進入 ActivityManagerService
循環,不斷地讀取消息隊列裡的消息,并分發消息。loop
-
儲存應用程序的一個代理對象,然後 ActivityManagerService
通過代理對象通知應用程序建立入口 ActivityManagerService
的執行個體,并執行它的生命周期函數。Activity
總結過程就是:
- 使用者在
程式裡點選應用圖示時,會通知Launcher
啟動應用的入口AMS
Activity
-
發現這個應用還未啟動,則會通知AMS
程序孵化出應用程序,然後在這個應用程序裡執行Zygote
的ActivityThread
方法main
- 應用程序接下來通知
應用程序已啟動,AMS
儲存應用程序的一個代理對象,這樣AMS
可以通過這個代理對象控制應用程序AMS
- 然後
通知應用程序建立入口AMS
的執行個體,并執行它的生命周期函數。Activity
6. 冷啟動的流程
-> Application 構造函數
-> Application.attachBaseContext()
-> Application.onCreate()
-> Activity 構造函數
-> Activity.setTheme()
-> Activity.onCreate()
-> Activity.onStart
-> Activity.onResume
-> Activity.onAttachedToWindow
-> Activity.onWindowFocusChanged
7. Android 啟動優化
應用在冷啟動之前,要執行三個任務:
- 加載啟動App;
- App啟動之後立即展示出一個空白的Window;
- 建立App的程序;
而這三個任務執行完畢之後會馬上執行以下任務:
- 建立App對象;
- 啟動Main Thread;
- 建立啟動的Activity對象;
- 加載View;
- 布置螢幕;
- 進行第一次繪制;
而一旦App程序完成了第一次繪制,系統程序就會用
MainActivity
替換已經展示的
Background Window
,此時使用者就可以使用
App
了。

作為普通應用,
App
程序的建立等環節我們是無法主動控制的,可以優化的也就是
Application
、
Activity
建立以及回調等過程。
同樣,Google也給出了啟動加速的方向:
- 利用提前展示出來的Window,快速展示出來一個界面,給使用者快速回報的體驗;
- 避免在啟動時做密集沉重的初始化(Heavy app initialization)
- 定位問題:避免I/O操作、反序列化、網絡操作、布局嵌套等。
- 利用主題快速顯示界面;
- 異步初始化組
- 梳理業務邏輯,延遲初始化元件、操作
- 正确使用線程
- 去掉無用代碼、重複邏輯等
參考連結
- 知乎 – 怎麼計算apk的啟動時間
- Android 中如何計算 App 的啟動時間?
- google 官方文檔 – App startup time
- Android性能優化(一)之啟動加速35%