應用程式啟動時間

透過集合功能整理內容 你可以依據偏好儲存及分類內容。

使用者希望應用程式可以快速回應和載入。應用程式如果啟動時間緩慢,不但不符合此期望,還可能會讓使用者感到失望。此類不佳體驗可能會導致使用者在 Play 商店中對您的應用程式給予負評,或甚至解除安裝應用程式。

這份文件提供的資訊可協助您最佳化應用程式的啟動時間。 首先我們要說明啟動程序內部運作流程。然後討論如何分析啟動效能。最後,本文將說明一些常見的啟動問題,並提供一些解決問題的訣竅。

瞭解不同應用程式啟動狀態

應用程式啟動可能有三種狀態,每種狀態都會影響顯示應用程式所需的時間長度:冷啟動、暖啟動或熱啟動。冷啟動就表示應用程式從頭開始啟動。在其他狀態中,系統需要將執行中的應用程式從背景移至前景。建議您一律以冷啟動為假設的狀態以進行最佳化。這麼做也可以改善暖啟動和熱啟動的效能。

如要最佳化應用程式快速啟動,建議您瞭解各系統和應用程式層級所發生的情況,以及各狀態如何互動。

冷啟動

冷啟動是指應用程式要從頭開始執行:從開始到建立此程序之前,系統程序皆尚未完成建立。冷啟動是指在裝置啟動後首次次啟動應用程式,或是系統終止應用程式後首次啟動應用程式。這種啟動方式很難將啟動時間縮減到最短,因為相較於其他啟動狀態,系統和應用程式需要執行更多工作。

在冷啟動開始時,系統會執行三項工作。這些工作包括:

  1. 載入和啟動應用程式。
  2. 啟動後,隨即在應用程式中顯示空白的起始視窗。
  3. 建立應用程式程序

系統建立應用程式程序後,應用程式程序就會負責進行下一個階段:

  1. 建立應用程式物件。
  2. 啟動主要執行緒。
  3. 建立主要活動。
  4. 加載檢視畫面。
  5. 版面配置螢幕畫面。
  6. 執行初始繪圖。

應用程式程序完成第一個繪圖後,系統程序會關閉目前顯示的背景視窗,並以主要活動取代。此時使用者就可以開始使用應用程式。

圖 1 顯示系統和應用程式程序如何交互處理。

圖 1:以視覺化方式呈現應用程式冷啟動的重要部分。

應用程式建立和活動建立期間可能會出現的效能問題。

應用程式建立

應用程式啟動時,畫面中會持續顯示空白的啟動視窗,直到系統完成應用程式繪圖作業為止。此時,系統程序會關閉應用程式的啟動視窗,讓使用者開始可以與應用程式互動。

如果在應用程式中覆寫 Application.onCreate(),系統就會在應用程式物件中叫用 onCreate() 方法。之後,應用程式就會產生主要執行緒 (也稱為 UI 執行緒),然後透過建立主要活動以執行該工作。

從這個時候開始,系統和應用程式層級的程序會根據應用程式生命週期階段繼續進行。

活動建立

應用程式程序建立活動後,該活動會執行下列作業:

  1. 初始化值。
  2. 呼叫建構函式。
  3. 依照目前的活動生命週期狀態呼叫回呼方法,例如:Activity.onCreate()

一般而言,onCreate() 方法對於載入時間會造成最大的影響,因為這會執行負載最高的工作:載入和加載檢視畫面,以及初始化執行活動所需的物件。

暖啟動

暖啟動包含冷啟動期間的部分作業,而且負載比熱啟動高。暖啟動期間有許多可能的狀態必須納入考量。例如:

  • 使用者退出應用程式,然後又重新啟動。此時程序可能仍會繼續執行,但應用程式必須呼叫 onCreate(),才能從頭開始重新建立活動。

  • 系統會從記憶體中清除應用程式,然後使用者再重新啟動應用程式。程序和活動必須重新啟動,但由於 onCreate() 中傳入的執行個體狀態組合已經儲存,因此可以減輕系統的工作量。

熱啟動

應用程式的熱啟動作業比冷啟動更簡單、成本更低。熱啟動時,系統只需要將活動移到前景即可。如果應用程式的所有活動仍留在記憶體中,應用程式就不需要重複初始化物件、版面配置加載和轉譯。

不過如果有些記憶體已因回應記憶體剪輯事件 (例如 onTrimMemory()) 而遭到清除,就需要重新建立這些物件才能回應熱啟動事件。

熱啟動顯示的畫面行為與冷啟動情境相同:

系統程序會顯示空白畫面,直到應用程式完成活動轉譯為止。

圖 2:此圖表顯示多種不同的啟動狀態及其各自的程序,並從第一個繪製的影格開始顯示每種狀態。

使用指標偵測及診斷問題

為了正確診斷開始時間的效能,您可以追蹤顯示應用程式啟動所需時間的指標。Android 提供多種可以讓您得知應用程式出現問題的方法,並可以幫助您診斷問題原因。Android Vitals 會提醒您發生問題,診斷工具則可協助您診斷問題。

使用啟動指標的優點

Android 會使用「初始顯示時間」和「完整顯示時間」指標,為冷啟動和暖啟動應用程式進行最佳化調整。Android 執行階段 (ART) 會使用這些指標中的資料,以有效率的方式預先編譯程式碼,針對未來的啟動程序進行最佳化。

啟動速度越快,就越能吸引使用者持續與應用程式互動,進而減少提前結束、重新啟動執行個體或離開並前往其他應用程式的情形。

Android Vitals

當應用程式啟動時間過長時,Android Vitals 會透過 Play 管理中心 發出提醒,藉此改善應用程式的效能。如果遇到以下情況,Android Vitals 會將應用程式的啟動時間視為過長:

  • 啟動所需時間超過 5 秒。
  • 啟動所需時間超過 2 秒。
  • 啟動所需時間超過 1.5 秒。

Android Vitals 會使用「初始顯示時間」指標。如要瞭解 Google Play 如何收集 Android Vitals 資料,請參閱 Play 管理中心說明文件。

初始顯示時間

初始顯示時間 (TTID) 指標可測量應用程式需要多久時間才能產生第一個影格,其中包括初始化程序 (冷啟動)、建立活動 (冷/暖啟動) 以及顯示第一個影格。

如何擷取 TTID

在 Android 4.4 (API 級別 19) 以上版本中,logcat 包含的輸出行中有名為 Displayed 的值。這個值代表程序啟動,以及完成畫面中對應活動之間的經過時間。經過時間涵蓋下列事件順序:

  • 啟動程序
  • 初始化物件。
  • 建立並初始化活動。
  • 加載版面配置。
  • 首次繪製應用程式。

回報的記錄行與下列範例類似:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms

如果是從指令列或在終端機中追蹤 Logcat 輸出內容,那麼找出經過時間就會相當簡單。如要在 Android Studio 中找出經過時間,您必須在 Logcat 檢視畫面中停用篩選器。由於系統伺服器 (非應用程式本身) 會提供此記錄,因此必須停用篩選器。

完成適當的設定後,您就可以輕鬆地搜尋正確的字詞以查看時間。圖 2 範例說明如何停用篩選器,並在倒數第二行輸出內容中顯示 Displayed 的 Logcat 輸出內容。

圖 2:停用篩選器,然後在 logcat 中尋找「Displayed」(顯示) 的值。

在所有資源皆已載入並顯示前,Logcat 輸出內容中的 Displayed 指標不一定會擷取所需時間:沒有在版面配置檔案中加入參考,或是應用程式為物件初始化建立的資源不會計算在內。由於這些資源會以內嵌程序載入,而且不會封鎖應用程式的初始顯示,因此會予以排除。

有時 Logcat 輸出內容的 Displayed 行中會包含總時間的額外欄位。例如:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)

在這種情況下,首次測量僅適用於首次繪製的活動。total 時間測量從應用程式處理程序開始,而且可能包含第一次開始但未在螢幕上顯示的任何活動。只有在單一活動和總啟動時間出現差異時,系統才會顯示 total 時間測量結果。

您也可以使用 ADB Shell Activity Manager 指令執行應用程式,以測量初始顯示時間。以下將舉例說明:

adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN</pre>

如前所述,Displayed 指標會在 logcat 輸出內容中顯示。您的終端機視窗應會顯示以下內容:

Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 2044
TotalTime: 2044
WaitTime: 2054
Complete

-c-a 是選用引數,可讓您指定 <category><action>

完整顯示時間

「完整顯示時間」(TTFD) 指標可測量應用程式需要多久時間才能產生第一個完整內容影格,包括在第一個影格之後以非同步方式載入的內容。一般來說,這是應用程式從網路載入的主要清單內容,由應用程式回報。

如何擷取 TTFD

您可以使用 reportFullyDrawn() 方法測量應用程式啟動到完整顯示資源和檢視區塊階層之間的經過時間。如果應用程式會執行延遲載入,這項功能就非常實用。在延遲載入中,應用程式不會封鎖視窗的初始繪圖,而是以非同步方式載入資源並更新檢視區塊階層。

如果因為延遲載入導致應用程式的初始顯示不包含所有資源,您可以將所有資源和檢視畫面的完整載入與顯示視為獨立指標。舉例來說,UI 也許會完全載入,而且會繪製某些文字,但尚未顯示應用程式必須從網路擷取的圖片。

如要解決這個問題,您可以手動呼叫 reportFullyDrawn(),讓系統知道您的活動已透過延遲載入完成。使用此方法時,logcat 顯示的值為從建立應用程式物件到呼叫 reportFullyDrawn() 時刻所經過的時間。以下是 Logcat 輸出內容的範例:

system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms

Logcat 輸出內容有時會包含 total 時間,如「初始顯示時間」所述。

如果發現顯示時間比平常慢,可以繼續在啟動過程中找出瓶頸。

找出瓶頸

如要找出瓶頸,使用 Android Studio CPU 分析器是一個很好的方式。 相關資訊請參閱「使用 CPU 分析器檢查 CPU 活動」。

此外,您也可以透過應用程式和活動 onCreate() 方法中的內嵌追蹤記錄,深入瞭解潛在瓶頸。如要瞭解內嵌追蹤記錄,請參閱 Trace 函式的說明文件和「系統追蹤總覽」。

注意常見問題

本節將討論會影響應用程式啟動效能的幾個問題。這些問題主要與初始化應用程式和活動物件,以及載入畫面有關。

大量應用程式初始化

程式碼覆寫 Application 物件時,啟動效能可能會受到影響,並在初始化物件時執行繁雜的工作或複雜邏輯。如果應用程式子類別會執行尚未執行的初始化作業,應用程式就可能會在啟動期間浪費時間。某些初始化作業可能並非必要:例如,當應用程式實際啟動回應意圖時,即可初始化主要活動的狀態資訊。透過意圖,應用程式就只會使用先前初始化的狀態資料子集。

應用程式初始化期間的其他挑戰包括具影響力或多餘的垃圾收集事件,或與初始化同時進行磁碟 I/O,進一步封鎖初始化程序。使用 Dalvik 執行階段時,垃圾收集是特別需要考量的部分;ART 執行階段會同時執行垃圾收集,將作業的影響降到最低。

診斷問題

您可以利用方法追蹤記錄或內嵌追蹤記錄,嘗試診斷問題所在。

方法追蹤記錄

執行 CPU 分析器會顯示 callApplicationOnCreate() 方法最終會呼叫 com.example.customApplication.onCreate 方法。如果工具顯示這些方法需要很長的時間才能完成執行,您就應進一步探索,看看目前發生了哪些作業。

內嵌追蹤記錄

使用內嵌追蹤記錄調查可能的問題情況,包括:

  • 應用程式的初始 onCreate() 函式。
  • 應用程式初始化的任何全域單例模式物件。
  • 瓶頸期間可能會發生的任何磁碟 I/O、去序列化或密集迴圈。

問題解決方案

不論問題是否在於非必要的初始化還是磁碟 I/O,解決方案都是延遲初始化。換句話說,您只應初始化立即需要的物件。與其建立全域靜態物件,請改用單例模式,讓應用程式僅在首次需要的時候初始化物件。

此外,請考慮使用 Hilt 等依附元件植入架構,以在第一次插入物件時建立物件和依附元件。

如果應用程式使用內容供應器初始化應用程式元件,請考慮改用應用程式啟動程式庫

大量活動初始化

活動製作通常需要大量的高負載工作。通常這種工作都可以進行最佳化以提升效能。這類常見問題包括:

  • 加載大型或複雜的版面配置。
  • 禁止在磁碟或網路 I/O 上繪圖。
  • 載入和解碼點陣圖。
  • 光柵處理 VectorDrawable 物件。
  • 活動的其他子系統初始化。

診斷問題

在這種情況下,方法追蹤記錄和內嵌追蹤記錄也非常實用。

方法追蹤記錄

使用 CPU 分析器時,請留意應用程式的 Application 子類別建構函式和 com.example.customApplication.onCreate() 方法。

如果工具顯示這些方法需要很長的時間才能完成執行,您就應進一步探索,看看目前發生了哪些作業。

內嵌追蹤記錄

使用內嵌追蹤記錄調查可能的問題情況,包括:

  • 應用程式的初始 onCreate() 函式。
  • 應用程式初始化的任何全域單例模式物件。
  • 瓶頸期間可能會發生的任何磁碟 I/O、去序列化或密集迴圈。

問題解決方案

潛在瓶頸有很多,以下是其中兩個常見的問題及補救方式:

  • 檢視區塊階層越大,應用程式就需要越多時間加載。 如要解決這個問題,您可以採取以下兩個步驟:
    • 減少多餘或巢狀版面配置,從而簡化您的檢視區塊階層。
    • 不要加載無須在啟動期間顯示的使用者介面部分。請改用 ViewStub 物件做為子階層的預留位置,讓應用程式可在更合適的時間加載。
  • 在主執行緒完成所有資源初始化作業也可能會降低啟動速度。您可以按照下列步驟解決這個問題:
    • 移動所有資源初始化作業,讓應用程式可在不同的執行緒上逐步執行。
    • 允許應用程式載入及顯示檢視畫面,然後再更新依附於點陣圖和其他資源的視覺屬性。

自訂啟動畫面

如果您之前曾使用以下其中一種方法在 Android 11 (API 級別 30) 以下版本中實作自訂啟動畫面,可能會發現啟動時需要更多時間:

  • 使用 windowDisablePreview 主題屬性關閉啟動期間系統繪製的初始空白畫面。
  • 使用專屬活動。

自 Android 12 起必須遷移至 SplashScreen API。此 API 可加快啟動時間,並可讓您以下列方式微調啟動畫面:

此外,compat 程式庫會向後移植 SplashScreen API,藉此獲得回溯相容性,並讓所有 Android 版本的啟動畫面顯示一致的外觀和風格。

詳情請參閱「啟動畫面遷移指南」。