本文從谷歌官網翻譯: developer.android.google.cn/topic/perfo…
使用者希望App啟夠足夠快的開始啟動,如果一個App啟動時間過長,會令使用者非常失望,並且可能會在play store中對App評價很低或者乾脆解除安裝我們的App.
這篇文章主要是提供了一種能夠優化我們App啟動時間的方法。文章首先會描述我們App的啟動過程,接下來會討論一下我們應該怎樣優化App的啟動效能。最後,文章會闡述一下我們在啟動優化過程中常見的問題,並且提出一些優化建議。
理解App的啟動過程
App啟動主要有三種狀態,每種狀態都會影響你的應用對使用者的可見所需的時間:冷啟動、溫啟動、熱啟動。熱啟動下,App會從頭開始啟動,溫啟動和冷啟動應用會從後臺切換到前臺。我們建議你應該基於冷啟動進行優化,這樣做也可以改善熱啟動和熱啟動的效能。
為了更好的優化App啟動效能,我們需要了解每一種狀態下,系統和App級別內部是怎樣工作的。
冷啟動
冷啟動是指應用從頭開始啟動:系統的程式還沒有建立應用程式的程式。冷啟動發生在比如裝置啟動後的首次啟動應用程式,或者系統殺死了應用程式程式後。冷啟動相比較於溫啟動和熱啟動,需要優化的工作更多挑戰更大。
在冷啟動開始開始,系統需要做三個主要的工作:
- 匯入和啟動App
- 啟動之後立即顯示一個空的Window窗體
- 建立App程式
一旦系統建立了App程式,App程式接下來會負責以下幾個步驟的開始:
- 建立App物件
- 啟動主程式
- 建立主Activity
- Inflating View
- 填充螢幕佈局
- 執行初始化繪製
App程式完成初始化繪製後,會把當前main Activity替換為當前顯示的背景視窗,這個時候使用者就可以開始使用App了。
圖1顯示了系統和App程式兩者之間是怎樣進行互動的
圖1 冷應用程式啟動的重要部分的直觀表示
圖中所知,產生的效能問題主要集中在建立Applicaition和建立Activity中
Applicaition的建立
當你的Applicaition啟動的時候,系統會建立一個空白的window視窗,直到應用第一次繪製完頁面才會消失。繪製完之後,使用者才可以開始使用App。
假如你對Application.onCreate()
方法進行過載,系統會在應用程式物件上呼叫onCreate()方法。之後,應用程式會生成主執行緒(也稱為UI執行緒),並通過建立main activcity來執行任務。
從這一點開始,系統和應用程式級別的流程將根據應用程式生命週期階段進行。
Activity的建立
在Application 啟動完成之後,App程式開始建立Activity,Activity的執行是按照以下幾個步驟進行的:
- 初始化值
- 呼叫構造方法
- 呼叫生命週期回撥方法,例如
Activity.onCreate()
等等
通常來說onCreate
方法是開銷最大的操作,因為在該方法中需要執行inflating views、初始化所需物件等等操作
熱啟動
熱啟動相比於冷啟動來說,熱啟動更簡單,花銷更少。在熱啟動中,主要開銷均來自於把Activity顯示到前臺來。如果應用程式的Activity仍然駐留在記憶體中,那麼應用程式可以避免重複物件初始化,佈局Inflating和渲染。
然後,在低記憶體情況下仍然可能會被回收掉,例如onTrimMemory()
方法被回撥,這個時候物件需要被重新建立。
熱啟動和冷啟動的螢幕顯示行為是一樣:系統啟動會也會先顯示一個空白的螢幕,直到App完成渲染和Activity的顯示。
溫啟動
溫啟動是冷啟動的一個子集,同樣的情況,它比熱啟動花銷更少,溫啟動情況下,有許多潛在的狀態可以被重用,例如:
- 使用者退出應用,但隨後重新啟動它。該程式可能還在繼續執行,但應用程式必須通過呼叫onCreate()從頭開始重新建立acitivty。
- App的記憶體被系統回收情況下,使用者重新啟動App,App程式和Activity需要被重新建立。但是它們可以從onCreate方法儲存的bundle中恢復。
檢測並診斷問題
Android提供了幾種方法可以知道你的App存在的一些問題。並且幫助你去診斷你的App。Android vitals能夠提醒你出現的問題,並且提供一些診斷工具去幫助你。
Android vitals
Android vitals能夠幫你提升App的效能並且能夠通過Play Console提供警告,當你App滿足一下幾個點的時候,Android vitals會認為你的App時間過長:
- 冷啟動超過5s或者更長
- 溫啟動2s或者更長
- 熱啟動1.5s或者更長
有一個每日使用記錄來記錄App的啟動時常。
Android vitals不會提供一個熱啟動的報告。關於Android vitals的更多資訊,請訪問Play Console 文件。
找出緩慢啟動時間的原因
為了找出你App啟動緩慢的原因,你需要跟蹤applicaition啟動指標。
顯示的初始化時間
在Android4.4(API 19)或者更高,logcat包含一個包含名為Displayed的值的輸出行,此值表示啟動過程和完成在螢幕上繪製相應Activity之間所經過的時間。經過的時間包括以下事件序列:
- 啟動App程式
- 初始化App物件
- 建立和初始化Activity
- Inflating 佈局
- 第一次繪製應用
報告的格式大概如下:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms
複製程式碼
如果您正在從命令列或終端中跟蹤logcat輸出,找出花費的時間是非常簡單的。在Android Studio中查詢已用時間,必須在logcat檢視中禁用過濾器。禁用過濾器是必要的,因為該日誌是系統級別的。
完成適當的設定後,您可以輕鬆搜尋正確的關鍵字以檢視時間。圖2顯示瞭如何禁用過濾器,以及在底部的第二行輸出中顯示了顯示時間的logcat輸出示例。
圖二
logcat中顯示的時間不是載入和顯示所有資源的時間:它會不包括未在佈局檔案中引用的資源,它只會建立初始化物件所需要的一部分資源。排除這些無關資源是因為匯入是一個聯絡的過程,只要不阻塞app初始化完成即可。(譯者注:意思是這裡顯示的時間度量不是載入完所有資源,它只會按需載入)
有的時候Displayed
標籤會輸出一個總時間,例如:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)
這種情況下,第一個時間表示的是當前Activity繪製所花費的時間,total 時間代表的是從測量app啟動開始時間,包括其它未顯示在螢幕上的Activity時間。total time顯示是為了顯示單個Activity和總共啟動時間的對比。
你也能夠通過執行ADB Shell Activity Manager
命令來顯示app初始化時間,例如:
adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN
複製程式碼
這裡Displayed
表現顯示和前面一樣,都是顯示在logcat中。在logcat會顯示如下:
Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 2044
TotalTime: 2044
WaitTime: 2054
Complete
複製程式碼
其中-c
和-a
引數可以對intent設定<category>
和<action>
完整顯示時間
使用reportFullyDrawn()
方法來度量應用程式啟動和完全顯示所有資源和檢視層次結構所用的時間。在app執行延遲載入的情況下,這可能很有用。在延遲載入中,應用程式不會阻止視窗的初始繪製,而是非同步載入資源並更新檢視層次結構。
如果由於延遲載入,應用程式的初始顯示不包含所有資源,可以將所有資源和檢視的完成載入和顯示視為單獨的度量標準:例如,您的UI可能已完全載入,並繪製了一些文字,但尚未顯示從網路中拉取的圖片資源。
為了解決這個問題,你需要手動呼叫reportFullyDrawn()
這個方法讓系統知道你的Activity是使用懶載入的方式。使用此方法時,logcat顯示的值是從建立應用程式物件到呼叫reportFullyDrawn()
的時間。這是logcat輸出的一個例子:
system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms
複製程式碼
logcat中輸出的是total time,前面顯示的初始化時間
已經討論過了
如果App到顯示時間比預想的要慢,可以繼續嘗試識別啟動過程中的存在的瓶頸。
識別瓶頸
一個比較好的方式是使用Android studio提供的CPU效能分析工具,關於CPU效能分析工具更多的資訊可以檢視Inspect CPU Activity with CPU Profiler。
您還可以通過跟蹤Application和Activity的onCreate()
方法來深入瞭解潛在的瓶頸。要了解內聯跟蹤,請參閱跟蹤功能的文件以及Systrace 工具。
常見問題
本節討論影響效能的幾個常見問題,這些問題主要涉及初始化應用程式和活物件,以及螢幕的載入。
大型應用的初始化
當你對Application
寫入大量初始化或者其它一些工作的時候,會影響應用的啟動效能。App需要浪費很多時間去執行子類複寫的方法。有一些 初始化的工作不是必須的:例如為main Activity初始化一些並不需要的狀態資訊。
其它的一些問題包括在App初始化期間產生大量的需要GC的事件,或者磁碟I/O頻繁讀取。尤其是Dalvik執行時的垃圾收集; Art runtime同時執行垃圾收集,最大限度地減少操作的影響。
找出問題
可以使用tracing或者內聯tracing的方式去找到問題所在。
方法 tracing
執行CPU Profiler工具檢視能夠呼叫你自定義的com.example.customApplication.onCreate
的 callApplicationOnCreate()
方法,如果該工具顯示這些方法需要很長時間才能完成執行,那麼您應該進一步探索以檢視正在進行的工作。
Inline tracing
使用Inline tracing來找出可能出現的問題:
- app中onCreate( 初始化方法
- 使用app的全域性單例物件
- 可能發生的任何磁碟I / O,反序列化或迴圈的地方
解決問題
如果問題的產生的原因是因為密集的磁碟I/O造成的,解決方法就是僅僅載入必要的物件,其它物件通過懶載入的方式進行物件的初始化。例如,通過建立單例來替代建立靜態物件,此外考慮使用像Dagger
這樣的依賴注入框架來建立物件,並在第一次注入它們時依賴它們。
大型Activity初始化
建立Activity通常需要大量高額開銷,通常可以優化這建立過程實現效能的改進。這些優化點包括:
- 優化複雜佈局
- 減少阻塞頁面的磁碟和網路I/O
- bitmaps載入優化
- 柵格化
VectorDrawable
物件 - 初始化其它Activity的子系統
找出問題
可以通過tracing 或者 inline tracing來提供有用的幫助
方法tracing
通過CPU profiler工具來分析自定義Application的子類onCreate()
方法。
如果該工具表明你的方法需要執行很長的時間,需要進一步找出時間開銷的分佈,從而解理解問題是怎麼產生的。
Inline tracing
使用Inline tracing來找出可能出現的問題:
- app中onCreate( 初始化方法
- 使用app的全域性單例物件
- 可能發生的任何磁碟I / O,反序列化或迴圈的地方
解決問題
產生效能問題的原因有很多,但是通常來說有2個主要問題。
-
複雜多層次的佈局,需要App花更多的時間來載入它。2個步驟能夠解決這個問題
- 通過減少冗餘或巢狀佈局來減少檢視層次結構。
- 不需要開始就顯示的佈局,可以通過ViewStup物件來載入
-
在主執行緒上進行所有資源初始化也會降低啟動速度。可以按如下方式解決此問題:
- 通過子執行緒進行資源的初始化工作
- 可以使用類似於佔位的方式來顯示佈局,等對應資源載入出來後再顯示上去。
App啟動主題
可以通過自定義主題的方式來啟動App,這樣可以隱藏Activity顯示緩慢的問題。
比較常見的方式就是使用windowDisablePreview主題屬性來替代App啟動時候的空白頁面。但是,與普通主題App相比,此方法可以導致更長的啟動時間。此外它會強制使用者在活動啟動時等待,並且不能知道當前程式是否執行正常。
找出問題
您通常可以在使用者啟動應用時觀察響應緩慢來找出問題。在這種情況下螢幕似乎被凍結,或者已經停止響應輸入。
解決問題
我們建議您不要禁用預覽視窗,而是遵循常見的Material Design模式。您可以使用Activity的windowBackground主題屬性為啟動活動提供簡單的自定義drawable。
例如,您可以建立一個新的可繪製檔案,並從佈局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" />
複製程式碼
在Activity中通過呼叫``setTheme(R.style.AppTheme)方法來設定當前頁面的主題,
class MyMainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Make sure this is before calling super.onCreate
setTheme(R.style.Theme_MyApp)
super.onCreate(savedInstanceState)
// ...
}
}
複製程式碼