本文首發於微信公衆號「玉剛說」java
原文連接:你的 APP 爲什麼啓動那麼慢?android
冷啓動是指APP在手機啓動後第一次運行,或者APP進程被kill掉後在再次啓動。
可見冷啓動的必要條件是該APP進程不存在,這就意味着系統須要建立進程,APP須要初始化。在這三種啓動方式中,冷啓動耗時最長,對於冷啓動的優化也是最具挑戰的。所以本文重點談論的是對冷啓動相關的優化。shell
App進程存在,當時Activity可能由於內存不足被回收。這時候啓動App不須要從新建立進程,可是Activity的onCrate仍是須要從新執行的。場景相似打開淘寶逛了一圈而後切到微信去聊天去了,過了半小時再次回到淘寶。這時候淘寶的進程存在,可是Activity可能被回收,這時候只須要從新加載Activity便可。apache
App進程存在,而且Activity對象仍然存在內存中沒有被回收。能夠重複避免對象初始化,佈局解析繪製。
場景就相似你打開微信聊了一會天這時候出去看了下日曆 在打開微信 微信這時候啓動就屬於熱啓動。微信
某些廠商爲了用戶體驗提供了給APP上鎖的功能,目的就是讓用戶本身作主是上鎖的APP不被殺,啓動的時候不會處於冷啓動方式,可是加鎖也不是萬能的,Low memory killer在內存極度吃緊的狀況下也會殺死加鎖APP,在此啓動時也將以冷啓動方式運行。app
AI在進程管理方面可謂是大有可爲。MIUI10發佈了進程AI喚醒功能,使APP啓動速度遠超友商。這其中的道理簡單說就是學習用戶的使用習慣,提早將App進程建立好,當用戶打開APP時不會出現冷啓動。好比你是微信重度用戶你發現用了MIUI10就再也見不到微信啓動頁面的那個地球了,這就是AI喚醒的功勞。ide
這裏咱們來討論冷啓動的過程,進程啓動原則上有四種途徑,也就是經過其餘進程對該APP的四大組件的調用來實現。工具
這裏咱們重點討論用戶點擊桌面後的APP啓動,經過startActivity方式的啓動。
調用startActivity,該方法通過層層調用,最終會調用ActivityStackSupervisor.java中的startSpecificActivityLocked,當activity所屬進程還沒啓動的狀況下,則須要建立相應的進程.佈局
void startSpecificActivityLocked(...) {
ProcessRecord app = mService.getProcessRecordLocked(r.processName,
r.info.applicationInfo.uid, true);
if (app != null && app.thread != null) {
...//進程已建立
return
}
//建立進程
mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
"activity", r.intent.getComponent(), false, false, true);
}
複製代碼
最終進程由 Zygote Fork進程:
學習
進程啓動後系統還有一個工做就是:進程啓動後當即顯示應用程序的空白啓動窗口。
一旦系統建立應用程序進程,應用程序進程就會負責下一階段。這些階段是:
1.建立應用程序對象
2.啓動主線程
3.建立主要Activity
4.繪製視圖(View)
5.佈局屏幕
6.執行初始化繪製
而一旦App進程完成了第一次繪製,系統進程就會用Main Activity替換已經展現的Background Window,此時用戶就可使用App了。
這裏很明顯有兩個優化點:
1.Application OnCrate()優化
當APP啓動時,空白的啓動窗口將保留在屏幕上,直到系統首次完成繪製應用程序。此時,系統進程會交換應用程序的啓動窗口,容許用戶開始與應用程序進行交互。若是應用程序中重載了Application.onCreate(),系統會調用onCreate()方法。以後,應用程序會生成主線程(也稱爲UI線程),並經過建立MainActivity來執行任務。
2.Activity onCreate()優化
onCreate()方法對加載時間的影響最大,由於它以最高的開銷執行工做:加載並繪製視圖,以及初始化Activity運行所需的對象。
1.目前爲止見過最最牛逼的是使用機械手和高速相機測試,手機開機後使用機械手點擊應用桌面圖標,高速相機記錄啓動過程,後續經過程序分析視頻,從機械手點擊圖標到Activity顯示出來使用了多少時間。這種方式是最直觀和精確的,可是成本也很高。
2.經過shell 命令
adb shell am start -W [packageName]/[packageName.MainActivity]
複製代碼
執行成功後將返回三個測量到的時間:
ThisTime:通常和TotalTime時間同樣,除非在應用啓動時開了一個透明的Activity預先處理一些事再顯示出主Activity,這樣將比TotalTime小。
TotalTime:應用的啓動時間,包括建立進程+Application初始化+Activity初始化到界面顯示。
WaitTime:通常比TotalTime大點,包括系統影響的耗時。
3.能夠經過在代碼中增長log來計算啓動時間
4.使用systrace
1.第三方SDK初始化的處理
Application是程序的主入口,不少三方SDK示例程序中都要求本身在Application OnCreate時作初始化操做。這就是增長Application OnCreate時間的主要元兇,因此須要儘可能避免在Application onCreate時同步作初始化操做。比較好的解決方案就是對三方SDK就行懶加載,不在Application OnCreate()時初始化,在真正用到的時候再去加載。
下面實例對比下ImageLoader在採用懶加載後啓動速度優化。
通常咱們在使用imageLoader時都會在Application onCreate()時在主線程加載:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
ImageLoaderConfiguration.Builder config =
new ImageLoaderConfiguration.Builder(this);
ImageLoader.getInstance().init(config.build());
}
}
複製代碼
此時使用adb shell am start -W [packageName]/[packageName.MainActivity]檢測應用啓動時間,每次執行命令時須要殺死進程。
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.luozhanwei.myapplication/.MainActivity }
Status: ok
Activity: com.luozhanwei.myapplication/.MainActivity
ThisTime: 423
TotalTime: 423
WaitTime: 441
複製代碼
Total time在423ms之間,下面是封裝了一個懶加載ImageLoader的工具類示例:
public class ImageUtil {
private static boolean sInit;
private synchronized static void ensureInit() {
if (sInit) {
return;
}
ImageLoaderConfiguration.Builder config =
new ImageLoaderConfiguration.Builder(SecurityCoreApplication.getInstance());
....
// Initialize ImageLoader with configuration.
ImageLoader.getInstance().init(config.build());
sInit = true;
}
public static void display(String uri, ImageView imageView, boolean cacheOnDisk) {
imageView.setImageResource(R.drawable.icon_app_default);
ensureInit();
ImageLoader loader = ImageLoader.getInstance();
if (cacheOnDisk) {
loader.displayImage(uri, imageView);
} else {
loader.displayImage(uri, imageView, OPTIONS_NO_CACHE_DISK);
}
}
複製代碼
使用這種方案後的啓動時間:
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.luozhanwei.myapplication/.MainActivity }
Status: ok
Activity: com.luozhanwei.myapplication/.MainActivity
ThisTime: 389
TotalTime: 389
WaitTime: 405
複製代碼
看到TotalTime比以前減小了34ms(給出的數據爲10次檢測平均值)。
因此Application OnCreate 避免在主線程作大量耗時操做,例如和IO相關的邏輯,這樣都會影響到應用啓動速度。若是必需要作須要放到子線程中。
減小LaunchActivity的View層級,減小View測量繪製時間。
避免主線程作耗時操做
消除啓動時的白屏/黑屏
爲何啓動時會出現短暫黑屏或白屏的現象?當用戶點擊你的app那一刻到系統調用Activity.onCreate()之間的這個時間段內,WindowManager會先加載app主題樣式中的windowBackground作爲app的預覽元素,而後再真正去加載activity的layout佈局。
很顯然,若是你的application或activity啓動的過程太慢,致使系統的BackgroundWindow沒有及時被替換,就會出現啓動時白屏或黑屏的狀況(取決於你的主題是Dark仍是Light)。
1.甩鍋給系統
使用透明主題:
<item name="android:windowIsTranslucent">true</item>
複製代碼
Activity.onCreate()以前App不作顯示,這樣用戶誤覺得是手機慢了,這種瞞天過海的方案你們仍是不要用了。
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowIsTranslucent">true</item>
</style>
</resources>
複製代碼
效果以下:
2.主題替換
咱們在style中自定義一個樣式Lancher,在其中放一張背景圖片,或是廣告圖片之類的
<style name="AppTheme.Launcher">
<item name="android:windowBackground">@drawable/bg</item>
</style>
複製代碼
把這個樣式設置給啓動的Activity
<activity
android:name=".activity.SplashActivity"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.Launcher"
>
複製代碼
而後在Activity的onCreate方法,把Activity設置回原來的主題
@Override
protected void onCreate(Bundle savedInstanceState) {
//替換爲原來的主題,在onCreate以前調用
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
}
複製代碼
這樣在啓動時就經過給用戶看一張圖片或是廣告來防止黑白屏的尷尬。