Android應用啓動優化:一種DelayLoad的實現和原理

今天看到一篇講關於延遲加載機制的文章,在時間調度上堪稱完美,因此轉載過來記錄。html

原文連接:http://androidperformance.com/2015/11/18/Android-app-lunch-optimize-delay-load.htmlandroid

下篇連接:http://androidperformance.com/2015/12/29/Android%E5%BA%94%E7%94%A8%E5%90%AF%E5%8A%A8%E4%BC%98%E5%8C%96-%E4%B8%80%E7%A7%8DDelayLoad%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%92%8C%E5%8E%9F%E7%90%86-%E4%B8%8B%E7%AF%87.htmlapp

深度講解了,下篇中深度講解了第三種延遲加載機制的邏輯。異步

強烈推薦給各位。ide

1. 應用啓動優化概述

在 Android 開發中,應用啓動速度是一個很是重要的點,應用啓動優化也是一個很是重要的過程.對於應用啓動優化,其實核心思想就是在啓動過程當中少作事情,具體實踐的時候無非就是下面幾種:函數

  1. 異步加載
  2. 延時加載
  3. 懶加載

不用一一去解釋,作過啓動優化的估計都使用過,本篇文章將詳細講解一下一種延時加載的實現以及其原理.
其實這種加載的實現是很是簡單的,可是其中的原理可能比較複雜,還涉及到Looper/Handler/MessageQueue/VSYNC等.以及其中碰到的一些問題,還會有一些我本身額外的思考.oop

2. 優化後的DelayLoad的實現

一提到DelayLoad,你們可能第一時間想到的就是在 onCreate 裏面調用 Handler.postDelayed方法, 將須要 Delay 加載的東西放到這裏面去初始化, 這個也是一個比較方便的方法. Delay一段時間再去執行,這時候應用已經加載完成,界面已經顯示出來了, 不過這個方法有一個致命的問題: 延遲多久?
你們都知道,在 Android 的高端機型上,應用的啓動是很是快的 , 這時候只須要 Delay 很短的時間就能夠了, 可是在低端機型上,應用的啓動就沒有那麼快了,並且如今應用爲了兼容舊的機型,每每須要 Delay 較長的時間,這樣帶來體驗上的差別是很明顯的.post

這裏先說優化方案:優化

  1. 首先 , 建立 Handler 和 Runnable 對象, 其中 Runnable 對象的 run方法裏面去更新 UI 線程.spa

    1
    2
    3
    4
    5
    6
    7
    8
    private Handler myHandler = new Handler();
    private Runnable mLoadingRunnable = new Runnable() {
    
      @Override
      public void run() {
        updateText(); //更新UI線程
      }
    };
  2. 在主 Activity 的 onCreate 中加入下面的代碼

    1
    2
    3
    4
    5
    6
    7
    getWindow().getDecorView().post(new Runnable() {
    
      @Override
      public void run() {
        myHandler.post(mLoadingRunnable);
      }
    });

其實實現的話很是簡單,咱們來對比一下三種方案的效果.

3. 三種寫法的差別對比

爲了驗證咱們優化的 DelayLoad的效果,咱們寫了一個簡單的app , 這個 App 中包含三張不一樣大小的圖片,每張圖片下面都會有一個 TextView , 來標記圖片的顯示高度和寬度. MainActivity的代碼以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class MainActivity extends AppCompatActivity {
  private static final int DEALY_TIME = 300 ;

  private ImageView imageView1;
  private ImageView imageView2;
  private ImageView imageView3;
  private TextView textView1;
  private TextView textView2;
  private TextView textView3;

  private Handler myHandler = new Handler();
  private Runnable mLoadingRunnable = new Runnable() {

    @Override
    public void run() {
      updateText();
    }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    imageView1 = (ImageView) findViewById(R.id.image1);
    imageView2 = (ImageView) findViewById(R.id.image2);
    imageView3 = (ImageView) findViewById(R.id.image3);

    textView1 = (TextView) findViewById(R.id.text1);
    textView2 = (TextView) findViewById(R.id.text2);
    textView3 = (TextView) findViewById(R.id.text3);

//  第一種寫法:直接Post
    myHandler.post(mLoadingRunnable);

//  第二種寫法:直接PostDelay 300ms.
//  myHandler.postDelayed(mLoadingRunnable, DEALY_TIME);

//  第三種寫法:優化的DelayLoad
//  getWindow().getDecorView().post(new Runnable() {
//    @Override
//    public void run() {
//      myHandler.post(mLoadingRunnable);
//    }
//  });

    // Dump當前的MessageQueue信息.
    getMainLooper().dump(new Printer() {

      @Override
      public void println(String x) {
        Log.i("Gracker",x);
      }
    },"onCreate");
}

  private void updateText() {
    TraceCompat.beginSection("updateText");
    textView1.setText("image1 : w=" + imageView1.getWidth() +
      " h =" + imageView1.getHeight());
    textView2.setText("image2 : w=" + imageView2.getWidth() +
      " h =" + imageView2.getHeight());
    textView3.setText("image3 : w=" + imageView3.getWidth() +
      " h =" + imageView3.getHeight());
    TraceCompat.endSection();
}

咱們須要關注兩個點:

  • updateText 這個函數是何時被執行的?
  • App 啓動後,三個圖片的長寬是否能夠被正確地顯示出來?
  • 是否有 Delay Load 的效果?

3.1 第一種寫法

  1. updateText執行的時機?
    下面是第一種寫法的Trace圖:

    能夠看到 updateText 是在 Activity 的 onCreate/onStart/onResume三個回調執行完成後纔去執行的.

  2. 圖片的寬高是否正確顯示?

    從圖片看一看到,寬高並無顯示. 這是爲何呢? 這個問題就要從Activity 的 onCreate/onStart/onResume三個回調提及了. 其實Activity 的 onCreate/onStart/onResume三個回調中,並無執行Measure和Layout操做, 這個是在後面的performTraversals中才執行的. 因此在這以前寬高都是0.

  3. 是否有 Delay Load 的效果?
    並無. 由於咱們知道, 應用啓動的時候,要等兩次 performTraversals 都執行完成以後纔會顯示第一幀, 而 updateText 這個方法在第一個 performTraversals 執行以前就執行了. 因此 updateText 方法的執行時間是算在應用啓動的時間裏面的.

3.2 第二種寫法

第二種寫法咱們Delay了300ms .咱們來看一下表現.

  1. updateText執行的時機?

    能夠看到,這種寫法的話,updateText是在兩個performTraversals 執行完成以後(這時候 APP 的第一幀才顯示出來)纔去執行的, 執行完成以後又調用了一次 performTraversals 將 TextView 的內容進行更新.

  2. 圖片的寬高是否正確顯示?

    從上圖能夠看到,圖片的寬高是正確顯示了出來. 緣由上面已經說了,measure/layout執行完成後,寬高的數據就能夠獲取了.

  3. 是否有 Delay Load 的效果?
    不必定,取決於 Delay的時長.
    從前面的 Trace 圖上咱們能夠看到 , updateText 方法因爲 Delay 了300ms, 因此在應用第一幀顯示出來170ms以後, 圖片的文字信息才進行了更新. 這個是有 Delay Load 的效果的.
    可是這裏只是一個簡單的TextView的更新, 若是是較大模塊的加載 , 用戶視覺上會有很明顯的 「 空白->內容填充」 這個過程, 或者會附加」閃一下」特效…這顯然是咱們不想看到的.

    有人會說:能夠把Delay的時間減少一點嘛,這樣就不會閃了. 話是這麼說,可是因爲 Android 機器的多元性(其實就是有不少高端機器,也有不少低端機器) , 在這個機子上300ms的延遲算是快,在另一個機子上300ms算是很慢.

    咱們將Delay時間調整爲50ms, 其Trace圖以下:

    能夠看到,updateText 方法在第一個 performTraversals 以後就執行了,因此也沒有 Delay Load 的效果(雖然寬高是正確顯示了,由於在第一個 performTraversals 方法中就執行了layout和measure).

3.3 第三種寫法

通過前兩個方法 , 咱們就會想, 若是能不使用Delay方法, updateText 方法能在 第二個performTraversals 方法執行完成後(即APP第一幀在屏幕上顯示),立刻就去執行,那麼即起到了 Delay Load的做用,又能夠正確顯示圖片的寬高.
第三種寫法就是這個效果:

  1. updateText執行的時機?

    能夠看到這種寫法. updateText 在第二個 performTraversals 方法執行完成後立刻就執行了, 而後下一個 VSYNC 信號來了以後, TextView就更新了.

  2. 圖片的寬高是否正確顯示?
    固然是正確顯示的.如圖:

  3. 是否有 Delay Load 的效果?
    從 Trace 圖上看, 是有 Delay Load的效果的, 並且能夠在應用第一幀顯示後立刻進行數據 Load , 不用考慮 Delay時間的長短.

4. 一些思考

關於優化的 Delay Load 的實現,從代碼層面來看實際上是很是簡單的.其帶來的效果也是很讚的. 可是實現以後咱們還須要思考一下,爲什麼這麼作就能夠實現這種功能呢?很顯然要回答這個問題,咱們須要知道更底層的一些東西.這個還涉及到 Handler/Message/MessageQueue/Looper/VSYNC/ViewRootImpl等知識. 往大里說應該還涉及到AMS/WMS等.因爲涉及到的東西比較多,我就不在這一篇裏面闡述了, 下一篇文章將會從從原理上講解一下爲什麼優化的 Delay Load 會起做用.

相關文章
相關標籤/搜索