1. 程式人生 > >android應用冷啟動過程分析與優化過程

android應用冷啟動過程分析與優化過程

http://yifeng.studio/2016/11/15/android-optimize-for-cold-start/?utm_source=tuicool&utm_medium=referral
你有沒有發現,點選安卓手機桌面上的App圖示時,有時候應用馬上進入主介面,有時候要經歷好幾秒甚至更久的白屏(也可能是黑屏)時間才能進入主介面呢?這其實是安卓應用常見的冷熱啟動問題。本文就和大家一起聊聊冷熱啟動方式和啟動頁的體驗優化方案。

啟動方式
安卓應用的啟動方式分為三種:冷啟動、暖啟動、熱啟動,不同的啟動方式決定了應用UI對使用者可見所需要花費的時間長短。顧名思義,冷啟動消耗的時間 最長。基於冷啟動方式的優化工作也是最考驗產品使用者體驗的地方。談及優化之前,我們先看看這三種啟動方式的應用場景,以及啟動過程中系統都做了些什麼工 作。

冷啟動 (Cold start)

在 安卓系統中,系統為每個執行的應用至少分配一個程序 (多程序應用申請多個程序) 。從程序角度上講,冷啟動就是在啟動應用前,系統中沒有該應用的人和程序資訊 (包括 Activity、Service 等) 。所以,冷啟動產生的場景就很容易理解了,比如裝置開機後應用的第一次啟動,系統殺掉應用程序 (如:系統記憶體吃緊引發的 kill 和 使用者主動產生的 kill) 後 的再次啟動等。那麼自然這種方式下,應用的啟動時間最長,因為相比另外兩種啟動方式,系統和我們的應用要做的工作最多。
應用發生冷啟動時,系統有三件任務要做:
1. 開始載入並啟動應用;

2. 應用啟動後,顯示一個空白的啟動視窗;

3. 建立應用程序資訊;

系統建立應用程序後,應用就要做下面這些事情:
1. 初始化應用中的物件 (比如 Application 中的工作);

2. 啟動主執行緒 (UI 執行緒) ;

3. 建立第一個 Activity;

4. 載入內容檢視 (Inflating) ;

5. 計算檢視在螢幕上的位置排版 (Laying out);

6. 繪製檢視 (draw)。

只有當應用完成第一次繪製,系統當前展示的空白背景才會消失,才會被 Activity 的內容檢視替換掉。也就是這個時候,使用者才能和我們的應用開始互動。下圖展示了冷啟動過程系統和應用的一個工作時間流:

這其中有兩個 creation 工作,分別為 Application 和 Activity creation。從圖中看出,他們均在 View 繪製展示之前。所以,在應用自定義的 Application 類和 第一個 Activity 類中,onCreate() 方法做的事情越多,冷啟動消耗的時間越長。

暖啟動 (Warm start)

當應用中的 Activities 被銷燬,但在記憶體中常駐時,應用的啟動方式就會變為暖啟動。相比冷啟動,暖啟動過程減少了物件初始化、佈局載入等工作,啟動時間更短。但啟動時,系統依然會展示一個空白背景,直到第一個 Activity 的內容呈現為止。

熱啟動 (Lukewarm start)
相比暖啟動,熱啟動時應用做的工作更少,啟動時間更短。熱啟動產生的場景很多,常見如:使用者使用返回鍵退出應用,然後馬上又重新啟動應用。

啟動時間
從 Android 4.4 (API 19) 開始,Logcat 自動幫我們打印出應用的啟動時間。這個時間值從應用啟動 (建立程序) 開始計算,到完成檢視的第一次繪製 (即 Activity 內容對使用者可見) 為止。如:
click me click me
1 11-15 14:06:25.710 1071-1130/? I/ActivityManager: Displayed com.yifeng.qqtemp/.MainActivity: +3s610ms

對應在 logcat 視窗上的顯示如圖 (記得修改過濾條件) :

對於使用者來講,看不到具體的啟動時間,而是應用啟動時白屏展示的體驗問題。舉個例子來對比一下冷熱啟動的實際啟動情況。新建一個工程,為了更好地模擬演示冷熱啟動效果,這裡我在自定義 Application 類的 onCreate 方法中添加了如下程式碼來延長應用的冷啟動時間:
click me click me
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }

然後第一次編譯執行,應用採取冷啟動的方式開啟 (使用選單鍵關閉最近開啟的應用,也能達到促使應用使用冷啟動方式,延長啟動時間的效果) ;接著使用返回鍵退出應用,再點選桌面應用圖示,應用將採用熱啟動的方式開啟。整個操作流程對應的效果如圖:

可以看到,冷啟動方式下,應用要經歷一個短暫的白屏時間 (這個時間的長短視具體情況而定),使用者體驗極其不好。相比而言,熱啟動方式下,使用者可以較快進入 Activity 主介面與應用發生互動行為。

優化方案
應用的冷啟動總是無法避免的,也就是說冷啟動時使用者總要經歷一個啟動等待時間。開發人員唯一能做的就是在 Application 和 第一個 Activity 中,減少 onCreate() 方法的工作量,從而縮短冷啟動的時間。像應用中嵌入的一些第三方 SDK,都建議在 Application 中做一些初始化工作,開發人員不妨採取懶載入的形式移除這部分程式碼,而在真正需要用到第三方 SDK 時再進行初始化。
還有一種簡單粗暴的方式就是通過主題設定,不顯示啟動時的白屏背景。新建一個主題樣式,並新增如下屬性:
click me click me
1 2 3 4 <style name="LaunchStyle" parent="AppTheme"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowNoTitle">true</item> </style>


click me click me
1 <item name="android:windowDisablePreview">true</item>

然後將這個主題樣式設定給第一個啟動的 Activity ,如:
click me click me
1 2 3 4 5 6 7 8 9 <activity android:name=".MainActivity" android:theme="@style/LaunchStyle"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>

備註:這裡沒有將該主題設定在 Application 標籤裡,主要是該主題只適用於第一個 Activity,如果放置在 Application 標籤中會修改所有 Activity 的主題樣式。
再修改該 Activity 類的程式碼,在載入佈局檢視前,將主題修改回來:
click me click me
1 2 3 4 5 6 7 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTheme(R.style.AppTheme); setContentView(R.layout.activity_main); }

這麼設定之後,效果如圖:

可以看到,冷啟動方式下,使用者點選桌面圖示,沒有任何反應,過一段時間應用才打開。其實這裡只是將白屏背景透明化或者隱藏起來而已。
很顯然,這種處理方式使用者體驗也極差。不過可喜的是,我們可以通過主題中的 windowBackground 屬性,自定義應用啟動時的視窗背景。至於這個背景如何設計,Google 已經在官網上給了建議和規範,可以參考:Launch screens。其實就是兩種樣式,如圖:

左圖採用應用的 Logo 和 Slogan (甚至為了簡單,Slogan 都可以不要),可以增強品牌推廣效應;右圖利用了 placeholder ,與主介面的 UI 框架保持一致,給使用者產生一種應用啟動非常快的視覺感受。這兩種方案各自目的和應用場景有所不同,但設計理念確實不錯。下面我們一一模仿實現,先看第一種。
新建一個名為 shape_launch.xml 的 drawable 檔案,內容如下:
click me click me
1 2 3 4 5 6 7 8 9 10 11 12 <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque"> <item android:drawable="@color/colorPrimary"/> <item > <bitmap android:src="@mipmap/ic_launcher" android:gravity="center" /> </item> </layer-list>

然後修改 styles.xml 檔案中的主題樣式:
click me click me
1 2 3 <style name="LaunchStyle" parent="AppTheme"> <item name="android:windowBackground">@drawable/shape_launch</item> </style>

最後將這個主題設定給啟動的 Activity,設定過程和上面隱藏啟動視窗時的設定一樣。效果如圖:

第二種,使用與主介面 UI 框架一致的 placeholder 內容,這種情況下需要計算諸如 Statusbar、Toolbar 控制元件的高度,shape_launch.xml 內容如下:
click me click me
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque"> <item android:drawable="@color/colorPrimaryDark"/> <item android:drawable="@color/colorPrimary" android:top="25dp"/> <item android:top="81dp" android:drawable="@android:color/white"> </item> </layer-list>

這裡模擬了一個高度為25dp的狀態列和一個高度為56dp的標題欄,給使用者一種錯覺:點選桌面圖示,應用立即啟動並進入主介面。效果如圖:

以上兩種方式是按照 Google 的建議實現的 UI 效果,當然你還可以有更好的設計方式,只要能夠解決冷啟動帶來的白屏過渡問題,都能帶來不錯的使用者體驗。比如你還可以適度結合 Activity 內容檢視使用動畫過渡效果,比如:

或者這樣

以上兩種效果來自 GitHub 上的開源專案 saulmm/onboarding-examples-android,具體實現方式這裡不再贅述,作者專門寫有部落格可供借鑑學習:Avoiding cold starts on Android