Android 勇闖高階性能優(yōu)化之啟動優(yōu)化篇
🔥 背景
用戶不會在乎你的項目是不是過大,里面是不是有很多初始化的邏輯。他只在乎你-慢了。
所以咱們這篇文章有兩個目的:
啟動速度提升(用戶眼中的大神就是你)
優(yōu)化代碼邏輯和規(guī)范(別讓自己成為繼任者中的XX)
今天咱們就來了解一下應用啟動內部機制和啟動速度優(yōu)化。
🔥 啟動內部機制
應用有三種啟動狀態(tài):
- 冷啟動;
- 溫啟動;
- 熱啟動。
💥 冷啟動
冷啟動是指應用從頭開始:冷啟動發(fā)生在設備啟動后第一次啟動應用程序 (Zygote>fork>app) ,或系統(tǒng)關閉應用程序后。
在冷啟動開始時,系統(tǒng)有三個任務。 這些任務是:
- 加載和啟動應用程序。
- 啟動后立即顯示應用程序的空白啟動頁面。
- 創(chuàng)建應用程序進程。
一旦系統(tǒng)創(chuàng)建了應用程序進程,應用程序進程就負責接下來的階段:
- 創(chuàng)建應用的實體。
- 啟動主線程。
- 創(chuàng)建主頁面。
- 繪制頁面上的View。
- 布局頁面。
- 執(zhí)行首次的繪制。
如下圖:

- Displayed Time:初始顯示時間
- reportFullyDrawn():完全顯示的時間
注意:在創(chuàng)建 Application 和創(chuàng)建 Activity 期間可能會出現(xiàn)性能問題。
🌀 創(chuàng)建 Application
當應用程序啟動時,空白啟動頁面保留在屏幕上,直到系統(tǒng)首次完成應用程序的繪制。
如果你重寫了Application.onCreate(),系統(tǒng)將調用Application 上的onCreate()方法。之后,應用程序生成主線程,也稱為UI線程,并將創(chuàng)建主Activity的任務交給它。
🌀 創(chuàng)建Activity
應用進程創(chuàng)建你的Activity后,Activity會執(zhí)行以下操作:
- 初始化值。
- 調用構造函數(shù)。
- 調用 Activity 當前生命周期狀態(tài)的回調方法,如 Activity.onCreate()。
注意:onCreate() 方法對加載時間的影響最大,因為它執(zhí)行開銷最高的工作:加載UI的布局和渲染,以及初始化Activity運行所需的對象。
💥 熱啟動
熱啟動時,系統(tǒng)將應用從后臺拉回前臺,應用程序的 Activity 在內存中沒有被銷毀,那么應用程序可以避免重復對象初始化,UI的布局和渲染。
如果 Activity 被銷毀則需要重新創(chuàng)建。
和冷啟動的區(qū)別: 不需要創(chuàng)建 Application。
💥 溫啟動
溫啟動介于冷啟動和熱啟動中間吧。例如:
用戶按返回鍵退出應用,然后重新啟動。進程可能還沒有被殺死,但應用必須通過調用onCreate()重新創(chuàng)建 Activity。
系統(tǒng)回收了應用的內存,然后用戶重新運行應用。應用進程和Activity都需要重新啟動。
咱們看看他們共同消耗多長時間。
🔥 查詢的啟動時間
💥 初始顯示時間(Time to initial display)
在 Android 4.4(API 級別 19)及更高版本中,logcat 包含一個輸出行,其中包含一個名為 Displayed 的值。 此值表示啟動流程和完成在屏幕上繪制相應活動之間經過的時間量。 經過的時間包含以下事件序列:
- 啟動進程。
- 初始化對象。
- 創(chuàng)建并初始化Activity。
- 加載布局。
- 第一次繪制你的應用程序。
注意這里查看日志需要如下操作:

報告的日志行類,如下圖:

//冷啟動
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +1s355ms
//溫啟動(進程被殺死)
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +1s46ms
//熱啟動
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +289ms
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +253ms
圖例講解:
第一個時間,冷啟動時間:+1s355ms;
然后我們在后臺殺死進程,再次啟動應用;
第二個時間,溫啟動時間:+1s46ms;
這里咱們在后臺殺死進程所以:應用進程和Activity需要重新啟動。
第三個時間:熱啟動時間:+289ms 和 +253ms;
按返回鍵,僅退出activity。所以耗時比較短。
當然整體看這個應用開啟時間并不長,因為 Demo 的 Application 和 Activity 都沒有進行太多的操作。
💥 完全顯示時間(Time to full display)
你可以使用 reportFullyDrawn() 方法來測量應用程序啟動和所有資源和視圖層次結構的完整顯示之間經過的時間。在應用程序執(zhí)行延遲加載的情況下,這可能很有價值。在延遲加載中,應用程序不會阻止窗口的初始繪制,而是異步加載資源并更新視圖層次結構。
這里我在Activity.onCreate()中加了個工作線程。并在里面調用reportFullyDrawn() 方法。代碼如下:
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(this.getClass().getName(), "onCreate");
setContentView(R.layout.activity_main);
...
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
reportFullyDrawn();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
報告的日志行類,如下圖:

I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +3s970ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +3s836ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +3s107ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +3s149ms
圖例講解:
然后你會發(fā)現(xiàn)界面出來好一會才打這個日志??吹竭@里我覺得好多人已經知道怎么去優(yōu)化啟動速度了。
🔥 性能遲緩分析
看到上面的實驗其實三種啟動情況,受我們影響的方面在于 application 和 activity 。
💥 Application 初始化
當你的代碼覆蓋 Application 對象并在初始化該對象時執(zhí)行繁重的工作或復雜的邏輯時,啟動性能可能會受到影響。 產生的原因包括:
- 應用程序的初始onCreate()函數(shù)。如:執(zhí)行了不需要立即執(zhí)行的初始化。
- 應用程序初始化的任何全局單例對象。如:一些不必要的對象。
- 可能發(fā)生的任何磁盤I/O、反序列化或緊密循環(huán)。
解決方案
無論問題在于不必要的初始化還是磁盤I/O,解決方案都是延遲初始化。換句話說,你應該只初始化立即需要的對象。不要創(chuàng)建全局靜態(tài)對象,而是轉向單例模式,應用程序只在第一次需要時初始化對象。
此外,考慮使用依賴注入框架(如Hilt)
💥 Activity初始化
活動創(chuàng)建通常需要大量高開銷工作。 通常,有機會優(yōu)化這項工作以實現(xiàn)性能改進。
產生的原因包括:
- 加載大型或復雜的布局。
- 阻止在磁盤或網絡 I/O 上繪制屏幕。
- 加載和解碼Bitmap。
- VectorDrawable 對象。
- Activity 初始化任何全局單例對象。
- 所有資源初始化。
解決方案如下。
🌀 布局優(yōu)化
- 通過減少冗余或嵌套布局來扁平化視圖層次結構。
- 布局復用(< include/>和 < merge/> )
- 使用ViewStub,不加載在啟動期間不需要可見的 UI 部分。
🌀 代碼優(yōu)化
- 不必要的初始化還是磁盤I/O,延遲初始化
- 資源初始化分類,以便應用程序可以在不同的線程上延遲執(zhí)行。
- 動態(tài)加載資源和Bitmap
關于這兩塊的優(yōu)化后續(xù)會有單獨的文章去寫。
🔥 阻塞實驗
💥 Application 阻塞 2秒, Activity 阻塞 2秒
🌀 SccApp.class
public class SccApp extends Application {
@RequiresApi(api = Build.VERSION_CODES.P)
@Override
public void onCreate() {
super.onCreate();
String name = getProcessName();
MLog.e("ProcessName:"+name);
getProcessName("com.scc.demo");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
🌀 MainActivity.class
public class MainActivity extends ActivityBase implements View.OnClickListener {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(this.getClass().getName(), "onCreate");
setContentView(R.layout.activity_main);
...
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
reportFullyDrawn();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
報告的日志,如下:
//冷啟動
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +5s458ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +8s121ms
//溫啟動(進程被殺死)
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +5s227ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +7s935ms
//熱啟動
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +2s304ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +5s189ms
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +2s322ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +5s169ms
💥 將Appliacation 和Activity阻塞的2秒都放在工作線程去操作
這個就是把代碼放在如下代碼中執(zhí)行即可,就不全部貼出來了。
new Thread(new Runnable() {
@Override
public void run() {
...
}
}).start();
運行結果如下:
//冷啟動
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +1s227ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +3s957ms
//溫啟動(進程被殺死)
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +1s83ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +3s828ms
//熱啟動
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +324ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +3s169ms
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +358ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +3s207ms
🔥 APP 啟動黑/白屏
Android 應用啟動時,尤其是大型應用, 經常出現(xiàn)幾秒鐘的黑屏或白屏,黑屏或白屏取決于主界面 Activity 的主題風格。
💥 優(yōu)雅的解決黑白屛
Android 應用啟動時很多大型應用都會有一個廣告(圖片及視頻)頁或閃屏頁(2-3S)。這并不是開發(fā)者想要放上去的,而是為了避免上述啟動白屏導致用戶體很差。當然你可以珍惜這2-3秒做一個異步加載或者請求。
寫到這里。應用啟動模式、啟動時間、啟動速度優(yōu)化算是完事了。當然后面如果有更好的優(yōu)化方案還會繼續(xù)補充。
到此這篇關于Android 勇闖高階性能優(yōu)化之啟動優(yōu)化篇的文章就介紹到這了,更多相關Android 啟動優(yōu)化內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Android SurfaceView拍照錄像實現(xiàn)方法
這篇文章主要為大家詳細介紹了Android SurfaceView拍照錄像實現(xiàn)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-02-02
Android中獲取資源 id 及資源 id 的動態(tài)獲取
這篇文章主要介紹了 Android中獲取資源 id 及資源 id 的動態(tài)獲取的相關資料,需要的朋友可以參考下2017-01-01
Android 判斷某個Activity 是否在前臺運行的實例
下面小編就為大家分享一篇Android 判斷某個Activity 是否在前臺運行的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-03-03
Android 源碼淺析RecyclerView ItemAnimator
這篇文章主要為大家介紹了Android 源碼淺析RecyclerView ItemAnimator,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12

