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 会起做用.

相关文章
相关标签/搜索