不平凡軟件,始于2014
您當前的位置:首頁 > 軟件開發(fā)知識>詳細
【鄭州軟件開發(fā)】Android 性能優(yōu)化--啟動時間優(yōu)化
請保持淡定,分析代碼,記?。盒阅芎苤匾?。
毫無疑問,應(yīng)用的啟動速度越快越好。
本文可以幫助你優(yōu)化應(yīng)用的啟動時間:首先解釋啟動過程內(nèi)部機制;然后討論如何分析啟動性能;最后,描述了一些常見的影響啟動時間的問題,并就如何解決這些問題給出一些提示。
應(yīng)用的啟動有三種狀態(tài),不同狀態(tài)的啟動時長是不一樣的。三種狀態(tài)分別為:冷啟動(cold start),熱啟動(warm start),溫啟動(lukewarm start)。冷啟動即應(yīng)用從零開始加載運行,而其它則是應(yīng)用從后臺運行回到前臺運行。建議您始終基于冷啟動的假設(shè)進行優(yōu)化,因為這樣做同樣提升了另兩種啟動狀態(tài)的表現(xiàn)。
要使得應(yīng)用能快速啟動,需要先理解應(yīng)用以不同狀態(tài)啟動時,系統(tǒng)和應(yīng)用內(nèi)發(fā)生了什么,以及它們?nèi)绾蜗嗷プ饔谩?
冷啟動指系統(tǒng)沒有該應(yīng)用的進程,直到開啟應(yīng)用才創(chuàng)建出應(yīng)用程序的進程。冷啟動一般指的就是應(yīng)用在開機后或者系統(tǒng)停止應(yīng)用后的第一次啟動。因為系統(tǒng)和應(yīng)用程序在該狀態(tài)下啟動需要做更多的工作,所以減少它的啟動時間的挑戰(zhàn)是最大的。
冷啟動初始時,系統(tǒng)完成三個任務(wù):
一旦系統(tǒng)創(chuàng)建了應(yīng)用的專屬進程,該進程開始創(chuàng)建應(yīng)用:
一旦應(yīng)用完成了第一次繪制,系統(tǒng)進程就把當前顯示的啟動窗口切換為應(yīng)用的界面,這時用戶就可以開始使用應(yīng)用了。
下圖展示了系統(tǒng)和應(yīng)用的啟動時相互之間的關(guān)系:
以上流程中的大部分由系統(tǒng)來控制,需要關(guān)注性能問題的地方往往出現(xiàn)在 Application 和 Activity 的創(chuàng)建(onCreate)過程中。
當你的應(yīng)用啟動,屏幕立即出現(xiàn)的空白屏幕,將在應(yīng)用完成首屏的繪制時,切換為應(yīng)用首屏,然后允許用戶開始與應(yīng)用進行交互。
如果你在應(yīng)用中重載了 Application.oncreate(),系統(tǒng)將先調(diào)用應(yīng)用的 onCreate()方法。大型的 App 通常會在這里做大量的通用組件、SDK 的初始化操作。
然后應(yīng)用程序會生成主線程,也被稱為 UI 線程,并開始創(chuàng)建 Main Activity。
在這之后,系統(tǒng)和應(yīng)用按各自的生命周期運行著。
應(yīng)用創(chuàng)建 Activity:
通常情況下,onCreate() 方法對加載時間的影響最大,因為它執(zhí)行了開銷最重的工作:加載、渲染,以及初始化 Activity 所需要的對象,如果布局過于復(fù)雜很可能導(dǎo)致嚴重的啟動性能問題。
應(yīng)用程序的熱啟動比冷啟動開銷低。在熱啟動中,系統(tǒng)只是需要把 Activity 切換到前臺運行。如果應(yīng)用的該 Activity 之前駐留在內(nèi)存中,那么應(yīng)用程序就不用重新初始化對象和渲染布局。
但是,如果由于響應(yīng)了低內(nèi)存事件,例如在 onTrimMemory() 方法中清除了資源對象,那么這些對象就需要在熱啟動時重新創(chuàng)建。
熱啟動與冷啟動的顯示情況是一致的:系統(tǒng)進程顯示空白屏幕,直到應(yīng)用程序已經(jīng)完成 Activity 的渲染。即如果從內(nèi)存中直接切換,則不會顯示空白屏幕,如果內(nèi)存內(nèi)容被清除,將顯示空白屏幕等待渲染完成。
溫啟動為冷啟動的過程操作的子集:這代表開銷比熱啟動稍大。以下這些情況可以認為是溫啟動:
用戶退出應(yīng)用,但隨后重新啟動它。應(yīng)用的進程還在運行,但應(yīng)用必須從新從 onCreate()開始創(chuàng)建 Activity。
系統(tǒng)從內(nèi)存中清除了應(yīng)用(非用戶主動),然后用戶重新啟動它。進程和 Activity 需要重新啟動,但 onCreate()將接收到保存狀態(tài)的 Bundle。事實上,savedInstanceState 的保存在用戶未主動銷毀 Activity 時系統(tǒng)就會調(diào)用。
為了正確評估啟動時的表現(xiàn),你需要跟蹤應(yīng)用啟動到顯示需要多長時間。下圖展示了應(yīng)用初始顯示的時間和完全顯示的時間的定義。
從 Android 4.4(API 19) 開始,logcat 的輸出包括了一行 Displayed 的值。這個值表示了應(yīng)用啟動進程到 Activity 完成屏幕繪制經(jīng)過的時間。經(jīng)過的時間包括以下事件,按順序為:
報告的日志行看起來類似于下面的例子:
I/ActivityManager: Displayed com.android.contacts/.activities.PeopleActivity: +612ms
如果您在終端使用 logcat,可以直接找到這一行,當然,為了方便需要使用 grep 進行查找。而如果使用 Android Studio 查看,你必須在你的 logcat 視圖中禁用過濾器,因為這是系統(tǒng)打的日志而不是應(yīng)用本身。一旦您完成了過濾器設(shè)置,就可以輕松地搜索到該行查看時間。下圖 展示了如何禁用過濾器,及 logcat 窗口顯示 Displayed 時間的例子。
Displayed 時間顯示的是到第一次繪制的時候,它并不包括不被布局文件及初始化對象所引用的資源的加載時間,因為這個加載是一個內(nèi)部過程,不阻塞應(yīng)用初始內(nèi)容的顯示。
你也可以使用 ADB Shell Activity Manager 測量啟動到顯示的時間。下面是一個例子:
adb shell am start -S -W com.android.contacts/.activities.PeopleActivity -c android.intent.category.LAUNCHER -a android.intent.action.MAIN
你的終端窗口就像顯示 Displayed 一樣地顯示如下內(nèi)容:
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.android.contacts/.activities.PeopleActivity } Status: ok Activity: com.android.contacts/.activities.PeopleActivity ThisTime: 701 TotalTime: 701 WaitTime: 718 Complete
通過可選參數(shù)-c 和-a 可以指定 Intent 的 <category> 和 <action>。
你可以使用reportFullyDrawn()方法來測量應(yīng)用啟動到所有資源和視圖層次結(jié)構(gòu)的完整顯示之間所經(jīng)過的時間,這在應(yīng)用使用延遲加載的情況下是很有用的。
在延遲加載時,應(yīng)用在初始的繪圖之后,異步加載資源,然后更新視圖。如果由于延遲加載,應(yīng)用的初始顯示并不包括所有的資源,你可能會考慮將所有的資源和視圖的完全加載和顯示作為一個單獨的指標。例如,您的用戶界面可能已經(jīng)完成了文本的加載,但又必須從網(wǎng)絡(luò)獲取圖像。
為了解決這個問題,你可以手動調(diào)用reportFullyDrawn(),讓系統(tǒng)知道你的 Activity 完成了它的延遲加載。當您使用此方法,logcat 將顯示出從創(chuàng)建應(yīng)用對象到調(diào)用 reportFullyDrawn()方法的時間。下面是 logcat 的輸出的例子:
system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms
還有一種測量啟動時間的方法值得一提,因為這種方法雖然繁瑣但可以很直觀查看起止位置的時間,那就是通過screenrecord命令。該命令可以直接錄制屏幕,通過以下命令啟動:
adb shell screenrecord --bugreport /sdcard/launch.mp4
在手機上操作,點擊 App,等待其顯示,必要時可以多等待一會兒,然后使用Ctrl + c停止命令,就得到了想要的視頻了。使用命令導(dǎo)出視頻:
adb pull /sdcard/launch.mp4
接著就可以使用一個能逐幀查看的視頻播放器——例如 QuickTime 播放器來查看視頻,一般地,認為 App 的圖標高亮?xí)r為計時的起點,記錄此刻到你想要的停止的位置之間的時間就可以了。簡單來說,就是錄制一個視頻,使用逐幀查看的視頻播放器方便地記錄下你想查看的任意起止時刻。
如果你發(fā)現(xiàn)啟動時間比預(yù)期要慢,你可以嘗試著找出啟動過程中的瓶頸。
有兩個很好的方法可以用來來定位問題:Method Tracer 工具和 Systrace 工具。
在 Android Studio 的 CPU Monitor 欄中,提供了 Method Tracer 工具。
首先需要啟動要監(jiān)控的應(yīng)用,在 Android Studio 下方的 Android Monitor 中選擇該應(yīng)用的進程(圖中長方框位置),就可以看到 Memory Monitor / CPU Monitor / Network Monitor 都開始工作起來。
如果要使用 Method Trace 功能,只需要點擊 Start Method Tracing(圖中小方框),在手機上進行操作之后,再次點擊它停止 Method Trace,稍等片刻就能在工程的 captures 文件夾中找到 .trace 文件了。
由以上流程可以知道對于冷啟動而言是無法在正確的時間啟動該工具以獲得日志信息的。這種情況下可以在代碼中合適的位置,例如onCreate()和onResume()中,添加android.os.Debug.startMethodTracing()和android.os.Debug.stopMethodTracing()方法來生成 trace 文件,該文件生成在 sdcard 根目錄下或者應(yīng)用的讀寫目錄中。
Note: 運行 Method Trace 將明顯地影響運行應(yīng)用的效果。 Method Trace 應(yīng)該用來了解程序的流程及方法的運行時間比例,其計時時間不可直接作為應(yīng)用性能的表現(xiàn)。
使用 Android Studio 打開 trace 文件,如果是 CPU Monitor 中生成了 trace 文件,Android Studio 會自動打開它,你將得到如下形式的圖片:
列名 | 具體含義 |
---|---|
Name | 方法名 |
Invocation Count | 方法調(diào)用次數(shù) |
Inclusive Time (microseconds) | 該方法及其調(diào)用的子方法的耗時 |
Exclusive Time (microseconds) | 該方法(不包含調(diào)用的子方法)的耗時 |
圖表的 x 坐標可以選擇Wall Clock Time或者Thread Time,其中前者表示方法調(diào)用到返回結(jié)果真實的 CPU 時間,后者表示線程調(diào)度的時間,如果線程不連續(xù)執(zhí)行,那么被中斷的時間將被排除,所以將小于前者的統(tǒng)計。
也可以使用 DDMS 打開 trace 文件,其展示的視圖如下所示:
各列名稱及其含義與 Android Studio 的圖示基本類似。
還可以使用 dmtracedump 工具解析生成 html 文件如下圖(dmtracedump 可以生成圖片,但往往混亂到看不出順序,有興趣的可以自行查閱相關(guān)資料):
從以上三種方式展示的 trace 文件結(jié)果來看,結(jié)果中包含了 JDK 函數(shù),第三方庫函數(shù),以及 Android SDK 中函數(shù),如果想僅分析應(yīng)用中的方法調(diào)用順序信息,可以根據(jù) trace 文件過濾出當前應(yīng)用下的方法信息。目前 GitHub 上有一個 Windows 平臺下的分析應(yīng)用方法耗時的 swing 工具,其使用方法很簡單:
該工具的思路基于:一個能讓你了解所有函數(shù)調(diào)用順序以及函數(shù)耗時的 Android 庫(無需侵入式代碼),該庫核心就是 2 個 build.gradle 中的 task 基于 dmtracedump 工具對 trace 文件進行解析、過濾。
Method Trace Tool 得到了良好的展示效果,如圖:
以上 trace 文件的幾種展示方式可以讓你了解到關(guān)于應(yīng)用中方法的調(diào)用順序及耗時信息(注意:該耗時信息不代表真正使用場景下的耗時),基于以上信息可以分析出一個方法或者一個環(huán)節(jié)是否成為性能瓶頸。
另一個跟蹤的方法就是 Systrace 的使用了。了解更多,請參閱 Trace 功能的參考文檔,以及 Systrace 工具的介紹。
相關(guān)新聞換一組