一、前言
APP優(yōu)化是我們進階高級開發(fā)工程師的必經(jīng)之路,而APP啟動速度的優(yōu)化,也是我們開啟APP優(yōu)化的第一步。用戶在使用我們的軟件時,交互最多最頻繁的也就是APP的啟動頁面,如果啟動頁面加載過慢,很可能造成用戶對我們APP的印象過差,進而消耗了用戶的耐心,更嚴重可能導(dǎo)致用戶的卸載行為。這也是微信始終堅持使用“一個小人望著地球”作為啟動頁面的背景,并且堅持不添加啟動廣告的的原因。
二、APP的三種啟動方式
來看一下Google官方文檔《Launch-Time Performance》對應(yīng)用啟動優(yōu)化的概述;
應(yīng)用的啟動可以分為冷啟動,熱啟動和溫啟動,而啟動最慢、耗時最長的就是冷啟動。
冷啟動(cold start)
當(dāng)應(yīng)用啟動時,后臺沒有該應(yīng)用的進程(常見如:進程被殺、首次啟動等),這時系統(tǒng)會重新創(chuàng)建一個新的進程分配給該應(yīng)用。
熱啟動(hot start)
這種啟動會從已有的進程中來啟動應(yīng)用,通俗來講就是已經(jīng)啟用的應(yīng)用,通過back鍵或者home鍵回到系統(tǒng)主界面,再次通過最近任務(wù)重新打開Activity的過程。開銷比冷啟動更小。
暖啟動(warm start)
暖啟動產(chǎn)生的場景很多。常見如:1。用戶使用返回鍵退出應(yīng)用,然后馬上又重新啟動應(yīng)用。2.應(yīng)用被內(nèi)存清除,再次打開應(yīng)用,會通過OnCreate()中保存的實例狀態(tài)恢復(fù)。
冷啟動是從頭開始啟動APP,而其他兩種啟動方式是從后臺活動返回到前臺的一個過程。熱啟動和暖啟動沒有明顯的區(qū)分界限,我們姑且把熱啟動和暖啟動統(tǒng)稱為熱啟動。開發(fā)中我們更多的關(guān)注冷啟動優(yōu)化,本文也是從Android的實例從發(fā),分析冷啟動的啟動過程,并給出冷啟動的優(yōu)化方案。
三、APP的冷啟動過程
冷啟動開始時,系統(tǒng)會依次執(zhí)行三個任務(wù)去啟動APP:
- 加載和啟動應(yīng)用程序
- APP啟動后,立即創(chuàng)建一個空白的啟動Window
- 創(chuàng)建APP的進程
在這三個任務(wù)執(zhí)行后,系統(tǒng)創(chuàng)建了應(yīng)用進程,那么應(yīng)用進程接下來會執(zhí)行下一步:
- 創(chuàng)建APP對象
- 開啟一個主線程
- 創(chuàng)建啟動頁的Activity
- 加載View
- 布局view到屏幕
- 進行初始繪制顯示視圖
當(dāng)應(yīng)用進程完成初始繪制之后,系統(tǒng)進程用啟動頁的Activity來替換當(dāng)前顯示的空白Window,這個時刻用戶就可以使用App了。
四、APP啟動時間
APP的啟動時間是我們可以檢驗優(yōu)化效果的依據(jù),啟動時間是指打開應(yīng)用從初始化到顯示啟動頁Activity的這一段時間。
Google官方的解釋:APP startup time
使用過logcat查看啟動時間
在Android4.4(API level 19)以上的Android版本上,當(dāng)啟動應(yīng)用時Android Studio自動會在logcat中輸出啟動時間。 這個時間從應(yīng)用啟動(創(chuàng)建進程)開始計算,到完成視圖的第一次繪制(即Activity內(nèi)容對用戶可見)為止。
如Display顯示:
I/ActivityManager: Displayed com.example.app/com.example.app.SplashActivity: +1s742ms (total +49s450ms)
reportFullyDrawn()方法
Activity 的reportFullyDrawn()方法,
它會在Logcat里打印從apk初始化到reportFullyDrawn()方法被調(diào)用用了多長時間(文章的reportFullyDrawn()在SplashActivity中的onCreate()中執(zhí)行,可以看到顯示的時間和Displayed的是一摸一樣的)
I/ActivityManager: Fully drawn com.example.app/com.example.app.SplashActivity: +1s742ms (total +49s450ms)
執(zhí)行adb命令手動查看啟動時間
adb shell am start -W [packagename]/[packagename.SplashActivity]
輸出:
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example.app/.SplashActivity }
Status: ok
Activity: com.example.app/.SplashActivity
ThisTime: 1368
TotalTime: 1368
WaitTime: 1432
Complete
ThisTime:最后一個啟動的Activity的啟動耗時;
TotalTime:自己的所有Activity的啟動耗時;
只用關(guān)注TotalTime就行了
啟動時間標(biāo)準(zhǔn)
官方給出,當(dāng)啟動時間超出以下指標(biāo)時,會被認為啟動時間過長,這是就需要考慮仔細優(yōu)化啟動時間。
- 冷啟動時間超過5s
- 熱啟動時間超過1.5s
- 暖啟動時間超過2s
說了一大堆啟動的基礎(chǔ)知識,下邊開始講解真正的優(yōu)化實戰(zhàn)
五、啟動優(yōu)化實戰(zhàn)
解決應(yīng)用剛啟動時的白屏問題
前邊講到,應(yīng)用初始化會進行一系列進程的創(chuàng)建,資源的初始化工作,這段時間系統(tǒng)會先分配一個空白的Window,這會造成用戶打開應(yīng)用到顯示第一個可交互的Activity,會經(jīng)歷一段白屏的時間。
這時我們可以在給app定義一個主題去解決,在Activity顯示出來之前先顯示一個主題背景,去填補空白的Window階段。
定義一個Splash主題
<!--Splash launcher-->
<style name="LauncherTheme" parent="AppTheme">
<item name="android:windowBackground">@mipmap/ic_splash_bg</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
在manifest中引用該主題
<activity android:name=".activity.SplashActivity"
android:theme="@style/Launcher">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
最后記得在啟動頁顯示以后恢復(fù)默認的APP主題
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setTheme(R.style.AppTheme)
setContentView(R.layout.activity_splash)
}
這種方法只是視覺上給用戶一種快速啟動的感覺,不能減少實際的啟動時間。
避免Application初始化過重
隨著我們工程越做越大,第三方庫和組件也逐漸被依賴到我們的項目中,避免不了會在Application的onCreate()中執(zhí)行很多第三方庫的初始化工作。大量的初始化工作導(dǎo)致該生命周期過于沉重,可能會加長應(yīng)用的啟動時間,因此我們應(yīng)該對這些第三方庫進行分類和優(yōu)化。
- 必須在onCreate()且是主進程中初始化
- 可以延遲,但是需要在Application中初始化
- 可以延遲到啟動頁的生命周期回調(diào)中初始化
- 延遲到用的時候再初始化
大家可以根據(jù)自身項目去整理代碼,可以延遲執(zhí)行的應(yīng)該放在IntentService或者Work Thread中進行初始化。
- 例如EventBus 需要在Activiy中使用的,必須在Application中初始化
- 例如Bugly ,GrowingIO等類似庫的可以放在Work Thread中初始化
- 例如地圖定位、ImageLoad可以延遲到使用之前初始化
- SplashActivity中網(wǎng)絡(luò)加載的資源,可以首次加載存放在緩存中,下次啟動的時候再顯示
- 注意有些第三方庫必須在主線程中初始化
- 避免耗時操作,如數(shù)據(jù)庫I/O操作不要放在主線程執(zhí)行
- 刪除無用或重復(fù)的代碼
- 減少首屏Activity中的網(wǎng)絡(luò)請求密度