前言
在了解绘制流程,首先我们需要知道什么是Vsync,没有了解的同学先去看看我的这篇文章,看了后必定如雷灌顶。
点击:2分钟带你了解什么是Vsync 回顾下这篇的内容,当程序在屏幕绘制一个画面的时候,他需要经过这样的流程:程序业务代码,经过cpu预算,计算所需要的数据,运算到gpu合成完,发送到FrameBuffer,然后被显示器读取,显示到屏幕。
有了这些概念后,我们开始探入安卓绘制流程。
安卓绘制流程
先说下思路,后面再一步步验证。我们已经知道,每当设备绘制完一帧后就会发出一个Vsync信号,而安卓的绘制流程第一步就是订阅这个信号,当需要屏幕刷新的时候(也就是你调用invalidate或requestLayout),系统就会就加入一个回调,当这个Vsync来到时候,系统就会执行这个回调,这个回调里面会将说有的视图进行业务的布局构建,然后将数据发送到gpu汇总成一个画面数据发送到缓冲区,再给显示器绘制显示出来。我们可以先粗略看下面这个图
了解视图链路
到了这里,我们已经了解大概流程,我们从视图链路层出发,认知整条连的关系,再从代码中认证。
ViewGroup和View的关系这里就不探讨了,相信大家已经清楚。从这个图我们可以发现,所有部件的顶端是一个DecorView,他的mParent和所有的不同,却是一个ViewRootImpl,并且所有子节点都可以间接访问到它。这个东西非常重要,它的作用是触发安排下一次的Vsync,然后执行回调将所有需要绘制的视图进行构建渲染!而且ViewRootImpl是每个窗口唯一,下面即将通过源码来验证这条链的关系。
我们先找到ActivityThread.java, 来到handleResumeActivity方法,这个方法主要是在Activity执行onCreate回调后,再执行onResume之前执行下面代码.这里不讨论其他的,粗略看主要代码即可,不想要看详细实现,不然我们根本看不完,看流程即可。
找到,并点进去
点进去可以发现,它是个接口,是由WindowManager继承的ViewManager的接口,他的实现类是WindowManagerImpl
因此我们去找他的实现方法addView
可见,addView并不是直接实现,由WindowManagerGlobal mGlobal代理实现,我们继续点进去看看。
由此可见,ViewRootImpl 是在Acitivity的onResume 时候创建的
下一步,找到
进去,发现他调用了requestLayout(),上面已经说过,调用这个方法会安排一个回调,下次Vsync信号回来就执行所有需要的绘制画面。
并将ViewRootImpl设置到DecorView,作为DecorView的mParent
由于一个Activity只有一个Window,也就是PhoneWindow,这个PhoneWindow并不是真正意义的视图窗口,他是由DecorView来提供视图,是本窗口所有视图的最底层视图.
在ViewGroup里面,他会调用子view的assignParent,将子view的mParent指向它。我们可以看看当我们添加View时候的方法。
再回顾下上面的图,就很清晰链路上的关系。子节点有了ViewParent就可以形成一条链,需要进行刷新的时候就可以将整条链进行遍历作业,最终获取到ViewRoorImpl去 schedule一个Vsync,等待订阅的Vsync过来,然后进行构建显示到屏幕
了解Vsync来时,执行的过程
我们先快速浏览下如图相关的类,留个初步印象,方便解读
相关类图
1.与视图生命周期相关
2. 与Vysnc响应与执行相关
流程概况
先简单说下总体流程,再通过源码来验证。完成这个个关键的类主要是3个, Choreographer.java,DisplayEventReceiver.java,ViewRootImpl.java. 通过阅读我们已经了解ViewRootImpl.java与视图的关系。**ViewRootImpl.java在整个环节中,主要负责管理视图的布局与绘制业务。**其中,ViewRootImpl与Choreographer各自持有双方的对象,如图
Choreographer的作用主要是管理Vysnc的安排与接收。Choreographer只是起到一个中介的作用,真正实现是他拥有的DisplayEventReceiver这个类。
从源码角度解锁流程
第一步:初始化以及绑定Vsync消息中心
从上面我们已经知道,ViewRootImpl是在Activity添加DecorView时候创建的。而绑定Vsync的订阅消息的初始化流程是从ViewRootImpl构造方法开始,如图
ViewRootImpl
创建ViewRootImpl对象时候,构造方法获取会Choreographer对象,第一次执行时候会创建Choreographer实例。第二次后构造后(其他窗口),从Choreographer单例里面取。如下
Choreographer
Choreographer构造时候,会创建一个FrameHandler,一个FrameDisplayEventReceiver,还有一个CallbackQueue数组。其中索引为CALLBACK_TRAVERSAL的队列,用来保存绘制工作的回调,这个回调会遍历所有的子节点的布局和绘制工作,整个环境后面会详细讲到。
FrameHandler
FrameHandler有3个消息执行处理。分别为MSG_DO_FRAME,MSG_DO_SCHEDULE_VSYNC,MSG_DO_SCHEDULE_CALLBACK
其中MSG_DO_FRAME消息执行doFrame,执行被绑定在每个队列中的回调,也就是执行了Choreographer.CALLBACK_TRAVERSAL的回调,也就是说这里会在UI线程中构建遍历绘制工作。
MSG_DO_SCHEDULE_VSYNC,执行doScheduleVsync,看名字就知道,这个是用来申请Vysnc信号的。
FrameDisplayEventReceiver
这个FrameDisplayEventReceiver是真正意义的拥有申请Vsync和接收Vsync的java类。初始化时候回调用父类的Native方法,初始化Vsync的消息控制中心 NativeDisplayEventReceiver.cpp
可以找到对应的JNI,android_view_DisplayEventReceiver.cpp, 他会创建真正的NativeDisplayEventReceiver对象并初始化
第二步: 当View绘制的时候,申请下一个Vsync进行处理
当我们需要刷新View的绘制时候,我们需要调用invalidate进行重新绘制,然后将父节点所有需要绘制的都设置绘制区域。最后invalidate会顺藤摸瓜,最终获取到ViewRootImpl对象,然后把回调塞到上面的CallbackQueue,然后申请下一个Vsync到来执行回调,遍历所有的ViewGroup和View绘制图像通过GPU发送到缓冲数据区域,被显示器读取然后显示出来。下面是具体步骤,如图
先看看invalidate这个方法,
我们在看看invalidateChild,点进去发现这个是ViewParent的接口方法。
通过As可以找到实现的地方
转移过去后,类是ViewGroup, 我们直接跳过不相关的代码。可以看见, 这个方法一直在调用invalidateChildInParent这个方法,这个方法是由2个类实现,一个是ViewGroup,另外一个是ViewRootImpl。先说下流程,这个类主要是将父节点的所有区域进行设置相关的标志位以及对应的绘制区域,然后设置完后交给最后一个parent也就是ViewRootImpl去进行申请Vsync,因为ViewRootImpl的parent是空的,所以最后会跳出循环,理解了吧。
我们先看看ViewGroup,其他代码略过,都是些设置标志位和设置绘制区域的代码。
看看ViewRootImpl的invalidateChildInParent.
可见,ViewRootImpl最终会调用scheduleTraversals,这个方法主要是去加入Vsync回来执行的回调并且申请Vsync. 下面的重头戏到了这里就开始了。
让我们看看这个有什么,他执行了doTraversal方法,这个方法属于ViewRootImpl,TraversalRunnable是ViewRootImpl的内部类。
我们在将重点切回来到Choreographer的postCallback方法,doTraversal流程后面会讲到。
最终还是回到了这里, 执行doScheduleVsync, 看名字就知道是执行申请Vsync
如果想去深入了解Vsync,可以去framework搜索NativeDisplayEventReceiver.cpp.
如果没有源码的小伙伴,可以到这个网站http://androidxref.com/,直接搜索
android_view_DisplayEventReceiver.cpp。
这个是android_view_DisplayEventReceiver.cpp.对应的JNI ,而NativeDisplayEventReceiver.cpp则是对应的处理类。
上面初始化流程的时候,我们已经知道,java的FrameDisplayEventReceiver.java这边会持有receiverPtr这个指针,
这个指针就是NativeDisplayEventReceiver.cpp的对象指针, 通过调用reinterpret_cast就可以转换成NativeDisplayEventReceiver对象
NativeDisplayEventReceiver.cpp去调用scheduleVsync这个部分,有兴趣的去看源码。c++代码并没有java这边的代码多,梳理出来就可以看出具体流程了。主要实现类有SurfaceFlinger.cpp,DispSync.cpp, DispSyncThread.cpp,HWComposer.cpp。
第三步:执行Vsync回来后的绘制工作
到了这一步了,马上要真相大白了。相信大家通过看这个时序图就能把所有环节连在一起 了。
NativeDisplayEventReceiver的DisplayEventDispatcher中有个handleEvent方法,当接收到Vsync时候会执行到这个方法,进行分发消息。
DisplayEventDispatcher并没有实现dispatchVsync,而是交给子类NativeDisplayEventReceiver去实现,也就是调用了子类的dispatchVsync.
我们再定位到DisplayEventReceiver.java,我们会发下确实有一个dispatchVsync这样的方法,注释写明了是被native调用,其实就是这个NativeDisplayEventReceiver调用。
DisplayEventReceiver是个抽象类,具体实现是FrameDisplayEventReceiver,onVsync主要是调用Run方法再调用Choreographer的doFrame.到了这里开始真正的处理回调的事务了。开始执行布局和绘制的回调
执行CALLBACK_TRAVERSAL的回调,其实就会执行mTraversalRunnable这个接口的run。之前是有介绍过的。mTraversalRunnable会调用ViewRootImpl的doTraversal方法。
doTraversal会调用performTraversals,这个方法主要是执行了3个步骤, 测量>布局>绘制。
是不是突然发现了新大陆,没错,执行View和ViewGroup的生命周期是在这里产生。
第一步:测量
View or ViewGroup
第二步:布局
View or ViewGroup
第三步: 绘制
View.java
好了到了这里,全部串通了!!!如果有帮助到你,请来个关注点赞三连吧。需要flutter 篇的小伙伴,请在评论区留下你的666。我的动力来自于你们的支持