概述
我们知道,activity显示出页面是在onresum之后,那么他具体到底是怎么添加和绘制的呢
绘制的入口
从前面讲的APP启动流程分析中我们知道,在创建Activiy的流程这一步中,其步骤
1、创建Activity的Context;
2、通过反射创建Activity对象;
3、执行Activity的attach动作,其中会创建应用窗口的PhoneWindow对象并设置WindowManage;
4、执行应用Activity的onCreate生命周期函数,并在setContentView中创建窗口的DecorView对象;
在Activiy Resume的流程这一步中,其步骤:
1、执行应用Activity的onResume生命周期函数;
2、执行WindowManager的addView动作开启视图绘制逻辑;
3、创建Activity的ViewRootImpl对象;
4、执行ViewRootImpl的setView函数开启UI界面绘制动作;
所以其入口跟随Activity的启动而调用
应用UI布局与绘制
应用在执行Activity的Resume流程的最后,会创建ViewRootImpl对象并调用其setView函数,从此并开启了应用界面UI布局与绘制的流程。在开始讲解这个过程之前,我们先来整理一下前面代码中讲到的这些概念,如Activity、PhoneWindow、DecorView、ViewRootImpl、WindowManager它们之间的关系与职责,因为这些核心类基本构成了Android系统的GUI显示系统在应用进程侧的核心架构,其整体架构如下图所示:

Window是一个抽象类,通过控制DecorView提供了一些标准的UI方案,比如背景、标题、虚拟按键等,而PhoneWindow是Window的唯一实现类,在Activity创建后的attach流程中创建,应用启动显示的内容装载到其内部的mDecor(DecorView);
DecorView是整个界面布局View控件树的根节点,通过它可以遍历访问到整个View控件树上的任意节点;
WindowManager是一个接口,继承自ViewManager接口,提供了View的基本操作方法;
WindowManagerImp实现了WindowManager接口,内部通过组合方式持有WindowManagerGlobal,用来操作View;
WindowManagerGlobal是一个全局单例,内部可以通过ViewRootImpl将View添加至窗口中;
ViewRootImpl是所有View的Parent,用来总体管理View的绘制以及与系统WMS窗口管理服务的IPC交互从而实现窗口的开辟;ViewRootImpl是应用进程运转的发动机,可以看到ViewRootImpl内部包含mView(就是DecorView)、mSurface、Choregrapher,mView代表整个控件树,mSurfacce代表画布,应用的UI渲染会直接放到mSurface中,Choregorapher使得应用请求vsync信号,接收信号后开始渲染流程;
我们从ViewRootImpl的setView流程继续结合代码往下看:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
mView = view;
}
...
// 开启绘制硬件加速,初始化RenderThread渲染线程运行环境
enableHardwareAcceleration(attrs);
...
// 1.触发绘制动作
requestLayout();
...
inputChannel = new InputChannel();
...
// 2.Binder调用访问系统窗口管理服务WMS接口,实现addWindow添加注册应用窗口的操作,并传入inputChannel用于接收触控事件
res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mDisplayCutout, inputChannel,
mTempInsets, mTempControls);
...
// 3.创建WindowInputEventReceiver对象,实现应用窗口接收触控事件
mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
Looper.myLooper());
...
// 4.设置DecorView的mParent为ViewRootImpl
view.assignParent(this);
...
}
}
从以上代码可以看出ViewRootImpl的setView内部关键流程如下:
1、requestLayout()通过一系列调用触发界面绘制(measure、layout、draw)动作,下文会详细展开分析;
2、通过Binder调用访问系统窗口管理服务WMS的addWindow接口,实现添加、注册应用窗口的操作,并传入本地创建inputChannel对象用于后续接收系统的触控事件,这一步执行完我们的View就可以显示到屏幕上了。关于WMS的内部实现流程也非常复杂,由于篇幅有限本文就不详细展开分析了。
3、创建WindowInputEventReceiver对象,封装实现应用窗口接收系统触控事件的逻辑;
4、执行view.assignParent(this),设置DecorView的mParent为ViewRootImpl。所以,虽然ViewRootImpl不是一个View,但它是所有View的顶层Parent。
接下来ViewRootImpl的requestLayout动作继续往下看界面绘制的流程代码:
/*frameworks/base/core/java/android/view/ViewRootImpl.java*/
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 检查当前UI绘制操作是否发生在主线程,如果发生在子线程则会抛出异常
checkThread();
mLayoutRequested = true;
// 触发绘制操作
scheduleTraversals();
}
}
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
//...
// 注意此处会往主线程的MessageQueue消息队列中添加同步栏删,因为系统绘制消息属于异步消息,需要更高优先级的处理
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 通过Choreographer往主线程消息队列添加CALLBACK_TRAVERSAL绘制类型的待执行消息,用于触发后续UI线程真正实现绘制动作
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//...
}
}
Choreographer 的引入,主要是配合系统Vsync垂直同步机制(Android“黄油计划”中引入的机制之一,协调APP生成UI数据和SurfaceFlinger合成图像,避免Tearing画面撕裂的现象),给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整,来控制每一帧绘制操作的时机。Choreographer 扮演 Android 渲染链路中承上启下的角色:
1、负责接收和处理 App 的各种更新消息和回调,等到 Vsync 到来的时候统一处理。比如集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作) ,判断卡顿掉帧情况,记录 CallBack 耗时等;
2、负责请求和接收 Vsync 信号。接收 Vsync 事件回调(通过 FrameDisplayEventReceiver.onVsync ),请求 Vsync(FrameDisplayEventReceiver.scheduleVsync) 。
Choreographer在收到CALLBACK_TRAVERSAL类型的绘制任务后,其内部的工作流程如下图所示

ViewRootImpl调用Choreographer的postCallback接口放入待执行的绘制消息后,Choreographer会先向系统申请APP 类型的vsync信号,然后等待系统vsync信号到来后,去回调到ViewRootImpl的doTraversal函数中执行真正的绘制动作(measure、layout、draw)。
接着ViewRootImpl的doTraversal函数的简化代码流程往下看:
/*frameworks/base/core/java/android/view/ViewRootImpl.java*/
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 调用removeSyncBarrier及时移除主线程MessageQueue中的Barrier同步栏删,以避免主线程发生“假死”
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
...
// 执行具体的绘制任务
performTraversals();
...
}
}
private void performTraversals() {
...
// 1.从DecorView根节点出发,遍历整个View控件树,完成整个View控件树的measure测量操作
windowSizeMayChange |= measureHierarchy(...);
...
if (mFirst...) {
// 2.第一次执行traversals绘制任务时,Binder调用访问系统窗口管理服务WMS的relayoutWindow接口,实现WMS计算应用窗口尺寸并向系统surfaceflinger正式申请Surface“画布”操作
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
}
...
// 3.从DecorView根节点出发,遍历整个View控件树,完成整个View控件树的layout测量操作
performLayout(lp, mWidth, mHeight);
...
// 4.从DecorView根节点出发,遍历整个View控件树,完成整个View控件树的draw测量操作
performDraw();
...
}
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
...
// 通过Binder IPC访问系统WMS服务的relayout接口,申请Surface“画布”操作
int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
mTmpFrame, mTmpRect, mTmpRect, mTmpRect, mPendingBackDropFrame,
mPendingDisplayCutout, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
mTempControls, mSurfaceSize, mBlastSurfaceControl);
if (mSurfaceControl.isValid()) {
if (!useBLAST()) {
// 本地Surface对象获取指向远端分配的Surface的引用
mSurface.copyFrom(mSurfaceControl);
} else {
...
}
}
...
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
// 原生标识View树的measure测量过程的trace tag
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
// 从mView指向的View控件树的根节点DecorView出发,遍历访问整个View树,并完成整个布局View树的测量工作
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
private void performDraw() {
...
boolean canUseAsync = draw(fullRedrawNeeded);
...
}
private boolean draw(boolean fullRedrawNeeded) {
...
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
...
// 如果开启并支持硬件绘制加速,则走硬件绘制的流程(从Android 4.+开始,默认情况下都是支持跟开启了硬件加速的)
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
// 否则走drawSoftware软件绘制的流程
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
}
ViewRootImpl中负责的整个应用界面绘制的主要流程
1、从界面View控件树的根节点DecorView出发,递归遍历整个View控件树,完成对整个View控件树的measure测量操作,由于篇幅所限,本文就不展开分析这块的详细流程;
2、界面第一次执行绘制任务时,会通过BinderIPC访问系统窗口管理服务WMS的relayout接口,实现窗口尺寸的计算并向系统申请用于本地绘制渲染的Surface“画布”的操作(具体由SurfaceFlinger负责创建应用界面对应的BufferQueueLayer对象,并通过内存共享的方式通过Binder将地址引用透过WMS回传给应用进程这边),由于篇幅所限,本文就不展开分析这块的详细流程;
3、从界面View控件树的根节点DecorView出发,递归遍历整个View控件树,完成对整个View控件树的layout测量操作;
4、从界面View控件树的根节点DecorView出发,递归遍历整个View控件树,完成对整个View控件树的draw测量操作,如果开启并支持硬件绘制加速(从Android 4.X开始谷歌已经默认开启硬件加速),则走GPU硬件绘制的流程,否则走CPU软件绘制的流程;
绘制流程如下:

DecorView是所有视图的根视图,也就是最顶层布局,它是一个ViewGroup。每个Activity的View绘制流程都是先从DecorView的绘制开始,然后依次递归绘制它的子View和子ViewGroup,子ViewGroup再递归绘制它包含的子View,直到所有的View都绘制完成。
这里说说View绘制涉及到的类和方法
从上面源码分析中可以看出,View绘制的入口是ViewRootImpl类的performTraversals()方法。在performTraversals()方法中有3个重要的步骤方法:
(1)performMeasure():该方法主要用于测量所有View和ViewGroup的大小(宽和高)
(2)performLayout():该方法用于确定所有View和ViewGroup在父布局的位置
(3)performDraw():该方法进行具体的绘制操作
performMeasure测量
1.测量涉及的方法
performMeasure()主要就是用来测量View和ViewGroup的宽和高,涉及到的方法有:
(1)View:Measure() —>onMeasure() —>setMeasureDimension() —>getDefaultSize()
View的测量流程:View的测量是从Measure()方法开始的,在Measure()里面没有具体的操作,而是直接调用onMeasure()方法。在onMeasure()方法里调用了两个getDefaultSize()方法来分别测量View的宽和高的值。最后调用setMeasureDimension()方法将View的宽和高测量值保存下来。
(2)ViewGroup:Measure() —>onMeasure() —>measureChildren() —>measureChild()/measureChildWithMargins() —>Measure() —>onMeasure()—>setMeasureDimension() —>getDefaultSize()。
ViewGroup除了需要测量自己的宽高大小,还需要测量它包含的子View或子ViewGroup的大小,所以它涉及到的方法除了测量View需要的方法之外,还有measureChildren()方法,在measureChildren()方法内通过循环调用measureChild()方法,在measureChild方法中又调用子View或子ViewGroup的Measure()方法。
总结:在performMeasure()方法中首先调用的是顶级视图DecorView的Measure()方法,在Measure()方法中调用onMeasure()方法进行DecorView的宽高大小测量。由于DecorView是一个ViewGroup,所以在onMeasure()方法内又调用了measureChildren()方法来测量它的子View和子ViewGroup宽高大小,这样一步步往下递归遍历,最后测量出所有的View和ViewGroup的大小。
2.MeasureSpec
上面描述了测量View和ViewGroup大小的大致流程,但是在具体测量的时候还涉及到了一个MeasureSpec类,这个类是View类的内部类,主要内容是一个int型变量,int是32位的,其中高2位表示模式(Mode),低30位表示大小(Size)。每一个View和ViewGroup都有自己的MeasureSpec,具体来说是有2个MeasureSpec:widthMeasureSpec和heightMeasureSpec。这两个MeasureSpec从measure()方法开始作为参数,一直传递到getDefaultSize()方法,最后在getDefaultSize()方法里面参考这2个MeasureSpec来确定具体大小。
MeasureSpec高2位表示的模式有3种:UNSPECIFIED(不指定的)、EXACTLY(确定的)、AT_MOST(至多的)。
子View的大小通常是受限于父布局的,举个例子,假如某个子View大小设为match_parent,那么该子View的大小就依赖于父布局的大小,父布局如果大小为200200dp,子View就为200200dp。
子View的MeasureSpec是由自身的LayoutParams和父布局的MeasureSpec共同确定的。在上面例子中父布局大小为200*200dp,于是父布局的widthMeasureSpec和widthMeasureSpec模式Mode为EXACTLY(确定的),大小Size为200。又由于子View的LayoutParams指定为match_parent,于是子View的widthMeasureSpec和widthMeasureSpec模式Mode也为EXACTLY(确定的),大小Size也为200。
上面子View的MeasureSpec确定过程发生在measureChild()方法中,确定好子View的MeasureSpec后就将其作为参数传入到measure()方法,最后在getDefaultSize()方法里根据子View的两个MeasureSpec来确定子View的宽高。
父MeasureSpec和子MeasureSpec的关系图如下,上面例子对应下图的第二行第一列。
| 父/子 | EXACTLY | AT_MOST | UNSPECIFIED |
|---|---|---|---|
| 具体尺寸 | EXACTLY 、childSize | EXACTLY 、childSize | EXACTLY 、childSize |
| match_parent | EXACTLY 、parentSize | AT_MOST、parentSize | UNSPECIFIED、SDK<23?0:parentSize |
| wrap_content | AT_MOST、parentSize | AT_MOST、parentSize | UNSPECIFIED、SDK<23?0:parentSize |
上图总结:
1.当子View的LayoutParams指定为具体的尺寸如200*200dp,那么无论父布局的MeasureSpec模式和大小是什么,子View的模式都为EXACTLY(确定的),大小就是LayoutParams指定的具体值200,而不依赖于父布局MeasureSpec指定的大小。
2.当子View的LayoutParams指定为match_parent时,子View的MeasureSpec就和父布局的MeasureSpec相同。
2.当子View的LayoutParams指定为wrap_content时,分两种情况:当父布局MeasureSpec模式为UNSPECIFIED(不指定的),子View的MeasureSpec模式也是UNSPECIFIED(不指定的);当父布局MeasureSpec模式为EXACTLY(确定的)和AT_MOST(至多的),子View的MeasureSpec模式都是AT_MOST(至多的)。无论Mode是哪一种,子View的MeasureSpec的大小(Size)都是父布局的MeasureSpec中指定的大小,表示子View大小不应该超过父布局大小。
只要知道了父布局的MeasureSpec和子View的LayoutParams,那么子View的MeasureSpec也就确定了,从而能够进一步确定子View的大小。
ps:由于DecorView是顶级布局,所以它的MeasureSpec是通过窗口大小和自身的LayoutParams确定的。
performLayout布局
在performTraversals()方法中,首先调用performMeasure()方法来测量所有View和ViewGroup的宽高大小。之后就是调用performLayout()方法确定每个View和ViewGroup在其父布局中的位置。
布局涉及的方法
(1)View:layout()—>setFrame()
View布局流程:View通过调用layout()方法来确定它在父布局的位置,具体是在layout()方法内调用setFrame()方法,将该View的上下左右四个点位置作为参数传入,从而确定了View在父布局的位置。
ViewGroup:layout()—>setFrame()—>onLayout()
ViewGroup布局流程:ViewGroup同样先调用layout()方法,通过layout()方法内的setFrame()方法设置自己相对于父布局的位置。但是由于ViewGroup包含子View或子ViewGroup,所以需要重写onLayout()方法,在onLayout()方法中遍历自己的子View或子ViewGroup,分别调用它们的layout()方法来完成子View或子ViewGroup的布局。
总结:在performLayout()方法中首先调用的是顶级布局DecorView的Layout()方法,由于DecorView是一个ViewGroup,所以它调用的是父类View的layout()方法(原因在下面PS中介绍),并且在layout()方法中确定了自己的位置之后再重写onLayout()方法来遍历子View和子ViewGroup,通过调用View和ViewGroup的layout()方法来确定它们在父布局DecorView中的位置,这样一步步往下递归遍历,直到所有的View都确定了在父布局的位置为止。
PS:
1.ViewGroup的layout()方法是final修饰的,不能被重写;View的layout()方法是可以被重写的。
2.在ViewGroup中layout()方法中最终调用的是View的layout()方法。
2.View的onLayout()方法是一个空方法,这是因为View没有子View或子ViewGroup,不需要onLayout()方法。而ViewGroup的onLayout()方法是一个抽象方法,即ViewGroup的子类必须重写这个方法,因为ViewGroup包含子View或子ViewGroup,需要在onLayout()方法中遍历View或子ViewGroup并调用它们的layout()方法,从而确定它们在父布局中的位置。
performDraw绘制
在performTraversals()方法中,首先调用performMeasure()方法来测量所有View和ViewGroup的宽高大小。之后就是调用performLayout()方法确定每个View和ViewGroup在其父布局中的位置。最后就是调用performDraw进行具体的绘制。
绘制涉及的方法:
View:draw()—>drawBackground()—>onDraw()—>onDrawScrollBars()。
View绘制流程:首先调用的是draw()方法,在该方法内依次调用drawBackground()方法来绘制View背景,调用onDraw()方法绘制View内容,最后调用onDrawScrollBars()方法绘制装饰。
ViewGroup:draw()—>drawBackground()—>onDraw()—>dispatchDraw()—>onDrawScrollBars()。
ViewGroup绘制流程:在ViewGroup的draw()方法中,在调用drawBackground()方法来绘制View背景、调用onDraw()方法绘制View内容之后还需要调用dispatchDraw()方法来绘制子View和子ViewGroup,具体是在dispatchDraw()方法内循环遍历子View和子ViewGroup,并且调用它们的draw()方法进行绘制。
总结:在performDraw()方法中首先调用顶级布局DecorView的draw()方法绘制自身,由于DecorView是一个ViewGroup,所以在绘制完自己之后还要调用dispatchDraw()方法来遍历绘制它的子View和子ViewGroup。这样一步步往下递归遍历绘制,直到所有View和ViewGroup都绘制完成为止。
PS:在View类中的onDraw()方法为空方法,具体的绘制逻辑需要在子类中的onDraw()方法中实现。
渲染
RenderThread渲染
在ViewRootImpl中完成了对界面的measure、layout和draw等绘制流程后,用户依然还是看不到屏幕上显示的应用界面内容,因为整个Android系统的显示流程除了前面讲到的UI线程的绘制外,界面还需要经过RenderThread线程的渲染处理,渲染完成后,还需要通过Binder调用“上帧”交给surfaceflinger进程中进行合成后送显才能最终显示到屏幕上。本小节中,我们将接上一节中ViewRootImpl中最后draw的流程继续往下分析开启硬件加速情况下,RenderThread渲染线程的工作流程。由于目前Android 4.X之后系统默认界面是开启硬件加速的,所以本文我们重点分析硬件加速条件下的界面渲染流程,我们先分析一下简化的代码流程:
/*frameworks/base/core/java/android/view/ViewRootImpl.java*/
private boolean draw(boolean fullRedrawNeeded) {
...
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
...
// 硬件加速条件下的界面渲染流程
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
...
}
}
/*frameworks/base/core/java/android/view/ThreadedRenderer.java*/
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
...
// 1.从DecorView根节点出发,递归遍历View控件树,记录每个View节点的绘制操作命令,完成绘制操作命令树的构建
updateRootDisplayList(view, callbacks);
...
// 2.JNI调用同步Java层构建的绘制命令树到Native层的RenderThread渲染线程,并唤醒渲染线程利用OpenGL执行渲染任务;
int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
...
}
从上面的代码可以看出,硬件加速绘制主要包括两个阶段:
1、从DecorView根节点出发,递归遍历View控件树,记录每个View节点的drawOp绘制操作命令,完成绘制操作命令树的构建;
2、JNI调用同步Java层构建的绘制命令树到Native层的RenderThread渲染线程,并唤醒渲染线程利用OpenGL执行渲染任务;
构建绘制命令树
我们先来看看第一阶段构建绘制命令树的代码简化流程:
/*frameworks/base/core/java/android/view/ThreadedRenderer.java*/
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
// 原生标记构建View绘制操作命令树过程的systrace tag
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
// 递归子View的updateDisplayListIfDirty实现构建DisplayListOp
updateViewTreeDisplayList(view);
...
if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
// 获取根View的SkiaRecordingCanvas
RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
try {
...
// 利用canvas缓存DisplayListOp绘制命令
canvas.drawRenderNode(view.updateDisplayListIfDirty());
...
} finally {
// 将所有DisplayListOp绘制命令填充到RootRenderNode中
mRootNode.endRecording();
}
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
private void updateViewTreeDisplayList(View view) {
...
// 从DecorView根节点出发,开始递归调用每个View树节点的updateDisplayListIfDirty函数
view.updateDisplayListIfDirty();
...
}
/*frameworks/base/core/java/android/view/View.java*/
public RenderNode updateDisplayListIfDirty() {
...
// 1.利用`View`对象构造时创建的`RenderNode`获取一个`SkiaRecordingCanvas`“画布”;
final RecordingCanvas canvas = renderNode.beginRecording(width, height);
try {
...
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
// 如果仅仅是ViewGroup,并且自身不用绘制,直接递归子View
dispatchDraw(canvas);
...
} else {
// 2.利用SkiaRecordingCanvas,在每个子View控件的onDraw绘制函数中调用drawLine、drawRect等绘制操作时,创建对应的DisplayListOp绘制命令,并缓存记录到其内部的SkiaDisplayList持有的DisplayListData中;
draw(canvas);
}
} finally {
// 3.将包含有`DisplayListOp`绘制命令缓存的`SkiaDisplayList`对象设置填充到`RenderNode`中;
renderNode.endRecording();
...
}
...
}
public void draw(Canvas canvas) {
...
// draw the content(View自己实现的onDraw绘制,由应用开发者自己实现)
onDraw(canvas);
...
// draw the children
dispatchDraw(canvas);
...
}
/*frameworks/base/graphics/java/android/graphics/RenderNode.java*/
public void endRecording() {
...
// 从SkiaRecordingCanvas中获取SkiaDisplayList对象
long displayList = canvas.finishRecording();
// 将SkiaDisplayList对象填充到RenderNode中
nSetDisplayList(mNativeRenderNode, displayList);
canvas.recycle();
}
从以上代码可以看出,构建绘制命令树的过程是从View控件树的根节点DecorView触发,递归调用每个子View节点的updateDisplayListIfDirty函数,最终完成绘制树的创建,简述流程如下:
利用View对象构造时创建的RenderNode获取一个SkiaRecordingCanvas“画布”;
利用SkiaRecordingCanvas,在每个子View控件的onDraw绘制函数中调用drawLine、drawRect等绘制操作时,创建对应的DisplayListOp绘制命令,并缓存记录到其内部的SkiaDisplayList持有的DisplayListData中;
将包含有DisplayListOp绘制命令缓存的SkiaDisplayList对象设置填充到RenderNode中;
最后将根View的缓存DisplayListOp设置到RootRenderNode中,完成构建。
以上整个构建绘制命令树的过程可以用如下流程图表示:

执行渲染绘制任务
经过上一小节中的分析,应用在UI线程中从根节点DecorView出发,递归遍历每个子View节点,搜集其drawXXX绘制动作并转换成DisplayListOp命令,将其记录到DisplayListData并填充到RenderNode中,最终完成整个View绘制命令树的构建。从此UI线程的绘制任务就完成了。下一步UI线程将唤醒RenderThread渲染线程,触发其利用OpenGL执行界面的渲染任务,本小节中我们将重点分析这个流程。我们还是先看看这块代码的简化流程:
/*frameworks/base/graphics/java/android/graphics/HardwareRenderer.java*/
public int syncAndDrawFrame(@NonNull FrameInfo frameInfo) {
// JNI调用native层的相关函数
return nSyncAndDrawFrame(mNativeProxy, frameInfo.frameInfo, frameInfo.frameInfo.length);
}
/*frameworks/base/libs/hwui/jni/android_graphics_HardwareRenderer.cpp*/
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
...
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo());
return proxy->syncAndDrawFrame();
}
/*frameworks/base/libs/hwui/renderthread/RenderProxy.cpp*/
int RenderProxy::syncAndDrawFrame() {
// 唤醒RenderThread渲染线程,执行DrawFrame绘制任务
return mDrawFrameTask.drawFrame();
}
/*frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp*/
int DrawFrameTask::drawFrame() {
...
postAndWait();
...
}
void DrawFrameTask::postAndWait() {
AutoMutex _lock(mLock);
// 向RenderThread渲染线程的MessageQueue消息队列放入一个待执行任务,以将其唤醒执行run函数
mRenderThread->queue().post([this]() { run(); });
// UI线程暂时进入wait等待状态
mSignal.wait(mLock);
}
void DrawFrameTask::run() {
// 原生标识一帧渲染绘制任务的systrace tag
ATRACE_NAME("DrawFrame");
...
{
TreeInfo info(TreeInfo::MODE_FULL, *mContext);
//1.将UI线程构建的DisplayListOp绘制命令树同步到RenderThread渲染线程
canUnblockUiThread = syncFrameState(info);
...
}
...
// 同步完成后则可以唤醒UI线程
if (canUnblockUiThread) {
unblockUiThread();
}
...
if (CC_LIKELY(canDrawThisFrame)) {
// 2.执行draw渲染绘制动作
context->draw();
} else {
...
}
...
}
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
ATRACE_CALL();
...
// 调用CanvasContext的prepareTree函数实现绘制命令树同步的流程
mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode);
...
}
/*frameworks/base/libs/hwui/renderthread/CanvasContext.cpp*/
void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued,
RenderNode* target) {
...
for (const sp<RenderNode>& node : mRenderNodes) {
...
// 递归调用各个子View对应的RenderNode执行prepareTree动作
node->prepareTree(info);
...
}
...
}
/*frameworks/base/libs/hwui/RenderNode.cpp*/
void RenderNode::prepareTree(TreeInfo& info) {
ATRACE_CALL();
...
prepareTreeImpl(observer, info, false);
...
}
void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
...
if (info.mode == TreeInfo::MODE_FULL) {
// 同步绘制命令树
pushStagingDisplayListChanges(observer, info);
}
if (mDisplayList) {
// 遍历调用各个子View对应的RenderNode的prepareTreeImpl
bool isDirty = mDisplayList->prepareListAndChildren(
observer, info, childFunctorsNeedLayer,
[](RenderNode* child, TreeObserver& observer, TreeInfo& info,
bool functorsNeedLayer) {
child->prepareTreeImpl(observer, info, functorsNeedLayer);
});
...
}
...
}
void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) {
...
syncDisplayList(observer, &info);
...
}
void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) {
...
// 完成赋值同步DisplayList对象
mDisplayList = mStagingDisplayList;
mStagingDisplayList = nullptr;
...
}
void CanvasContext::draw() {
...
// 1.调用OpenGL库使用GPU,按照构建好的绘制命令完成界面的渲染
bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes,
&(profiler()));
...
// 2.将前面已经绘制渲染好的图形缓冲区Binder上帧给SurfaceFlinger合成和显示
bool didSwap =
mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo, &requireSwap);
...
}
从以上代码可以看出:UI线程利用RenderProxy向RenderThread线程发送一个DrawFrameTask任务请求,RenderThread被唤醒,开始渲染,大致流程如下:
syncFrameState中遍历View树上每一个RenderNode,执行prepareTreeImpl函数,实现同步绘制命令树的操作;
调用OpenGL库API使用GPU,按照构建好的绘制命令完成界面的渲染(具体过程,由于本文篇幅所限,暂不展开分析);
将前面已经绘制渲染好的图形缓冲区Binder上帧给SurfaceFlinger合成和显示;
整个过程可以用如下流程图表示:

SurfaceFlinger合成显示
SurfaceFlinger合成显示部分完全属于Android系统GUI中图形显示的内容,逻辑结构也比较复杂,但不属于本文介绍内容的重点。所以本小节中只是总体上介绍一下其工作原理与思想,不再详细分析源码,感兴趣的读者可以关注笔者后续的文章再来详细分析讲解。简单的说SurfaceFlinger作为系统中独立运行的一个Native进程,借用Android官网的描述,其职责就是负责接受来自多个来源的数据缓冲区,对它们进行合成,然后发送到显示设备。如下图所示:

从上图可以看出,其实SurfaceFlinger在Android系统的整个图形显示系统中是起到一个承上启下的作用:
对上:通过Surface与不同的应用进程建立联系,接收它们写入Surface中的绘制缓冲数据,对它们进行统一合成。
对下:通过屏幕的后缓存区与屏幕建立联系,发送合成好的数据到屏幕显示设备。
图形的传递是通过Buffer作为载体,Surface是对Buffer的进一步封装,也就是说Surface内部具有多个Buffer供上层使用,如何管理这些Buffer呢?答案就是BufferQueue ,下面我们来看看BufferQueue的工作原理:
BufferQueue机制
借用一张经典的图来描述BufferQueue的工作原理:

BufferQueue是一个典型的生产者-消费者模型中的数据结构。在Android应用的渲染流程中,应用扮演的就是“生产者”的角色,而SurfaceFlinger扮演的则是“消费者”的角色,其配合工作的流程如下:
应用进程中在开始界面的绘制渲染之前,需要通过Binder调用dequeueBuffer接口从SurfaceFlinger进程中管理的BufferQueue 中申请一张处于free状态的可用Buffer,如果此时没有可用Buffer则阻塞等待;
应用进程中拿到这张可用的Buffer之后,选择使用CPU软件绘制渲染或GPU硬件加速绘制渲染,渲染完成后再通过Binder调用queueBuffer接口将缓存数据返回给应用进程对应的BufferQueue(如果是 GPU 渲染的话,这里还有个 GPU处理的过程,所以这个 Buffer 不会马上可用,需要等 GPU 渲染完成的Fence信号),并申请sf类型的Vsync以便唤醒“消费者”SurfaceFlinger进行消费;
SurfaceFlinger 在收到 Vsync 信号之后,开始准备合成,使用 acquireBuffer获取应用对应的 BufferQueue 中的 Buffer 并进行合成操作;
合成结束后,SurfaceFlinger 将通过调用 releaseBuffer将 Buffer 置为可用的free状态,返回到应用对应的 BufferQueue中。
Vsync同步机制
Vysnc垂直同步是Android在“黄油计划”中引入的一个重要机制,本质上是为了协调BufferQueue的应用生产者生成UI数据动作和SurfaceFlinger消费者的合成消费动作,避免出现画面撕裂的Tearing现象。Vysnc信号分为两种类型:
app类型的Vsync:app类型的Vysnc信号由上层应用中的Choreographer根据绘制需求进行注册和接收,用于控制应用UI绘制上帧的生产节奏。根据第7小结中的分析:应用在UI线程中调用invalidate刷新界面绘制时,需要先透过Choreographer向系统申请注册app类型的Vsync信号,待Vsync信号到来后,才能往主线程的消息队列放入待绘制任务进行真正UI的绘制动作;
sf类型的Vsync:sf类型的Vsync是用于控制SurfaceFlinger的合成消费节奏。应用完成界面的绘制渲染后,通过Binder调用queueBuffer接口将缓存数据返还给应用对应的BufferQueue时,会申请sf类型的Vsync,待SurfaceFlinger 在其UI线程中收到 Vsync 信号之后,便开始进行界面的合成操作。
Vsync信号的生成是参考屏幕硬件的刷新周期的,其架构如下图所示:

总结
本文结合源码完整的分析了应用Activity界面第一帧画面显示的完整流程,这其中涉及了App应用、system_server框架、Art虚拟机、surfaceflinger等一系列Android系统核心模块的相互配合,有很多的细节也由于篇幅所限无法完全展开分析,感兴趣的读者可以结合AOSP源码继续深入分析。