Android应用启动优化:一种DelayLoad的实现和原理(下篇)

上一篇文章我们使用第三种方法来实现延迟加载。不过上一篇写的比较简单,只是讲解了如何去实现,这一篇就来讲一下为何要这么做,以及这么做后面的原理。
其中会涉及到一些 Android 中的比较重要的类,以及 Activity 生命周期中比较重要的几个函数。
其实这个其中的原理比较简单,不过要弄清楚其实现的过程,还是一件蛮好玩的事情,其中会用到一些工具,自己加调试代码等,一步一步下来,自己对 Activity 的启动的理解又深了一层,希望大家读完之后也会对大家有一定的帮助。

 

上一篇中我们最终使用的 DelayLoad 的核心方法是在 Activity 的 onCreate 函数中加入下面的方法 :

我们一一来看涉及到的类和方法

1. Activity.getWindow 及 PhoneWindow 的初始化时机

Activity 的 getWindow 方法获取到的是一个 PhoneWindow 对象:

这个 mWindow 就是一个 PhoneWindow 对象,其初始化的时机为这个 Activity attach 的时候:

这里需要注意 Activity 的 attach 方法很早就会调用的,是要早于 Activity 的 onCreate 方法的。

总结:

  • PhoneWindow 与 Activity 是一对一的关系,通过上面的初始化过程你应该更加清楚这个概念
  • Android 中对 PhoneWindow 的注释是 :Android-specific Window ,可见其重要性
  • PhoneWindow 中有很多大家比较熟悉的方法,比如 setContentView / addContentView 等 ; 也有几个重要的内部类,比如:DecorView ;

2. PhoneWindow.getDecorView 及 DecorView 的初始化时机

上面我们说到 DecorView是 PhoneWindow 的一个内部类,其定义如下:

那么 DecorView 是什么时候初始化的呢?DecorView 是在 Activity 的父类的 onCreate 方法中被初始化的,比如我例子中的 MainActivity 是继承自 android.support.v7.app.AppCompatActivity ,当我们调用 MainActivity 的 super.onCreate(savedInstanceState); 的时候,就会调用下面的

由于我们导入的是 support.v7 包里面的AppCompatActivity, getDelegate() 得到的就是AppCompatDelegateImplV7 ,其 onCreate 方法如下:

就是这里的 mWindow.getDecorView() ,对 DecorView 进行了实例化:

第一次调用 getDecorView 的时候,会进入 installDecor 方法,这个方法对 DecorView 进行了一系列的初始化 ,其中比较重要的几个方法有:generateDecor / generateLayout 等,generateLayout 会从当前的 Activity 的 Theme 提取相关的属性,设置给 Window,同时还会初始化一个 startingView,添加到 DecorView上,也就是我们所说的 startingWindow。

总结

  • Decor 有装饰的意思,DecorView 官方注释为 “This is the top-level view of the window, containing the window decor” , 我们可以理解为 DecorView 是我们当前 Activity 的最下面的布局。所以我们打开 DDMS 查看 Tree Overview 的时候,可以发现最根部的那个 View 就是 DecorView:
  • 应用从桌面启动的时候,在主 Activity 还没有显示的时候,如果主题没有设置窗口的背景,那么我们就会看到白色(这个和手机的Rom也有关系),如果应用启动很慢,那么用户得看好一会白色。如果要避免这个,则可以在 Application 或者 Activity 的 Theme 中设置 WindowBackground , 这样就可以避免白色(当然现在各种大厂都是SplashActivity+广告我也是可以理解的)

3. Post

当我们调用 DecorView 的 Post 的时候,其实最终会调用 View 的 Post ,因为 DecorView 最终是继承 View 的:

注意这里的 mAttachInfo ,我们调用 post 是在 Activity 的 onCreate 中调用的,那么此时 mAttachInfo 是否为空呢?答案是 mAttachInfo 此时为空。

这里有一个点就是 Activity 的各个回调函数都是干嘛的?是不是平时自己写应用的时候,貌似在 onCreate 里面搞定一切就OK了, onResume ? onStart?没怎么涉及到嘛,其实不然。
onCreate 顾名思义就是 Create ,我们在前面看到,Activity 的 onCreate 函数做了很多初始化的操作,包括 PhoneWindow/DecorView/StartingView/setContentView等,但是 onCreate 只是初始化了这些对象.
真正要设置为显示则在 Resume 的时候,不过这些对开发者是透明了,具体可以看 ActivityThread 的 handleResumeActivity 函数,handleResumeActivity 中除了调用 Activity 的 onResume 回调之外,还初始化了几个比较重要的类:ViewRootImpl / ThreadedRenderer。

ActivityThread.handleResumeActivity:

主要是 wm.addView(decor, l); 这句,将 decorView 与 WindowManagerImpl联系起来,这句最终会调用到 WindowManagerGlobal 的 addView 函数,

我们知道 ViewRootImpl 是 View 系统的一个核心类,其定义如下:

ViewRootImpl 初始化的时候会对 AttachInfo 进行初始化,这就是为什么之前的在 onCreate 的时候 attachInfo 为空。ViewRootImpl 里面有很多我们比较熟悉也非常重要的方法,比如 performTraversals / performLayout / performMeasure / performDraw / draw 等。
我们继续 addView 中的root.setView(view, wparams, panelParentView); 传入的 view 为 decorView,root 为 ViewRootImpl ,这个函数中将 ViewRootImpl 的mView 变量 设置为传入的view,也就是 decorView。
这样来看,ViewRootImpl 与 DecorView 的关系我们也清楚了。

扯了一圈,我们再回到大标题的 Post 函数上,前面有说这个 Post 走的是 View 的Post 函数,由于 在 onCreate 的时候 attachInfo 为空,所以会走下面的分支:ViewRootImpl.getRunQueue().post(action);
注意这里的 getRunQueue 得到的并不是 Looper 里面的那个 MessageQueue,而是由 ViewRootImpl 维持的一个 RunQueue 对象,其核心为一个 ArrayList :

当我们执行了 Post 之后 ,其实只是把 Runnable 封装成一个 HandlerAction 对象存入到 ArrayList 中,当执行到 executeActions 方法的时候,将存在这里的 HandlerAction 再通过 executeActions 方法传入的 Handler 对象重新进行 Post。
那么 executeActions 方法是什么时候执行的呢?传入的 Handler 又是哪个 Handler 呢?

4. PerformTraversals

我们之前讲过,ViewRootImpl 的 performTraversals 方法是一个很核心的方法,每一帧绘制都会走一遍,调用各种 measure / layout / draw 等 ,最终将要显示的数据交给 hwui 去进行绘制。
我们上一节讲到的 executeActions ,就是在 performTraversals 中执行的:

可以看到这里传入的 Handler 是 mAttachInfo.mHandler ,上一节讲到 mAttachInfo 是在 ViewRootImpl 初始化的时候一起初始化的:

这里的 mHandler 是一个 ViewRootHandler 对象:

我们注意到 ViewRootHandler 在创建的时候并没有传入一个 Looper 对象,这意味着此 ViewRootHandler 的 Looper 就是 mainLooper。

这下我们就清楚了,我们在 onCreate 中 Post 的 runnable 对象,最终还是在第一个 performTraversals 方法执行的时候,加入到了 MainLooper 的 MessageQueue 里面了。

绕了一圈终于我们终于把文章最前面的那句话解释清楚了,当然中间还有很多的废话,不过我估计能耐着性子看到这里的人会很少,所以如果你看到了这里,可以在底下的评论里面将 index ++ ;这里 index = 0 ;就是看看几个人是真正认真看了这篇文章的。

5. UpdateText

接着 performTraversals 我们继续说,话说在第一篇文章 我们有讲到,Activity 在启动时,会在第二次执行 performTraversals 才会去真正的绘制,原因在于第一次执行 performTraversals 的时候,会走到 Egl 初始化的逻辑,然后会重新执行一次 performTraversals 。
所以前一篇文章的评论区有人问为何在 run 方法里面还要 post 一次,如果在 run 方法里面直接执行 updateText 方法 ,那么 updateText 就会在第一个 performTraversals 之后就执行,而不是在第一帧绘制完成后才去执行,所以我们又 Post 了一次 。所以大概的处理步骤如下:

第一步:Activity.onCreate –> Activity.onStart –> Activity.onResume

第二步:ViewRootImpl.performTraversals –>Runnable

第三步:Runnable –> ViewRootImpl.performTraversals

第四步:ViewRootImpl.performTraversals –> UpdateText

第五步:UpdateText

6. 总结

其实一路跟下来发现其实原理很简单,其实 DelayLoad 其实只是一个很小的点,关键是教大家如何去跟踪一个自己不认识的知识点或者优化,这里面主要用到了两个工具:Systrace 和 Method Trace, 以及源码编译和调试。
关于 Systrace 和 Method Trace 的使用,之后会有详细的文章去介绍,这两个工具非常有助于理解源码和一些技术的实现。

Systrace

Method Trace

源码编译与调试

代码

本文章所所涉及到的代码我放到了Github上:
https://github.com/Gracker/DelayLoadSample

 

1 6 收藏 6 评论

相关文章

可能感兴趣的话题



直接登录
最新评论
跳到底部
返回顶部