天天看點

Android 啟動時長分析

應用啟動介紹

我們平時在寫應用的時候,一般會指定一個 ​

​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()​

    ​ 調用耗時。
  • ​WaitTime​

    ​ 就是總的耗時,包括前一個應用 Activity pause 的時間和新應用啟動的時間;
  • ​ThisTime​

    ​ 表示一連串啟動 Activity 的最後一個 Activity 的啟動耗時;
  • ​TotalTime​

    ​ 表示新應用啟動的耗時,包括新程序的啟動和 Activity 的啟動,但不包括前一個應用 Activity pause 的耗時。也就是說,開發者一般隻要關心 TotalTime 即可,這個時間才是自己應用真正啟動的耗時。

總結下:

  • 如果隻關心某個應用自身啟動耗時,參考 ​

    ​TotalTime​

    ​;
  • 如果關心系統啟動應用耗時,參考 ​

    ​WaitTime​

    ​;
  • 如果關心應用界面 ​

    ​Activity​

    ​ 啟動耗時,參考 ​

    ​ThisTime​

    ​。

3. 起始時間點

  • 冷啟動啟動時間一般可以在​

    ​Application.attachBaseContext()​

    ​ 開始的位置記錄起始時間點,因為在這之前 Context 還沒有初始化,一般也幹不了什麼事情,當然這個是要視具體情況來定,其實隻要保證在 App 的具體業務邏輯開始執行之前記錄起始時間點即可。
  • 熱啟動啟動時間點可以在​

    ​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​

    ​ 的執行個體,并執行它的生命周期函數。

總結過程就是:

  1. 使用者在​

    ​Launcher​

    ​​ 程式裡點選應用圖示時,會通知​

    ​AMS​

    ​​ 啟動應用的入口​

    ​Activity​

  2. ​AMS​

    ​​ 發現這個應用還未啟動,則會通知​

    ​Zygote​

    ​​ 程序孵化出應用程序,然後在這個應用程序裡執行​

    ​ActivityThread​

    ​​ 的​

    ​main​

    ​ 方法
  3. 應用程序接下來通知​

    ​AMS​

    ​​應用程序已啟動,​

    ​AMS​

    ​​儲存應用程序的一個代理對象,這樣​

    ​AMS​

    ​可以通過這個代理對象控制應用程序
  4. 然後​

    ​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​

​了。

Android 啟動時長分析

作為普通應用,​

​App​

​ 程序的建立等環節我們是無法主動控制的,可以優化的也就是 ​

​Application​

​、​

​Activity​

​ 建立以及回調等過程。

同樣,Google也給出了啟動加速的方向:

  • 利用提前展示出來的Window,快速展示出來一個界面,給使用者快速回報的體驗;
  • 避免在啟動時做密集沉重的初始化(Heavy app initialization)
  • 定位問題:避免I/O操作、反序列化、網絡操作、布局嵌套等。
  • 利用主題快速顯示界面;
  • 異步初始化組
  • 梳理業務邏輯,延遲初始化元件、操作
  • 正确使用線程
  • 去掉無用代碼、重複邏輯等

參考連結

  • ​​知乎 – 怎麼計算apk的啟動時間​​
  • ​​Android 中如何計算 App 的啟動時間?​​
  • ​​google 官方文檔 – App startup time​​
  • ​​Android性能優化(一)之啟動加速35%​​