作者:帥次
作者簡介:CSDN博客專家,歡迎點贊、收藏、評論
粉絲福利:公眾號「帥次」一個分享Android 體系技術·相關知識·面試題庫·技識訓助·干貨·資訊·高薪職位·教程的地方,
🔥 背景
用戶希望應用能夠快速打開,啟動時間過長的應用不能滿足這個期望,并且可能會令用戶失望,輕則鄙視你,重則直接卸載你的應用,
用戶不會在乎你的專案是不是過大,里面是不是有很多初始化的邏輯,他只在乎你-慢了,
所以咱們這篇文章有兩個目的:
-
啟動速度提升(用戶眼中的大神就是你)
-
優化代碼邏輯和規范(別讓自己成為繼任者中的XX)
今天咱們就來了解一下應用啟動內部機制和啟動速度優化,
🔥 啟動內部機制
應用有三種啟動狀態:
-
冷啟動;
-
溫啟動;
-
熱啟動,
💥 冷啟動
冷啟動是指應用從頭開始:冷啟動發生在設備啟動后第一次啟動應用程式 (Zygote>fork>app) ,或系統關閉應用程式后,
在冷啟動開始時,系統有三個任務, 這些任務是:
-
加載和啟動應用程式,
-
啟動后立即顯示應用程式的空白啟動頁面,
-
創建應用程式行程,
一旦系統創建了應用程式行程,應用程式行程就負責接下來的階段:
-
創建應用的物體,
-
啟動主執行緒,
-
創建主頁面,
-
繪制頁面上的View,
-
布局頁面,
-
執行首次的繪制,
如下圖:

-
Displayed Time:初始顯示時間
-
reportFullyDrawn():完全顯示的時間
注意:在創建 Application 和創建 Activity 期間可能會出現性能問題,
🌀 創建 Application
當應用程式啟動時,空白啟動頁面保留在螢屏上,直到系統首次完成應用程式的繪制,
如果你重寫了Application.onCreate(),系統將呼叫Application 上的onCreate()方法,之后,應用程式生成主執行緒,也稱為UI執行緒,并將創建主Activity的任務交給它,
🌀 創建Activity
應用行程創建你的Activity后,Activity會執行以下操作:
-
初始化值,
-
呼叫建構式,
-
呼叫 Activity 當前生命周期狀態的回呼方法,如 Activity.onCreate(),
注意:onCreate() 方法對加載時間的影響最大,因為它執行開銷最高的作業:加載UI的布局和渲染,以及初始化Activity運行所需的物件,
💥 熱啟動
熱啟動時,系統將應用從后臺拉回前臺,應用程式的 Activity 在記憶體中沒有被銷毀,那么應用程式可以避免重復物件初始化,UI的布局和渲染,
如果 Activity 被銷毀則需要重新創建,
和冷啟動的區別: 不需要創建 Application,
💥 溫啟動
溫啟動介于冷啟動和熱啟動中間吧,例如:
-
用戶按回傳鍵退出應用,然后重新啟動,行程可能還沒有被殺死,但應用必須通過呼叫onCreate()重新創建 Activity,
-
系統回收了應用的記憶體,然后用戶重新運行應用,應用行程和Activity都需要重新啟動,
咱們看看他們共同消耗多長時間,
🔥 查詢的啟動時間
💥 初始顯示時間(Time to initial display)
在 Android 4.4(API 級別 19)及更高版本中,logcat 包含一個輸出行,其中包含一個名為 Displayed 的值, 此值表示啟動流程和完成在螢屏上繪制相應活動之間經過的時間量, 經過的時間包含以下事件序列:
-
啟動行程,
-
初始化物件,
-
創建并初始化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() 方法來測量應用程式啟動和所有資源和視圖層次結構的完整顯示之間經過的時間,在應用程式執行延遲加載的情況下,這可能很有價值,在延遲加載中,應用程式不會阻止視窗的初始繪制,而是異步加載資源并更新視圖層次結構,
這里我在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
圖例講解:
然后你會發現界面出來好一會才打這個日志,看到這里我覺得好多人已經知道怎么去優化啟動速度了,
🔥 性能遲緩分析
看到上面的實驗其實三種啟動情況,受我們影響的方面在于 application 和 activity ,
💥 Application 初始化
當你的代碼覆寫 Application 物件并在初始化該物件時執行繁重的作業或復雜的邏輯時,啟動性能可能會受到影響, 產生的原因包括:
-
應用程式的初始onCreate()函式,如:執行了不需要立即執行的初始化,
-
應用程式初始化的任何全域單例物件,如:一些不必要的物件,
-
可能發生的任何磁盤I/O、反序列化或緊密回圈,
解決方案
無論問題在于不必要的初始化還是磁盤I/O,解決方案都是延遲初始化,換句話說,你應該只初始化立即需要的物件,不要創建全域靜態物件,而是轉向單例模式,應用程式只在第一次需要時初始化物件,
此外,考慮使用依賴注入框架(如Hilt)
💥 Activity初始化
活動創建通常需要大量高開銷作業, 通常,有機會優化這項作業以實作性能改進,
產生的原因包括:
-
加載大型或復雜的布局,
-
阻止在磁盤或網路 I/O 上繪制螢屏,
-
加載和解碼Bitmap,
-
VectorDrawable 物件,
-
Activity 初始化任何全域單例物件,
-
所有資源初始化,
解決方案如下,
🌀 布局優化
-
通過減少冗余或嵌套布局來扁平化視圖層次結構,
-
布局復用(< include/>和 < merge/> )
-
使用ViewStub,不加載在啟動期間不需要可見的 UI 部分,
🌀 代碼優化
-
不必要的初始化還是磁盤I/O,延遲初始化
-
資源初始化分類,以便應用程式可以在不同的執行緒上延遲執行,
-
動態加載資源和Bitmap
關于這兩塊的優化后續會有單獨的文章去寫,
🔥 阻塞實驗
💥 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秒都放在作業執行緒去操作
這個就是把代碼放在如下代碼中執行即可,就不全部貼出來了,
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 應用啟動時,尤其是大型應用, 經常出現幾秒鐘的黑屏或白屏,黑屏或白屏取決于主界面 Activity 的主題風格,
💥 優雅的解決黑白屛
Android 應用啟動時很多大型應用都會有一個廣告(圖片及視頻)頁或閃屏頁(2-3S),這并不是開發者想要放上去的,而是為了避免上述啟動白屏導致用戶體很差,當然你可以珍惜這2-3秒做一個異步加載或者請求,
寫到這里,應用啟動模式、啟動時間、啟動速度優化算是完事了,當然后面如果有更好的優化方案還會繼續補充,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/323049.html
標籤:其他
上一篇:make 除錯方法
下一篇:力扣——四數之和
