用戶希望應用程序能夠快速響應并加載。 一個啟動速度慢的應用程序不符合這個期望,可能會令用戶失望。 這種糟糕的體驗可能會導致用戶在應用商店中對您的應用進行糟糕的評價,甚至完全放棄您的應用。
本文檔提供的信息可幫助您優化應用的啟動時間。 它首先解釋啟動過程的內部。 接下來,討論如何配置啟動性能。 最后,它描述了一些常見的啟動時間問題,并提供了一些關于如何解決它們的提示
Launch Internals
應用程序啟動可以發生在三種狀態之一,每種狀態都會影響您的應用程序對用戶可見的時間長度:冷啟動,熱啟動和溫熱啟動。 冷啟動,你的應用程序從頭開始。 在其他的狀態,系統需要將應用程序從后臺引向前臺。 我們建議您始終根據冷啟動的情況進行優化。 這樣做可以改善熱啟動和溫熱啟動的性能。
為了優化您的應用程序以實現快速啟動,常常需要了解系統和應用程序級別發生了什么以及它們如何交互
冷啟動
冷啟動是指應用程序從頭開始:系統的進程在開始之前還沒有創建應用程序的進程。 冷啟動發生在您的應用程序自啟動設備以來第一次啟動的情況下,或由于系統殺死應用程序。 這種類型的啟動在縮短啟動時間方面提出了最大的挑戰,因為系統和應用比其他啟動狀態有更多的工作要做。
在冷啟動開始時,系統有三項任務。 這些任務是:
- 加載并啟動應用。
- 啟動后立即顯示應用程序的空白開始窗口。
- 創建應用進程。
只要系統創建應用程序進程,應用程序進程就負責下一個階段。 這些階段是:
- 創建該應用對象
- 啟動主線程
- 創建主activity
- 填充views
- 鋪設屏幕
執行初始繪制
一旦應用程序完成第一次繪制,系統進程就會將當前顯示的背景窗口替換為主要活動。 此時,用戶可以開始使用該應用程序。
性能問題可能在創建應用程序和創建activity時出現
Application creation
當您的應用程序啟動時,空白的起始窗口將保留在屏幕上,直到系統第一次完成繪制應用程序。 此時,系統進程為應用程序換掉了啟動窗口,允許用戶開始與應用程序進行交互
如果您在自己的應用程序中重載了Application.oncreate(),系統將調用您的應用程序對象上的onCreate()方法。 之后,應用程序會生成主線程,也稱為UI線程,并執行創建主Activity的任務。
從這一點來看,系統和應用程序級別的進程按照應用程序生命周期階段進行。
Activity的創建
應用程序進程創建您的活動后,活動執行以下操作:
- 初始化值
- 調用構造方法
- 調用回調方法,按照activity的生命周期
通常情況下,onCreate()方法對加載時間影響最大,因為它執行的開銷最高:加載和擴充視圖,并初始化活動運行所需的對象
熱啟動
應用程序的熱啟動比冷啟動更簡單,開銷更低。 在一個溫暖的開始,所有的系統都會把你的activity帶到前臺。 如果您的所有應用程序的活動仍駐留在內存中,則應用程序可以避免重復對象初始化,布局填充和渲染
但是,如果某些內存已被清除以響應內存調整事件(如onTrimMemory()),則將響應熱啟動事件重新創建這些對象。
熱啟動顯示與冷啟動場景相同的屏幕行為:系統進程顯示空白屏幕,直到應用完成activity的渲染。
Lukewarm start
一個lukewarm start包含了在冷啟動期間發生的一些操作子集; 與此同時,它代表的不是一個熱啟動的開始。 有許多潛在的狀態可以被認為是溫和的開始。 例如:
- 用戶退出應用程序,但重新啟動它。 該進程可能會繼續運行,但應用程序必須通過調用onCreate()從頭開始重新創建活動
- 系統從內存中清除您的應用程序,然后用戶重新啟動它。 進程和Activity需要重新啟動,但是任務可以從saved instance state bundle 傳遞給onCreate()
分析啟動性能
為了正確地診斷開始時間性能,您可以跟蹤顯示啟動應用程序所需時間
要重現用戶體驗,請確保以非可調試模式對應用進行配置。 可調試模式啟用調試功能,導致啟動時間非典型的用戶體驗。
初始時間顯示
從Android 4.4(API級別19),logcat包含一個輸出行,其中包含一個名為Displayed的值。 該值表示啟動過程和完成在屏幕上繪制相應活動之間所經過的時間量。 經過的時間包括以下一系列事件:
- 啟動進程
- 初始對象
- 創建并初始化activity
- 填充布局
- 在第一時間繪制你的應用
報告的日志行與以下示例類似:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms
如果您正在從命令行或終端跟蹤logcat輸出,則查找已用時間很簡單。 要在Android Studio中查找已用時間,您必須在logcat視圖中禁用篩選器。 禁用過濾器是必要的,因為系統服務器,而不是應用程序本身,服務于這個日志。
一旦你做了適當的設置,你可以輕松地搜索正確的術語來查看時間。 圖2顯示了如何禁用過濾器,并在底部輸出的第二行顯示了Displayed時間的logcat輸出示例。
Logcat輸出中的“顯示”度量不一定會捕獲所有資源加載和顯示之前的時間量:它會遺漏布局文件中未引用的資源或應用程序作為對象初始化的一部分創建的資源。 它排除了這些資源,因為加載它們是一個內嵌的過程,并且不會阻止應用程序的初始顯示
您也可以使用ADB Shell活動管理器命令運行您的應用程序來測量初始顯示的時間。 這是一個例子:
adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN
顯示的度量像以前一樣出現在logcat輸出中。 您的終端窗口還應顯示以下內容:
Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 2044
TotalTime: 2044
WaitTime: 2054
Complete
-c和-a參數是可選的,并讓您為intent指定和
完全呈現的時間
您可以使用reportFullyDrawn()方法來測量應用程序啟動和完成顯示所有資源和視圖層次之間的已用時間。 在應用執行延遲加載的情況下,這可能很有價值。 在延遲加載中,應用程序不會阻止窗口的初始繪制,而是異步加載資源并更新視圖層次結構。
如果由于延遲加載,應用程序的初始顯示不包含所有資源,則可以將所有資源和視圖的加載和顯示視為一個單獨的度量標準:例如,您的UI可能已完全加載,繪制了一些文本, 但還沒有顯示應用程序必須從網絡中獲取的圖像。
為了解決這個問題,您可以手動調用reportFullyDrawn()讓系統知道您的活動已經完成了延遲加載。 使用此方法時,logcat顯示的值是從創建應用程序對象到調用reportFullyDrawn()的時間。 這里是一個logcat輸出的例子:
system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms
logcat輸出有時會包含一個總時間,正如在“初始顯示時間”中所述
如果你知道你的顯示時間比你想要的慢,你可以繼續嘗試確定啟動過程中的瓶頸
查找瓶頸的兩個好方法是Android Studio方法跟蹤工具和內聯跟蹤。 要了解Method Tracer,請參閱工具文檔。
如果您無法訪問Method Tracer工具,或無法在正確的時間啟動該工具以獲取日志信息,則可以通過在應用程序和活動的onCreate()方法中進行內嵌跟蹤來獲得類似的洞察。 要了解內聯跟蹤,請參閱跟蹤功能參考文檔以及Systrace工具
常見問題
本節討論經常影響應用程序啟動性能的幾個問題。 這些問題主要涉及初始化應用程序和活動對象,以及加載屏幕
沉重的應用初始化
當您的代碼覆蓋Application對象時,啟動性能會受到影響,并在初始化該對象時執行繁重的工作或復雜的邏輯。 如果您的應用程序子類執行不需要完成的初始化,您的應用程序可能會浪費時間在啟動過程中。 一些初始化可能是完全不必要的:例如,當應用程序實際啟動以響應意圖時,初始化主要活動的狀態信息。 意圖是,應用程序只使用以前初始化的狀態數據的一個子集。
應用程序初始化過程中的其他問題包括垃圾收集事件的影響或數量眾多,或磁盤I / O與初始化同時發生,進一步阻止初始化過程。 垃圾收集尤其是Dalvik運行時的一個考慮因素; Art運行時同時執行垃圾收集,最大限度地減少操作的影響
診斷問題
您可以使用方法跟蹤或內聯跟蹤來嘗試診斷問題。
方法追蹤
運行Method Tracer工具顯示callApplicationOnCreate()方法最終調用com.example.customApplication.onCreate方法。 如果該工具顯示這些方法需要很長時間才能完成執行,那么您應該進一步研究以查看正在進行的工作。
內聯追蹤
使用內聯追蹤來調查可能的罪魁禍首,包括:
- 你的應用初始onCrate() 方法
- 全局的單例對象初始
任何磁盤I / O,反序列化,或瓶頸期間可能發生的緊密循環
解決問題
無論問題出在不必要的初始化還是磁盤I / O,解決方案都會調用延遲初始化對象:只初始化那些立即需要的對象。 例如,不是創建全局靜態對象,而是移動到單例模式,應用程序只在第一次訪問對象時創建對象。 另外,考慮使用像Dagger這樣的依賴注入框架來創建對象,并且依賴關系是當它們被首次注入時繁重的activity 初始化
activity創建通常需要大量的高開銷工作。 通常,有機會優化這項工作來實現性能改進。 這些常見問題包括:填充龐大或者復雜的布局
- 阻塞磁盤的屏幕繪圖或網絡I / O
- 加載并解碼圖片
- 柵格化VectorDrawable對象
- 在activity初始化其他子系統
解決問題重點內容
在這種情況下,方法跟蹤和內聯跟蹤也是有用的
Method tracing
當運行Method Tracer工具時,特定的區域將關注于您的應用程序的Application子類的構造函數和com.example.custom的Application.onCreate()方法。
如果該工具顯示這些方法需要很長時間才能完成執行,那么您應該進一步研究以查看正在進行的工作。
Inline tracing
使用內聯追蹤來調查可能的罪魁禍首,包括:
- 你的應用初始onCreate() 方法
- 全局的單例對象初始
任何磁盤I / O,反序列化,或瓶頸期間可能發生的緊密循環
解決問題
有很多潛在的瓶頸,但是兩個常見的問題和解決方法如下視圖層次越大,應用所需的時間就越多。 你可以采取兩個步驟來解決這個問題
- 通過減少冗余或嵌套布局來平坦您的視圖層次結構
- 不要在啟動時填充不需要顯示的部分UI。使用ViewStub對象作為應用程序可以在更合適的時間填充的子層次結構的占位符來代替。
在主線程上完成所有的資源初始化操作也會減慢啟動速度。 你可以解決這個問題如下:
- 把所有可以通過懶加載的初始資源放到不同的線程去執行
- 允許應用加載展示你的視圖,并且可以稍后跟新視覺屬性通過bitmaps 和其他資源。
主題啟動屏幕
您可能希望主題化您的應用的加載體驗,以便應用的啟動屏幕與應用的其余部分在主題上保持一致,而不是與系統主題一致。 這樣做可以隱藏一個緩慢的activity 啟動。
實現主題啟動屏幕的常用方法是使用windowDisablePreview主題屬性來關閉啟動應用程序時系統進程繪制的初始空白屏幕。 但是,這種方法會導致比不抑制預覽窗口的應用程序更長的啟動時間。 此外,它強制用戶在活動啟動時等待而沒有任何反饋,使他們不知道該應用程序是否運行正常。
診斷問題
您可以通過觀察用戶啟動應用程序時的慢速響應來經常診斷此問題。 在這種情況下,屏幕可能會被凍結,或者停止響應輸入
解決問題
我們建議您不要禁用預覽窗口,而要遵循常見的Material Design模式。 您可以使用該活動的windowBackground主題屬性為開始活動提供一個簡單的自定義繪圖。
例如,您可以創建一個新的可繪制文件,并從布局XML和應用程序清單文件中引用它,如下所示:
布局文件:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque"><!-- The background color, preferably the same as your normal theme --><item android:drawable="@android:color/white"/><!-- Your product logo - 144dp color version of your app icon --><item><bitmap
android:src="@drawable/product_logo_144dp"android:gravity="center"/></item>
</layer-list>
Manifest file:
<activity ...
android:theme="@style/AppTheme.Launcher" />
轉換回正常主題最簡單的方法是在調用super.onCreate()和setContentView()之前調用setTheme(R.style.AppTheme):
public class MyMainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {// Make sure this is before calling super.onCreatesetTheme(R.style.Theme_MyApp);super.onCreate(savedInstanceState);// ...}
}