毕业一年了,都没有找到时间对Android技术进行整理,疲于项目工作,发现对一些基础性的疑惑没有进行追根溯源。若想在技术方面有所提升,对源码的解读是必须要迈过去的坎。以后要继续坚持写博客了。
首先回答:setContentView只是建立了View树,并没有进行渲染工作(其实真正的渲染工作是在onResume之后,后面我会从源码角度讲到)。也正是建立了View树,因此我们可以通过findViewById()来获取到View对象,但是由于并没有进行渲染视图的工作(没有执行ViewRootImpl.performTransversal),其实没有进行渲染工作的直接感受是,在onResume()方法里直接获取View.getHeight()/View.getWidth()得到的结果总是0,这是因为View还没有执行onMeasure()。
下面细细琢磨一下setContentView的流程:
setContentView->都是调用的PhoneWindow.setContentView()(注:PhoneWindow是Window的唯一实现类)。
那么这里的PhoneWindow对象(Activity.mWindow)是在何时进行初始化的呢?显然是在onCreate()的方法之前,不然就空指针了,那具体是什么时候呢?
ActivityThread.performLaunchActivity()->Activity.attach()
知道了如何初始化PhoneWindow,那么PhoneWindow具体做了什么工作呢?
参考:https://blog.csdn.net/stven_king/article/details/49095221
回答这个问题就必须引出ViewRootImpl,因为View的绘制工作都是由ViewRootImpl来进行完成的。
在前面已经分析过,PhoneWindow以及其内部的DecorView都是在执行了Activity.setContentView之后,就已经进行了初始化。
虽然PhoneWindow和DecorView都已经进行了初始化,但是并没有将DecorView加入到WindowManager中,从上面看到,直到执行了ActivityThread.handleResunmeActivity()才将DecorView与WindowManager进行了绑定。
下面就需要看下WindowManager.addView()是如何执行的?(WindowManagerImpl是WindowManager的实现类)
可以看到WindowManagerImpl里面执行了WindowManagerGlobal.addView(),WindowManageGlobal是全局的类,里面保存了WindowManger与View之间的一一映射关系,并且相当于WindowManager的代理类。
接下来看看WindowManagerGlobal.addView()具体做了什么?
最核心的一行代码ViewRootImpl.setView()
也正是ViewRootImpl.setView()方法里面触发了requestLayout()
performTraversals()方法实现了绘制View的经典三部曲的步骤。
这里需要注意的是scheduleTraversals(),大家可以往前看下,这个方法是安排绘制任务的方法,如果执行此方法,则必然会再次执行performTraversals(),因此有些时候我们会发现可能会处触发两次performTraversals(),并且这两次performTraversals()中,第一次performTraversals()不会进行执行onDraw(),知道第二次performTraversals()才会执行onDraw(),从代码中也可以看出来,如果执行了两次scheduleTraversal是(),则必然会少执行一次onDraw();
到此,综合1,2,我们分析了整个从lauch Activity开始,到setContView()来创建PhoneWindow和DecorView,再到绑定到WindowManager,最后绘制View的完整流程。相信大家如果从一步步慢慢跟着节奏,一定对整个流程有了清晰的了解。
onCreat()中执行View.post()时,mAttachInfo还没有初始化即为null(因为mAttachInfo在onAttachWindow()中初始化,并且onAttachWindow()在onCreat()之后执行),因此会执行getRunQueue().post(Runnable),这里是将Runnable保存到HandleActionQueue队列中,但是该Runnable并没有执行。什么时候执行呢?后面再说,但是我们可以大胆猜测post的Runnable任务,一定晚于View的绘制任务(TraversalRunnable)执行的,为什么呢?因为只有执行完View的绘制任务即已经进行了onMeasure(),再执行post的Runnable任务,这样才能在Runnable中获取到View的宽高不为0。
那么是如何保证post的Runnable任务,一定晚于View的绘制任务(TraversalRunnable)执行的呢?
其实这一细节,在performTraversals()可以看出来,但是上面我们贴出来的代码,没有体现这一细节。就是performTraversals()中会执行getRunQueue.executeActions(mAttachInfo.mHandler),该方法会将Runnable(假设是获取View宽高任务)放到主线程的消息队列执行。
由于执行performTraversals()就意味着正在执行绘制任务(TraversalRunnable),而Runnable任务(假设是获取View宽高任务)又是在performTraversals()中才得以执行,所以TraversalRunnable必然是先于Runnable任务(假设是获取View宽高任务)先执行。而且消息队列必须是消费完前一个任务,才能消费下一个任务,因此当执行Runnable任务时,TraversalRunnable已经执行完毕,也就已经完成了测量过程,所以Runnable中得到View的宽高不为0.
参考:
1.https://blog.csdn.net/stven_king/article/details/78775166(ViewRootImpl)
2.https://blog.csdn.net/u011810352/article/details/79380243(ViewRootImpl)
3.https://blog.csdn.net/scnuxisan225/article/details/49815269(onCreat()中执行View.post()能够获取View宽高)