在Window创建过程中了解到:
在WM创建了ViewRootImpl之后,ViewRootImpl执行了requestLayout操作
这个requestLayout就是整个绘制的起点
ViewRootImpl
子View调用requestLayout最终会调用ViewRootImpl.requestLayout进行绘制
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
// 创建ViewRootImpl时的线程
final Thread mThread;
// 是否正在进行布局,即正在执行
boolean mHandlingLayoutInLayoutRequest = false;
// 是否已经请求了布局
boolean mLayoutRequested;
// 是否有待处理的布局请求
public boolean mTraversalScheduled;
// 是否正在进行布局传递 layout()
private boolean mInLayout = false;
// handle消息屏障标识
int mTraversalBarrier;
// VSync垂直信号
final Choreographer mChoreographer;
final View.AttachInfo mAttachInfo;
public ViewRootImpl(Context context, Display display, IWindowSession session,
boolean useSfChoreographer) {
...
mThread = Thread.currentThread();
// 创建mAttachInfo
mAttachInfo = new View.AttachInfo(.., this, mHandler, ..);
}
// 将Window添加到窗口管理器
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
mView = view;
// 屏幕输入事件
mInputChannel = new InputChannel();
...
// 在添加到窗口管理器之前请求一次布局
// 以确保我们从系统接收任何其他事件之前进行重新布局。
requestLayout();
// Window的添加等操作
mWindowSession.addToDisplayAsUser(mWindow, ...., mInputChannel, ...);
...
// 将ViewRootImpl设置为DecorView的parent (继承ViewParent)
view.assignParent(this);
...
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
// 这边检测了当前线程是否与创建ViewRootImpl的线程一致
// 不一致,则抛异常
// 子线程创建ViewRootImpl(在子线程调用WindowManager.addView),则在当前线程就能更新ui?
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
// 利用同步消息屏障 + Vsync信号机制
// 在下一个Vsync信号来之前阻止同步消息的执行
// 在下一个Vsync信号来时执行刷新操作
void scheduleTraversals() {
if (!mTraversalScheduled) {
//此字段保证同时间多次更改只会刷新一次,例如TextView连续两次setText(),也只会走一次绘制流程
mTraversalScheduled = true;
// 发送同步消息屏障,只有处理异步消息能够得到处理
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// mChoreographer内部发送了一个异步消息
// 在下一个Vsync信号来时,会执行异步消息执行
// 在异步消息执行时,执行了mTraversalRunnable
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 执行测量/布局/绘制
performTraversals();
}
}
private void performTraversals() {
// DecorView
final View host = mView;
....
// 第一次调用
if (mFirst) {
// mAttachInfo 初始化
...
host.dispatchAttachedToWindow(mAttachInfo, 0);
}
getRunQueue().executeActions(mAttachInfo.mHandler);
// 当前是否有布局请求的副本
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
// 清除状态
mLayoutRequested = false;
}
boolean windowShouldResize = layoutRequested && ...
if (mFirst || windowShouldResize ...) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// 测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
// 如果有设置权重,比如LinearLayout设置了weight,需要测量两次
if (measureAgain) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
...
}
// 布局
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
performLayout(lp, mWidth, mHeight);
...
}
// 绘制
performDraw();
}
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mInLayout = true;
final View host = mView;
// 执行第一次布局
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
// 布局完成后,检查是否有待处理的请求,有则处理(主要是布局期间调用requestLayout的操作)
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
mHandlingLayoutInLayoutRequest = true;
// 这边主要是校验,请求requestLayout的View是否扔有效
// 有几个条件:mParent不为null,设置了强制刷新标识PFLAG_FORCE_LAYOUT
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, false);
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
view.requestLayout();
}
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
}
mInLayout = false;
}
}
public class View implements ... {
protected ViewParent mParent;
AttachInfo mAttachInfo;
private HandlerActionQueue mRunQueue;
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
...
// 如果调用post时,mAttachInfo为null,会将消息延迟到dispatchAttachedToWindow执行
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
...
}
// 如果ViewRootImpl正在执行布局传递时,再次调用requestLayout()
// 1.请求会延迟到当前布局请求完成后再次执行布局
// 2.或者当前帧绘制完成后,下一次布局请求发起时执行
@CallSuper
public void requestLayout() {
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
ViewRootImpl viewRoot = getViewRootImpl();
// 正在执行布局传递
if (viewRoot != null && viewRoot.isInLayout()) {
// 如果正在执行延迟的布局请求,则当前请求再次延迟到下一次布局请求发起时执行
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
// 设置强制刷新标识,ViewTree重新测量布局(measure、layout)的时候
// 会过滤没有设置这个标识的View
// 相关代码在measure、layout方法中,这边就不贴了
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
// 递归调用mParent的requestLayout,最终调用到ViewRootImpl
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
}
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but"
+ " it already has a parent");
}
}
}
1.requestLayout最终传递到ViewRootImpl,并且传递的时候,给路过的View设置强制刷新标识
2.ViewRootImpl使用同步屏障+Choreographer+异步消息,在下一个VSync信号执行刷新
3.ViewRootImpl会依次递归执行测量、布局、绘制
4.未设置强制刷新标识的View,不会执行测量、布局
5.在layout()递归期间,再次调用requestLayout,不会再次发起请求,会延迟到当前布局请求完成后再次执行布局;或者当前帧绘制完成后,下一次布局请求发起时执行
View.post
View.post为何能拿到View的宽高信息
1.拿到 View 内部的 mAttachInfo,拿到 mAttachInfo.handler,调用post将runnable插入消息队列
2.又下可知,mAttachInfo 在 ViewRootImpl 执行测量时初始化
3.此时,刚插入消息队列的message肯定会在测量完成后才会执行
5.所以,View.post 能拿到View的宽高信息
public class View implements ... {
// 当视图附加到其父窗口时提供给视图的一组信息。
AttachInfo mAttachInfo;
private HandlerActionQueue mRunQueue;
// 由于DecorView是一个ViewGroup,所以会将dispatchAttachedToWindow传递给每一个子View
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
...
// 如果调用post时,mAttachInfo为null,会将消息延迟到dispatchAttachedToWindow执行
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
...
}
// 由上述 ViewRootImpl 可知,
// mAttachInfo 在 ViewRootImpl 执行测量时,调用View.dispatchAttachedToWindow 传进来
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// 如果 mAttachInfo 还未初始化,延迟到dispatchAttachedToWindow执行
getRunQueue().post(action);
return true;
}
}
invalidate
最终调用的是ViewRootImp的scheduleTraversals()
由于mLayoutRequested为false,所以不会执行测量和布局
硬件绘制和软件绘制具有不一样的调用流程
绘制
前面经过View的测量、布局,收集到了绘制需要的信息之后,需要将其绘制到屏幕上,这里需要区分软件绘制和硬件绘制
这里就不细讲了,主要参考:
屏幕刷新机制
关于UI渲染,你需要了解什么
RenderThread:异步渲染动画
网友评论