持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第28天,点击查看活动详情
接下来会出三篇文章详细介绍,View视图如何经过一遍遍的流程渲染到界面上的,从
Activity的onResume(),经过Choreographer处理,经过FrameDisplayEventReceiver处理,最终触发View的measure、layout、draw三大流程,将视图绘制到界面上。
历史文章
从onResume()生命周期说起
总说周知,视图渲染到界面上的时机就是从onResume()生命周期开始的,接下来我们细细分析:
Activity的onResume生命周期的分发是来自于ActivityThread的方法handleResumeActivity(),这个方法中就除了分发onResume()生命周期外,还帮助我们将视图渲染到界面上.
1. ActivityThread.handleResumeActivity()
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
boolean isForward, String reason) {
if (!performResumeActivity(r, finalStateRequest, reason)) {
return;
}
ViewManager wm = a.getWindowManager();
wm.addView(decor, l);
}
这段代码精简了很多 ,一开始就是分发resume周期 ,接下来的addView()方法就要开始真正的绝活了:渲染视图到界面。
先说下这个ViewManager是一个接口,包含了下面三个方法
这三个方法大家应该很熟悉,依次分别代表着界面中添加View、更新View、移除View的三个最基本的功能,这里的ViewManager其最终的实现类为WindowManagerImpl,其父接口WindowManager实现了ViewManager。
所以addView()这个方法的具体实现就得看下WindowManagerImpl这个类了。
2. WindowManagerImpl.addView()
可以看到啥也没有,实现给我们隐藏了,这里直接进行解密,最终交给了WindowManagerGlobal的方法addView()进行处理:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
ViewRootImpl root;
synchronized (mLock) {
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
throw e;
}
}
}
这个上面的代码也是经过精简后的处理,下面我们一步步进行分析:
-
创建
ViewRootImpl对象 ,这个对象大家应该非常熟悉,当我们在非UI线程更新UI时经常报的那个错误就来自于这个对象的方法checkThread()检测。这个对象是连接
DecorView和Window窗口(不要被窗口所迷惑,其实其唯一实现类PhoneWindow就是提供了真正窗口的一些操作,比如转发触摸事件、设置背景等等)的桥梁。 -
将视图、ViewRootImpl、视图的Param保存到
mViews、mRoots、mParams三大集合进行管理; -
通过
ViewRootImpl的方法setView()开始将View添加到Window窗口中,开始和渲染核心类Choreographer的交互。
我们继续看下ViewRootImpl方法setView()。
3. ViewRootImpl.setView()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
requestLayout();
}
这个方法中的代码非常长,目前我们只需要关注上面的方法requestLayout()即可。这里顺便说下,这个setView()方法还有其他非常重要的逻辑,比如注册接受底层传来的触摸事件、构造触摸事件分发对象责任链等等,以后会出其他的文章专门讲解。
4. ViewRootImpl.requestLayout()
我们继续看下方法requestLayout():
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
-
checkThread()上面有提到,校验当前线程是否合法; -
将
mLayoutRequested置为true标识开始渲染开始,标识之后视图需要测量、布局等等操作; -
scheduleTraversals()开始于Choreographer打交道;
5. ViewRootImpl.scheduleTraversals()
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
这个方法主要的处理逻辑如下:
-
mTraversalScheduled置为true,主要是在本次渲染没结束前来接之后的渲染请求; -
postSyncBarrier()添加异步屏障消息,方便优先处理异步消息,接下来的渲染就会包装成一个异步消息分发执行; -
Choreographer.postCallback()注册渲染回调,在底层脉冲信号收到之时,触发该回调执行,真正开始View视图的三个流程measure、layout、draw。
总结
本篇文章主要分析了ViewRootImpl这个类在视图渲染中扮演了什么角色:
-
沟通
DecorView和Window的桥梁,使得两者之间建立联系,即将View绘制的区域映射到WMS中,通过匿名共享内存的方式使得绘制区域在二者之间共享; -
添加屏障消息,注册渲染回调,同时触发
Choreographer注册接受下一次底层的垂直脉冲,以便于触发之前注册的渲染回调。
其中,与Choreographer打交道的讲解会放到下一篇文章中,在这一篇文章中,你会真正了解到Choreographer如何注册渲染回调、如何注册脉冲信号监听、如何触发界面真正的渲染流程的等等,请敬请期待。