冷啓動html
當啓動應用時,後臺沒有該應用的進程(常見如:進程被殺、首次啓動等),這時系統會從新建立一個新的進程分配給該應用java
暖啓動android
當啓動應用時,後臺已有該應用的進程(常見如:按back
鍵、home
鍵,應用雖然會退出,可是該應用的進程是依然會保留在後臺,可進入任務列表查看),因此在已有進程的狀況下,這種啓動會從已有的進程中來啓動應用git
熱啓動github
相比暖啓動,熱啓動時應用作的工做更少,啓動時間更短。熱啓動產生的場景不少,常見如:用戶使用返回鍵退出應用,而後立刻又從新啓動應用shell
熱啓動和暖啓動由於會從已有的進程中來啓動,不會再建立和初始化Application
數據庫
平時咱們討論中基本都會將暖啓動和熱啓動合在一塊兒統稱爲熱啓動,由於暖啓動與熱啓動差別很小,若是不是特別留意啓動流程,那麼在用戶體驗和感官上沒有直接差別,可是在framework
層執行時是有必定差別的。本次優化點也是圍繞冷啓動和熱啓動來作,將暖啓動與熱啓動統稱爲熱啓動緩存
另外有一點,從絕對時間上來看,app
安裝後的首次啓動將會最耗時,由於首次啓動會新建數據庫,sp
文件,各類緩存,配置等性能優化
白屏或黑屏,具體是哪個,取決於app
的Theme
使用的是dark
仍是light
主題微信
Android Studio 引發的白屏
2.x
時代的AS
開啓了instant run
之後可能會致使白屏,但實際完整的apk
包不會出現此問題
冷啓動引發的白屏/黑屏
點擊你app
那一刻到系統調用Activity.onCreate()
之間的時間段。在這個時間段內,WindowManager
會先加載app
主題樣式中的windowBackground
做爲app
的預覽元素,而後再真正去加載activity
的layout
佈局
暖啓動/熱啓動引發的白屏/黑屏
這點在配置較好,內存空間充足的手機上不是很明顯,但低端手機或者內存吃緊的狀況下依舊會出現」閃屏」效果,持續時間很短,一閃而過
我將冷啓動優化分爲可控階段和不可控階段
不可控階段
點擊app
之後到初始化Application
之間這段時間,系統接管,從Zygote
進程中fork
建立新進程,GC
回收等等一系列操做,和咱們app
無關
可控階段
初始化Application
開始,以下圖
從上圖能夠看到,整個冷啓動流程中至少有兩處onCreate
,分別是Application
和Activity
,整個流程都是可控的。因此,onCreate
方法作的事情越多,冷啓動消耗的時間越長
Logcat 自動打印
從Android 4.4(API 19)
開始,Logcat
自動幫咱們打印出應用的啓動時間。這個時間從應用啓動(建立進程)開始計算,到完成視圖的第一次繪製(即Activity
內容對用戶可見)爲止
04-25 14:53:09.317 869-1214/? I/ActivityManager: Displayed cn.com.dhc.danlu/.shell.activity.InitializeActivity: +4s256ms
04-25 14:53:11.077 869-1214/? I/ActivityManager: Displayed cn.com.dhc.danlu/.shell.main.MainActivity: +559ms
複製代碼
04-25 14:53:20.407 869-1214/? I/ActivityManager: Displayed cn.com.dhc.danlu/.shell.activity.InitializeActivity: +178ms
04-25 14:53:22.447 869-1214/? I/ActivityManager: Displayed cn.com.dhc.danlu/.shell.main.MainActivity: +131ms
複製代碼
com.android.server.am.ActivityRecord#reportLaunchTimeLocked(long curTime)
中打印出來的:private void reportLaunchTimeLocked(final long curTime) {
final ActivityStack stack = task.stack;
final long thisTime = curTime - displayStartTime;
final long totalTime = stack.mLaunchStartTime != 0
? (curTime - stack.mLaunchStartTime) : thisTime;
if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "launching", 0);
EventLog.writeEvent(EventLogTags.AM_ACTIVITY_LAUNCH_TIME,
userId, System.identityHashCode(this), shortComponentName,
thisTime, totalTime);
StringBuilder sb = service.mStringBuilder;
sb.setLength(0);
sb.append("Displayed ");
sb.append(shortComponentName);
sb.append(": ");
TimeUtils.formatDuration(thisTime, sb);
if (thisTime != totalTime) {
sb.append(" (total ");
TimeUtils.formatDuration(totalTime, sb);
sb.append(")");
}
Log.i(ActivityManagerService.TAG, sb.toString());
}
mStackSupervisor.reportActivityLaunchedLocked(false, this, thisTime, totalTime);
if (totalTime > 0) {
//service.mUsageStatsService.noteLaunchTime(realActivity, (int)totalTime);
}
displayStartTime = 0;
stack.mLaunchStartTime = 0;
}
// normal time:統計的是 Activity 從啓動到界面繪製完畢的時間
// total time :統計的是 normal time + Activity 棧創建完畢的時間
複製代碼
測量 Activity 啓動時間
Activity
的reportFullyDrawn()
咱們能夠在Activity
的任意位置調用此方法已打印你想看到的、執行完某個方法的最終時間。它會在Logcat
裏打印從apk
初始化(和前面Displayed
的時間是同樣的)到reportFullyDrawn()
方法被調用用了多長時間
ActivityManager: Displayed com.Android.myexample/.StartupTiming: +768ms
複製代碼
在4.4
上調用reportFullyDrawn()
方法會崩潰(可是log
仍是能正常打印),提示須要UPDATE_DEVICE_STATS
權限 ,可是這個權限沒法拿到
try {
reportFullyDrawn();
} catch (SecurityException e) {
}
複製代碼
本地調試啓動時間
上述命令能夠直接啓動對應包名的對應activity
,但要注意不是所有activity
都能使用這個命令直接啓動
adb screenrecord 命令
bugreport
選項(它能夠在frames
中添加時間戳-應該是L
中的特性)的screenrecord
命令app
圖標,等待app
顯示,ctrl-C
中止screenrecord
命令,在手機存儲中會生 成aunch.mp4
視頻文件,而後pull
到電腦frame
時間戳。一直往前直到你發現app
圖標高亮了爲止。這個時候系統已經處理了圖標上的點擊事件,開始啓動app
了,記錄下這一幀的時間。繼續播放幀直到你看到了app
整個UI
的第一幀爲止。根據不一樣狀況(是否有啓動窗口,是否有啓動畫面等等)。事件和窗口發生的實際順序可能會有不一樣。對於一個簡單的app
來講,你會首先見到啓動窗口,而後漸變出app
真實的UI
。在你看到UI
上的任何內容以後,再記錄下第一幀,這時app
完成了佈局和繪製,準備開始顯示出來了。同時也記錄下這一幀所發生的時間((UI displayed) - (icon tapped))
獲得app
從點擊到繪製就緒的全部時間。雖然這個時間包含了進程啓動以前的時間,可是至少它能夠用於跟其餘app
比較因而可知,app
冷啓動時間大約爲4s
,熱啓動時間大約爲132ms
.
從啓動流程分析
減小兩處onCreate()
中的初始化操做,將部分初始化移動到IntentService
中進行
從用戶體驗分析
將app
首頁的按返回鍵響應修改成響應Home
鍵,曲線救國。讓用戶覺得app
確實退出了,可是其實是點了Home
鍵。如此一來,下次點擊app
圖標的時候,直接喚起,不須要進行初始化操做,主要能夠避免再次走閃屏頁,參考美團,微信,QQ,淘寶等(實現的效果同樣,可是實現方式就不得而知了)
Home
鍵的操做同樣,底部選中tab
沒有作自切換Home
鍵的操做不同,底部選中tab
作了自切換tab
,而後再退出利用Google
官方文檔推薦的方式,咱們將啓動頁界面的主題設置爲SplashTheme
。此界面是冷啓動後首先加載的界面:
主題內的代碼以下:
這個主題至關於丟了一張圖片做爲背景,也就是紅色背景Logo
和Slogan
圖片,無版本號
此時咱們已經「消除了」白屏/黑屏頁,將冷啓動的白屏/黑屏單調的純色背景替換爲咱們即將展現給用戶的 InitializeActivity
界面的圖片,從系統的Window
到咱們本身App
跳轉過程,使用了全屏屬性,以達到無縫跳轉
須要說明的是,這一步作了以後,對總體啓動時間並無任何的減小,時間不變,只是說給用戶的體驗要友好不少,再也不顯示一個突兀的白屏/黑屏界面 ------將鍋甩給本身app
,app
太卡,竟然在InitializeActivity
要等這麼久(固然用戶不知道的是:系統window
界面和InitializeActivity
不是同一個界面)
固然也能夠採用另外一種方式,那就是將上面的主題中的backgroud設置爲透明用戶點擊了圖標開始啓動的時候,界面上沒有任何變化,由於此時系統啓動的那個白屏/黑屏界面背景透明的 ------將鍋甩給系統,太卡,點了圖標竟然隔了這麼久才顯示InitializeActivity
接下來咱們查看Application
和InitializeActivity
的onCreate()
是否有能夠遷移到IntentService
中的代碼:
能夠看到,其實當中的邏輯不是不少,而且都是須要在Application
中初始化完畢的,不能單獨提出來進行初始化,其中只有GrowingIO
能夠考慮提出來
推送初始化能夠提出來
onCreate 中乍看沒有什麼耗時操做,內部的幾個方法也都是必需要的業務邏輯,惟一能動的就是內部針對界面停留作的延時時間,目前是2s,能夠減小到1s左右。
最後一步,在MainActivity
中處理返回鍵邏輯。
將確實是退出的邏輯替換爲按Home
鍵:
這種作法給用戶一個假象:用戶按返回鍵退出,可是實際上並沒退出,app
處於後臺,下次點擊圖標時直接喚起
針對這種操做,須要注意幾個點
onRestart
中須要判斷tab
狀態onSaveInstanceState
和onRestoreInstanceState
中須要保存和恢復數據,用於判斷用戶是點了 home
仍是back
,這兩種操做須要區分開,同時須要保存tab
狀態Home
鍵事件結果
冷啓動:
熱啓動:
由上圖可知,優化後的冷啓動時間大約爲3544ms
,熱啓動時間大約爲127ms
,相比以前的4121ms
以及151ms
來講有必定的提高,白屏效果也被消除了。
可是其實提高最大的點不是白屏優化,由於咱們沒有把Application
和Activity
中onCreate
的邏輯減小並提到IntentService
中。
最大的提高點是,咱們讓用戶退出app
時,形成假象,讓用戶覺得他確實退出了app
,但實際上咱們是藏在後臺,當用戶熱啓動或者溫啓動時,咱們不用再通過InitializeActivity
的流程進入首頁。
onCreate
中儘可能避免作過多的初始化動做,若是必須,那麼考慮IntentService
Back
和Home
鍵的動做作一些假象,使用戶按Back
鍵時覺得他退出了,以減小下次啓動的沒必要要動做(建議:非即時消息類和社交類app
,這種作法慎用,由於可能有流氓之嫌。。。(逃))Activity#moveTaskToBack(true)