1. 程式人生 > >效能優化之App啟動時間

效能優化之App啟動時間

App啟動模式分類

1.冷啟動 冷啟動狀態:系統不存在該應用的程序。啟動應用才能創建出應用的程序。 一般是中應用在開機後或者系統停止後的第一次啟動過程。因為系統和應用在冷啟動時需要做跟多的工作 所以減少它的啟動時間是最難的。建議始終基於冷啟動的假設進行優化,因為這樣做同樣提升了另兩種啟動狀態的表現。 冷啟動初始時,系統完成三個任務

  • 啟動和載入應用

  • 建立應用的專屬程序

  • 啟動後立刻顯示啟動的檢視

    一旦系統穿件應用的專屬程序,該程序開始建立應用:

  • 建立應用物件

  • 啟動主執行緒

  • 建立MainActivity

  • 載入檢視(inflating views)

  • 渲染布局(layout)

  • 渲染布局(layout)

  • .渲染布局(layout)

  • 執行初始化繪製

    啟動流程大致如下:

       點選Launcher 上的 icon開載入app -->立即顯示白屏或黑屏等 --> Application onCreate --> Activity Init---->
       Activity onCreate ---> 初始化資料,填充顯示View ---> Activity onResume等
    

    2.暖啟動

    在暖啟動中,系統只需要把 Activity 切換到前臺執行。如果應用的該 Activity 之前駐留在記憶體中, 那麼應用程式就不用重新初始化物件和渲染布局,但是,如果由於響應了低記憶體事件, 例如在 onTrimMemory() 方法中清除了資源物件,那麼這些物件就需要在熱啟動時重新建立 處於冷啟動與熱啟動之間,既包含一些冷啟動的操作,又含有部分熱啟動的功能。例如以下兩種狀態:

    使用者退出APP後重新Launch。此時此APP的程序可能會存在,然而,Activity 必須重新建立並呼叫onCreate方法 APP 被快取中清理掉時。此時使用者重新Launch APP時,此app的程序和Activity都需要重新建立,但是任務棧中會儲存部分APP例項資料(bundle型別)傳遞個Activity onCreate方法

    3.熱啟動 熱啟動為冷啟動的過程操作的子集,記憶體開銷也更低。以下這些情況可以認為是熱啟動: 1.使用者退出應用,但隨後重新啟動它。應用的程序還在執行,但應用必須重新從 onCreate() 開始建立 Activity。 2.系統從記憶體中清除了應用(非使用者主動),然後使用者重新啟動它。程序和 Activity 需要重新啟動,但 onCreate() 將接收到儲存狀態的 Bundle。 事實上,savedInstanceState 在使用者未主動銷燬 Activity 時系統就會呼叫。

    App的啟動過程 簡單解釋一下App的啟動過程:

    1.點選Launcher,啟動程式,通知ActivityManagerService

    2.ActivityManagerService通知zygote程序孵化出應用程序,分配記憶體空間等

    3.執行該應用ActivityThread的main()方法

    4.應用程式通知ActivityManagerService它已經啟動,ActivityManagerService儲存一個該應用的代理物件,ActivityManagerService通過它可以控制應用程序

    5.ActivityManagerService通知應用程序建立入口的Activity例項,執行它的生命週期

    啟動過程中Application和入口Activity的生命週期方法按如下順序呼叫:

    1.Application 構造方法

    2.attachBaseContext()

    3.onCreate()

    4.入口Activity的物件構造

    5.setTheme() 設定主題等資訊

    6.入口Activity的onCreate()

    7.入口Activity的onStart()

    8.入口Activity的onResume()

    9.入口Activity的onAttachToWindow()

    10.入口Activity的onWindowFocusChanged()

App啟動時間測量與分析

1.通過adb命令測量App冷啟動時間

   adb shell am start -W [packageName]/[packageName.MainActivity]
       或
       adb [-d|-e|-s <serialNumber>] shell am start -S -W [packageName]/[packageName.MainActivity] -c android.intent.category.LAUNCHER -a android.intent.action.MAIN
	  
	   例子如下:
	   adb shell am start -W com.example.administrator.androidtestdemo/com.example.administrator.BluetoothBLE.BlueToothActivity

2.在程式碼中測量app啟動效能的方法

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            Trace.beginSection("programandroid");
        }
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_blue_tooth);
        ButterKnife.bind(this);
        init();
        Configuration config = getResources().getConfiguration();
        int smallestScreenWidth = config.smallestScreenWidthDp;
        Log.i("BlueToothActivity","smallest width : "+ smallestScreenWidth);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            Trace.endSection();
        }
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            reportFullyDrawn();
        }
		日誌:
		09-19 14:54:06.456 1504-3591/? I/ActivityManager: Displayed com.example.administrator.androidtestdemo/com.example.administrator.BluetoothBLE.BlueToothActivity: +2s273ms
        Fully drawn com.example.administrator.androidtestdemo/com.example.administrator.BluetoothBLE.BlueToothActivity: +2s273ms

結合上述分析的我們優化的入口是應用和Activity物件的初始化及View展示 1.Applicatyion初始化開銷大。 Application的建立過程。如果執行復雜邏輯或者大量物件被初始化,將會影響app啟動時間 具體而言,就是你繼承了 Application 並在初始化時執行了不必要的程式碼,比如:初始化 MainActivity 的狀態資訊; 建立了大量臨時變數導致 GC(GC 在 ART 下影響很小);執行磁碟 I/O 操作(這甚至就會直接阻塞應用的執行); 反序列化操作;多重迴圈等等。

懶載入:只初始化那些必要的物件,而其他的全域性靜態物件移動到一個單例模式中。此外,可以考慮依賴注入框架 Dagger2 來建立物件及其依賴關係。

2.Activity 初始化開銷大 Activity 的建立中除了要避免 Application 建立中提到的問題,還需要注意以下問題: * 載入極其複雜的佈局 * 主執行緒中出現磁碟或網路 I/O * 載入和解碼 Bitmap * 渲染多個 VectorDrawable 物件。 解決問題的方法 1.檢視層次過深: 減少冗餘、巢狀的佈局層次。 不佈局繪製不可見的 UI,而是使用 ViewStub 物件在適當的時間佈局繪製。 2.大量的資源初始化: 調整資源初始化的位置,可以在不同的執行緒執行懶載入。 載入部分檢視,然後再載入大的點陣圖和其他資源。

3.啟動介面

     應用啟動時就會立即顯示啟動介面。而通常是個白屏。可以設定與應用類似的啟動動畫。
	 這樣可以向用戶隱藏這個啟動過程,使用者會感受到應用已經在運行了。顯示的介面就是應用的一部分或者流程的一部分。
  使用 Activity 的 windowBackground 屬性,在啟動時顯示簡單的自定義的畫面。
		  
		  建立啟動時顯示的畫面
		  <?xml version="1.0" encoding="utf-8"?>
		<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

			<item android:drawable="@color/colorAccent"/>
			<item>
				<bitmap android:src="@drawable/leak_canary_icon"
					android:gravity="center"/>
			</item>


		</layer-list>
		
		為啟動的Activity自定義一個Theme
		<style name="AppTheme.Launcher">
			<item name="android:windowBackground">@drawable/start_activity_background</item>
		</style>
		
		在 AndroidManifest 檔案中 Activity 的屬性裡設定該 style:
		 <activity android:name="com.example.administrator.BluetoothBLE.BlueToothActivity"
            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 後要正確的設定回正確的 style
		public class BlueToothActivity extends AppCompatActivity {
		  @Override
		  protected void onCreate(Bundle savedInstanceState) {
			// Make sure this is before calling super.onCreate
			setTheme(R.style.Theme_MyApp);
			super.onCreate(savedInstanceState);
			// ...
		  }
		}

4.利用TraceView分析啟動時間


			Debug.startMethodTracing("TestApp");
			...
			Debug.stopMethodTracing();
			  
	     執行程式, 會在sdcard上生成一個”TestApp.trace”的檔案.
			注意: 需要給程式加上寫儲存的許可權:

			<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
			通過adb pull將其匯出到本地
			adb pull /sdcard/TestApp.trace ~/testSpeed.trace
			開啟DDMS分析trace檔案,

5.啟動時間耗時常見原因及優化建議

	  1. 常見主要問題(持續補充ing)

                  部分資料庫及IO的操作發生在首屏Activity主執行緒;
		Application中建立了執行緒池;
		啟動時做密集沉重的初始化(Heavy app initialization);
		Multidex的使用,也是拖慢啟動速度的元凶;
		UI存在過度繪製;
		首屏Activity網路請求密集;
		非核心功能資源過早請求載入
		工作執行緒使用未設定優先順序;
		資訊未快取,重複獲取同樣資訊;
		流程問題:例如閃屏圖每次下載,當次使用;
		執行無用老程式碼;
		執行開發階段使用的程式碼;
		執行重複邏輯;
		呼叫三方SDK裡或者Demo裡的多餘程式碼;

	 2.建議:

		去掉無用但被執行的老程式碼;
		去掉開發階段使用但線上被執行的程式碼;
		去掉重複邏輯執行程式碼;
		UI渲染優化,去除重複繪製,減少UI重複繪製時間
		去掉呼叫三方SDK裡或者Demo裡的多餘程式碼;
		資訊快取,常用資訊只在第一次獲取,之後從快取中取;
		專案是多程序架構,只在主程序執行Application的onCreate();
		根據優先順序的劃分,一些初始化工作能否將任務優先順序劃分成3個等級,任務優先順序為2,3的,通過懶載入的方式在首頁渲染完成後進行載入
		避免I/O操作、反序列化、網路操作、佈局巢狀等。
		在Application的構造器方法、attachBaseContext()、onCreate()方法中不要進行耗時操作的初始化,一些資料預取放在非同步執行緒中,可以採取Callable實現。
        對於sp的初始化,因為sp的特性在初始化時候會對資料全部讀出來存在記憶體中,所以這個初始化放在主執行緒中不合適,反而會延遲應用的啟動速度,對於這個還是需要放在非同步執行緒中處理。
        對於MainActivity,由於在獲取到第一幀前,需要對contentView進行測量佈局繪製操作,儘量減少佈局的層次,考慮StubView的延遲載入策略,當然在onCreate、onStart、onResume方法中避免做耗時操作。