一、啓動流程
①、點擊桌面App圖標,Launcher進程採用Binder IPC向system_server進程發起startActivity請求;
②、system_server進程接收到請求後,向zygote進程發送創建進程的請求;
③、Zygote進程fork出新的子進程,即App進程;
④、App進程,通過Binder IPC向sytem_server進程發起attachApplication請求;
⑤、system_server進程在收到請求後,進行一系列準備工作後,再通過binder IPC向App進程發送scheduleLaunchActivity請求;⑥、App進程的binder線程(ApplicationThread)在收到請求後,通過handler向主線程發送LAUNCH_ACTIVITY消息;
⑦、主線程在收到Message後,通過反射機制創建目標Activity,並回調Activity.onCreate()等方法。
⑧、到此,App便正式啓動,開始進入Activity生命週期,執行完onCreate/onStart/onResume方法,UI渲染結束後便可以看到App的主界面。
二、啓動狀態
應用有三種啓動狀態,每種狀態都會影響應用向用戶顯示所需的時間:冷啓動
、溫啓動
與熱啓動
。在冷啓動中,應用從頭開始啓動。在另外兩種狀態中,系統需要將後臺運行的應用帶入前臺。建議始終在假定冷啓動的基礎上進行優化。這樣做也可以提升溫啓動和熱啓動的性能。
冷啓動:
冷啓動是指應用從頭開始啓動:系統進程在冷啓動後才創建應用進程。發生冷啓動的情況包括應用自設備啓動後或系統終止應用後首次啓動。熱啓動:
在熱啓動中,系統的所有工作就是將 Activity 帶到前臺。只要應用的所有 Activity 仍駐留在內存中,應用就不必重複執行對象初始化、佈局加載和繪製。溫啓動:
溫啓動包含了在冷啓動期間發生的部分操作;同時,它的開銷要比熱啓動高。有許多潛在狀態可視爲溫啓動。例如:
用戶在退出應用後又重新啓動應用。進程可能未被銷燬,繼續運行,但應用需要執行
onCreate()
從頭開始重新創建 Activity。系統將應用從內存中釋放,然後用戶又重新啓動它。進程和 Activity 需要重啓,但傳遞到
onCreate()
的已保存的實例savedInstanceState
對於完成此任務有一定助益。
三、啓動耗時統計
3.1、系統日誌統計
在 Android 4.4(API 級別 19)及更高版本中,logcat 包含一個輸出行,其中包含名爲 Displayed
的值。此值代表從啓動進程到在屏幕上完成對應 Activity 的繪製所用的時間。
3.2、adb命令統計
adb shell
am start -S -R -W [包名]/[啓動的Activity全類名、或在一個包下直接 .Activity]
-
S
:表示每次啓動前先強制殺死應用進程,達到每次都是冷啓動,一定要大寫 -
R
:表示重複次數,一定要大寫 -
W
:表示是否等待輸出日誌信息,一定要大寫 - 後面接包名加Activity名稱;要啓動的Activity一定要有< intent-filter>標籤或者android:exported="true"屬性,否則會報SecurityException: Permission Denial異常
開發者一般只要關心TotalTime即可,這個時間纔是自己應用真正啓動的耗時。
3.3、啓動耗時統計
adb 命令啓動應用,一般會輸入三個值:ThisTime
、TotalTime
與WaitTime
。
-
WaitTime
:包括前一個應用Activity pause的時間和新應用啓動的時間; -
ThisTime
:表示一連串啓動Activity的最後一個Activity的啓動耗時; -
TotalTime
:表示新應用啓動的耗時,包括新進程的啓動和Activity的啓動,但不包括前一個應用Activity pause的耗時
四、CPU Profile
-
Debug API
除了直接使用 Profile 啓動之外,我們還可以藉助Debug API生成trace文件。
public class MyApplication extends Application {
public MyApplication() {
Debug.startMethodTracing("enjoy");
}
//.....
}
public class MainActivity extends AppCompatActivity {
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Debug.stopMethodTracing();
}
//.......
}
運行App,則會在sdcard中生成一個enjoy.trace文件(需要sdcard讀寫權限)。將手機中的trace文件保存至電腦,隨後拖入Android Studio即可。
三、StrictMode
StrictMode是一個開發人員工具,它可以檢測出我們可能無意中做的事情,並將它們提請我們注意,以便我們能夠修復它們。
StrictMode最常用於捕獲應用程序主線程上的意外磁盤或網絡訪問。幫助我們讓磁盤和網絡操作遠離主線程,可以使應用程序更加平滑、響應更快。
public class MyApplication extends Application {
public MyApplication() {
}
@Override
public void onCreate() {
if (BuildConfig.DEBUG) {
//線程檢測策略
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads() //讀、寫操作
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.penaltyDeath()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects() //Sqlite對象泄露
.detectLeakedClosableObjects() //未關閉的Closable對象泄露
.penaltyLog() //違規打印日誌
.penaltyDeath() //違規崩潰
.build());
}
super.onCreate();
}
}
六、冷啓動白屏問題
當系統加載並啓動 App 時,需要耗費相應的時間,這樣會造成用戶會感覺到當點擊 App 圖標時會有 “延遲” 現象,爲了解決這一問題,Google 的做法是在 App 創建的過程中,先展示一個空白頁面,讓用戶體會到點擊圖標之後立馬就有響應。
如果你的application或activity啓動的過程太慢,導致系統的BackgroundWindow沒有及時被替換,就會出現啓動時白屏或黑屏的情況(取決於Theme主題是Dark還是Light)。
消除啓動時的黑/白屏問題,大部分App都採用自己在Theme中設置背景圖的方式來解決。
<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="windowNoTitle">true</item>
</style>
<style name="AppTheme.Launcher">
<item name="android:windowBackground">@drawable/bg</item>
<item name="android:windowFullscreen">true</item>
</style>
</resources>
<activity android:name=".MainActivity" android:theme="@style/AppTheme.Launcher">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
然後在Activity的onCreate方法,把Activity設置回原來的主題。
@Override
protected void onCreate(Bundle savedInstanceState) {
//替換爲原來的主題,在onCreate之前調用
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
}
七、總結
啓動速度優化也會涉及到佈局優化與卡頓優化,包括內存抖動等問題。優化是一條持續的道路,很多時候我們會發現通過各種檢測手段花費了大量的精力去對代碼進行修改得到的優化效果可能並不理想。因爲優化就是一點一滴積累下來的,我們平時在編碼的過程中就需要多注意自己的代碼性能。
可能實際過程中優化並不會很順利,不同的設備上可能表現不一樣。我們只能結合對業務、對自己代碼的瞭解去不斷去實踐。
1)、合理的使用異步初始化、延遲初始化、懶加載機制。
2)、啓動過程避免耗時操作,如數據庫 I/O操作不要放在主線程執行。
3).、類加載優化:提前異步執行類加載。
4)、合理使用IdleHandler進行延遲初始化。
5)、簡化佈局