当前位置:网站首页>View 工作流程
View 工作流程
2022-07-06 04:15:00 【jthou20121212】
提示:本文基于 Android API 31
ViewRootImpl
任何控件的展示都是通过 WindowManager.addView 来实现的并且 WindowManager 是一个接口真正的实现是 WindowManagerImpl 它又直接调用了 WindowManagerGlobal 的 addView 方法在这个方法里创建了 ViewRootImpl 它是所有控件的抽象父控件,它没有不是继承自 View 但是实现了 ViewParent 接口是 DecorView 的 parent,在 Activity 中通过 setContentView 方法传入的布局就是设置给 DecorView 的一个 id 为 android.R.id.content 的子控件的
// android.view.WindowManagerGlobal#addView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
// 代码省略 ..
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// 代码省略 ..
// 创建 ViewRootImpl 对象
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
在 ViewRootImpl 的 setView 方法里会调用 requestLayout() 方法触发测量、布局、绘制工作
// android.view.ViewRootImpl#requestLayout
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
// 标记位置为 true
mLayoutRequested = true;
// 调度
scheduleTraversals();
}
}
// android.view.ViewRootImpl#scheduleTraversals
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 发送一个同步屏障消息优先处理异步消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 发送一个 mTraversalRunnable
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
mTraversalRunnable 是 TraversalRunnable 对象在下一次 Vsync 信号来的时候会调用其 run 方法
// android.view.ViewRootImpl.TraversalRunnable
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
// android.view.ViewRootImpl#doTraversal
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除同步屏障消息
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 真正的开始 View 的工作流程
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
// android.view.ViewRootImpl#performTraversals
private void performTraversals() {
// 代码省略 ..
performMeasure
// 代码省略 ..
performLayout
// 代码省略 ..
performDraw
// 代码省略 ..
}
performTraversals 方法中会一次调用测量、布局、绘制的方法,依次看一下
测量
// mWidth mHeight 是屏幕宽高
// lp 是 android.view.WindowManager.LayoutParams
// lp.width lp.height 默认是 MATCH_PARENT
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
DecorView 因为是顶层 View 没有实际父级只有抽象父级 ViewRootImpl 所以它的测量过程与普通控件有一些区别,其他控件的 MeasureSpec 都是通过父级的 MeasureSpec 和自己的 ViewGroup.LayoutParams 得到的而 DecorView 的 MeasureSpec 是通过屏幕的宽高和 WindowManager.LayoutParams 得到的,下面先来介绍一下 MeasureSpec
MeasureSpec 代表一个 32 位 int 值,高 2 位代表 SpecMode,低 30 位代表 SpecSize,SpecMode 是指测量模式,而 SpecSize 是指在某种测量模式下的规格大小。
SpecMode 有三类,每一类都表示特殊的含义,如下所示。
UNSPECIFIED:父容器不对 View 有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态。
EXACTLY:父容器已经检测出 View 所需要的精确大小,这个时候 View 的最终大小就是 SpecSize 所指定的值。它对应于 LayoutParams 中的 match_parent 和具体的数值这两种模式。
AT_MOST:父容器指定了一个可用大小即 SpecSize,View 的大小不能大于这个值,具体是什么值要看不同的 View 的具体实现。它对应于 LayoutParams 中的 wrap_content
《Android 开发艺术探索》
// 确定 DecorView 的 MeasureSpec 再去测量子控件
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
// 递归测量所有控件
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
// android.view.View#measure
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 代码省略 ..
// 如果设置了 PFLAG_FORCE_LAYOUT 标记表示需要强制布局
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// 如果大小改变则需要重新测量
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
// 代码省略 ..
onMeasure(widthMeasureSpec, heightMeasureSpec);
// 代码省略 ..
// 设置 PFLAG_LAYOUT_REQUIRED 标记下面会再提到
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
// 代码省略 ..
}
在view.measure()的方法里,仅当给与的MeasureSpec发生变化时,或要求强制重新布局时,才会进行测量。
强制重新布局 : 控件树中的一个子控件内容发生变化时,需要重新测量和布局的情况,在这种情况下,这个子控件的父控件(以及父控件的父控件)所提供的MeasureSpec必定与上次测量时的值相同,因而导致从ViewRootImpl到这个控件的路径上,父控件的measure()方法无法得到执行,进而导致子控件无法重新测量其布局和尺寸。
解决途径 : 因此,当子控件因内容发生变化时,从子控件到父控件回溯到ViewRootImpl,并依次调用父控件的requestLayout()方法。这个方法会在mPrivateFlags中加入标记PFLAG_FORCE_LAYOUT,从而使得这些父控件的measure()方法得以顺利执行,进而这个子控件有机会进行重新布局与测量。这便是强制重新布局的意义所在。
measure 是 View 的 final 方法不可被子类重写,在这个方法里会调用 onMeasure 传入控件的 MeasureSpec 完成子控件的测量,ViewGroup 并没有重写 onMeasure 方法因为不同的 ViewGroup 的子类的测量规则是不一样的所以需要子类自己去实现,因为 DecorView 继承自 FrameLayout 所以看一下 FrameLayout 的 onMeasure 方法
// android.widget.FrameLayout#onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
// 如果自己宽高之一测量模式不是 EXACTLY 模式
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 遍历所有子控件逐一测量
// 如果子控件是 ViewGroup 则在子控件中重复此过程
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 记录最大宽高用于最后 FrameLayout 测量自己
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
// 如果子控件宽高之一是 MATCH_PARENT 则记录下来
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// 省略一些最大值的检查 ..
// 设置自己的大小
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
count = mMatchParentChildren.size();
// 如果 FrameLayout 自己宽高之一是 wrap_content 并且有超过一个子控件宽高之一是 MATCH_PARENT 则再测量一次
// 因为 MATCH_PARENT 的子控件需要跟 FrameLayout 一样大但是 FrameLayout 只有测量了所有的控件拿到最大的控件的大小后才能知道自己的大小
// 所以 FrameLayout 在知道自己的大小后再重新测量一次子控件
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
// 高的逻辑与宽一致
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
FrameLayout 的 onMeasure 方法大致就是逐一测量所有的子控件然后使用最大的子控件的宽高设置自己的大小,如果有特殊情况则再测量一次子控件,看一下测量子控件的方法
// android.view.ViewGroup#measureChildWithMargins
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
measureChildWithMargins s是 ViewGroup 的方法,处理了间距调用 getChildMeasureSpec 方法,在 FrameLayout 需要二次测量的时候也调用了这个方法
// android.view.ViewGroup#getChildMeasureSpec
// 参数 spec 是父控件的 MeasureSpec
// 参数 padding 是间距,表示父控件的 padding + 子控件自己的 margin
// 参数 childDimension 是子控件的 LayoutParams 的大小即在 xml 文件中声明的宽高
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 父控件的测量模式
int specMode = MeasureSpec.getMode(spec);
// 父控件的测量大小
int specSize = MeasureSpec.getSize(spec);
// 父控件的可用大小(测量大小 - 间距)
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// 父控件是精确模式
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
// 如果子控件宽高是精确值 xxdp 则直接使用这个值
resultSize = childDimension;
// 子控件的测量模式也是精确模式
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 如果子控件宽高是 MATCH_PARENT 则使用父控件的大小充满父控件
resultSize = size;
// 子控件的测量模式也是精确模式
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 子控件宽高是 WRAP_CONTENT 填充模式
resultSize = size;
// 子控件的测量模式是 '最大' 模式表示最大不超出父控件大小 size
resultMode = MeasureSpec.AT_MOST;
}
break;
// 父控件是 '最大' 模式
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// 如果子控件宽高是精确值 xxdp 则直接使用这个值
resultSize = childDimension;
// 子控件的测量模式也是精确模式
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 如果子控件宽高是 MATCH_PARENT 则使用父控件的大小充满父控件
resultSize = size;
// 通常这个时候父控件也还没有确定自己的大小所以子控件也是 '最大' 模式
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 子控件宽高是 WRAP_CONTENT 填充模式
resultSize = size;
// 通常这个时候父控件也还没有确定自己的大小所以子控件也是 '最大' 模式
resultMode = MeasureSpec.AT_MOST;
}
break;
// 父控件不限制子控件的大小
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// 如果子控件是明确的大小就是用这个大小模式是 '精确' 模式
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 如果子控件宽高是 MATCH_PARENT 大小取决于 View.sUseZeroUnspecifiedMeasureSpec 的值
// View.sUseZeroUnspecifiedMeasureSpec 的值默认为 true 即子控件大小为 0
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 如果子控件宽高是 WRAP_CONTENT 大小取决于 View.sUseZeroUnspecifiedMeasureSpec 的值
// View.sUseZeroUnspecifiedMeasureSpec 的值默认为 true 即子控件大小为 0
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
// 根据测量的宽高封装一个 MeasureSpec 对象
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
方法已经详细注释了,具体逻辑可以通过一个表呈现:
右下角两项 UNSPECIFIED 为 0 或 parentSize 取决于 View 中静态变量 sUseZeroUnspecifiedMeasureSpec 的值,它的值是 sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < Build.VERSION_CODES.M
最后根据测量大小和测量模式封装成一个 MeasureSpec 然后去测量子控件,子控件的 measure 调用 onMeasure 方法接下来看一下 View 的 onMeasure 方法
// android.view.View#onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
// 如果有设置 background 则取 background 的原始宽度和 minWidth 的大值
// 如果没有设置 background 则取 minWidth 对应 androind:minWidth 属性
// getSuggestedMinimumHeight 也是这个逻辑相当于获取控件的默认大小
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
// 父控件的测量模式
int specMode = MeasureSpec.getMode(measureSpec);
// 父控件的测量大小
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
// 如果测量模式是 UNSPECIFIED 则使用默认大小
case MeasureSpec.UNSPECIFIED:
result = size;
break;
// 测量模式是 AT_MOST 或 EXACTLY 都使用父控件的大小
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
通过 getDefaultSize 方法可以知道在 View 的默认实现中,不管是 EXACTLY 还是 AT_MOST 模式拿到的都是父控件的可用大小,这就是说在 xml 文件中不管是设置为 match_parent 还是 wrap_content 显示出来的大小都是一样的所以当自定义 View 的时候通常要重写 onMeasure 方法根据需求给一个 AT_MOST 模式下的默认大小
总结一下 View 的测量过程就是从 DecorView 开始,根据屏幕宽高和 WindowManager.LayoutParams(width, height 默认是 MATCH_PARENT)得到 MeasureSpec 然后去测量自己,因为自己是 ViewGroup 所以会先测量所有的子控件才能知道自己的大小,如果子控件是 View 测量就结束了如果是 ViewGroup 则重复这个过程,这里普通控件(非 DecorView 其他 View)与 DecorView 的区别是普通控件的 MeasureSpec 是通过父控件的 MeasureSpec 和自己的 ViewGroup.LayoutParams 得到的
布局
布局从 ViewRootImpl 的 performLayout 开始然后调用真正的顶级控件 DecorView 的 layout 方法,FrameLayout 并没有重写 layout 方法 ViewGroup 也是简单的调用了 super.layout 方法所以直接看 View 的 layout 方法
// android.view.ViewRootImpl#performLayout
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
// 代码省略 ..
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
// 代码省略 ..
}
// android.view.View#layout
// 参数 l 对应上面 0
// 参数 t 对应上面 0
// 参数 r 对应上面 host.getMeasuredWidth() 测量宽度
// 参数 b 对应上面 host.getMeasuredHeight() 测量高度
public void layout(int l, int t, int r, int b) {
// 代码省略 ..
// 通过 setFrame 设置子控件在父控件中的位置
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// 如果位置有变化或者设置了 PFLAG_LAYOUT_REQUIRED 标记(通过 view.requestLayout 触发重新测量、布局时在 view.measure 中标记)调用 onLayout 重新布局,这里是针对 ViewGroup 让其去遍历布局子控件
// View 的 onLayout 是空实现
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
// 代码省略 ..
}
// 代码省略 ..
}
// android.view.View#setFrame
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
// 代码省略 ..
// 位置改变
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// 会触发重绘
invalidate(sizeChanged);
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
mPrivateFlags |= PFLAG_HAS_BOUNDS;
if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
// 代码省略 ..
}
return changed;
}
所谓布局其实就是把自己在父控件的坐标记录下来,之后就可以通过 getWidth/getHeight 拿到宽高了,所以 getWidth/getHeight 与 getMeasureWidth/getMeasureHeight 的区别就是时机不同,getMeasureWidth/getMeasureHeight 是在测量完成后可以拿到 getWidth/getHeight 是在布局完成后可以拿到,并且结果通常都是一样的(可以在布局时处理成不一样但没有意义)
ViewGroup 中 onLayout 是抽象方法需要子类去根据自己的需要重写,在具体 ViewGroup 子类中会遍历所有的子控件,如果子控件是 View 流程结束如果子控件是 ViewGroup 则递归执行这个过程
绘制
// android.view.ViewRootImpl#performDraw
private void performDraw() {
// 代码省略 ..
try {
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false;
}
}
// 代码省略 ..
}
// android.view.ViewRootImpl#draw
private boolean draw(boolean fullRedrawNeeded) {
// 代码省略 ..
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty || mNextDrawUseBlastSync) {
// 如果开启了硬件加速
if (isHardwareEnabled()) {
// 代码省略 ..
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
// 代码省略 ..
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
}
}
// 先来看未开启硬件加速的情况
// android.view.ViewRootImpl#drawSoftware
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
// 代码省略 ..
// 获取画布
final Canvas = mSurface.lockCanvas(dirty);
// 代码省略 ..
// 开始绘制流程
mView.draw(canvas);
// 代码省略 ..
}
// android.view.View#draw(android.graphics.Canvas)
public void draw(Canvas canvas) {
// 代码省略 ..
// 绘制背景不能重写
drawBackground(canvas);
// 代码省略 ..
// 绘制自己
onDraw(canvas);
// 绘制子控件
// 针对 ViewGroup 去分发绘制子控件
// 对于 View 没有意义
dispatchDraw(canvas);
// 绘制自动填充
drawAutofilledHighlight(canvas);
// 绘制 Overlay 在前景下面
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// 绘制装饰,前景、精度条等
onDrawForeground(canvas);
// 绘制高亮
drawDefaultFocusHighlight(canvas);
}
View 的绘制流程主要是 onDraw 绘制自己 dispatchDraw 分发绘制子控件,对于自定义 View 不需要重写 dispatchDraw 需要重写 onDraw 绘制自己的逻辑,对于自定义 ViewGroup 一般不需要重写 onDraw 绘制自己(并且 ViewGroup 的 onDraw 默认情况下不执行)需要重写 dispatchDraw 分发绘制所有子控件,如果子控件也是 ViewGroup 重复此过程
// android.view.ViewGroup#dispatchDraw
protected void dispatchDraw(Canvas canvas) {
// 代码省略 ..
for (int i = 0; i < childrenCount; i++) {
// 代码省略 ..
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
// 绘制子控件
more |= drawChild(canvas, child, drawingTime);
}
}
// 代码省略 ..
}
// android.view.ViewGroup#drawChild
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
// android.view.View#draw(android.graphics.Canvas, android.view.ViewGroup, long)
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
// 代码省略 ..
if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((RecordingCanvas) canvas).drawRenderNode(renderNode);
} else {
// 如果 View 设置了 WILL_NOT_DRAW 标记并且背景、前景、高亮都为空则会设置 PFLAG_SKIP_DRAW 标记跳过绘制自己
// ViewGroup 在初始化的时候默认会设置 WILL_NOT_DRAW 标记
// 所以如果 ViewGroup 不设置背景一般不会执行 onDraw 方法只会去尝试绘制子控件
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
// 否则执行自己完成的绘制流程
draw(canvas);
}
}
}
// 代码省略 ..
mRecreateDisplayList = false;
return more;
}
总结一下未开启硬件加速的流程从 android.view.ViewRootImpl#performDraw 调用开始调用 android.view.ViewRootImpl#draw 通过 Surface 对象申请一块画布调用 com.android.internal.policy.DecorView#draw 传入这块画布先通过 onDraw 方法绘制自己(也可以通过设置不绘制自己)再调用 dispatchDraw 绘制所有的子控件如果子控件是 ViewGroup 则递归执行这个流程。在看硬件加速的流程之前先看一下 android.view.View#invalidate() 这个方法(postInvalidate 是在子线程使用通过 Handler 间接调用的 invalidate 方法)
invalidate
// android.view.View#invalidate()
public void invalidate() {
// 参数值为 true
invalidate(true);
}
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
// 代码省略 ..
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {
// 代码省略 ..
if (invalidateCache) {
// 添加 PFLAG_INVALIDATED 标记
mPrivateFlags |= PFLAG_INVALIDATED;
// 移除 PFLAG_DRAWING_CACHE_VALID 标记
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
// 调用父控件的重绘方法并且把传入自己要绘制的脏区
p.invalidateChild(this, damage);
}
// 代码省略 ..
}
}
// android.view.ViewGroup#invalidateChild
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
// 如果开启了硬件加速
onDescendantInvalidated(child, child);
return;
}
ViewParent parent = this;
if (attachInfo != null) {
// 如果关闭硬件加速这里应该是 LAYER_TYPE_SOFTWARE 吧所以会执行吧
if (child.mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
// 代码省略 ..
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
// 代码省略 ..
// 循环调用父控件的 invalidateChildInParent 方法
// 因为 ViewRootImpl 是 DecorView 的 parent
// 所以最后会调用到 ViewRootImple 的 invalidateChildInParent 方法
parent = parent.invalidateChildInParent(location, dirty);
// 代码省略 ..
} while (parent != null);
}
}
// android.view.ViewGroup#onDescendantInvalidated
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
// 代码省略 ..
// 调用是循环调用 parent 的 onDescendantInvalidated 方法直到 ViewRootImpl
if (mParent != null) {
mParent.onDescendantInvalidated(this, target);
}
}
// android.view.ViewRootImpl#onDescendantInvalidated
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
// 代码省略 ..
invalidate();
}
// android.view.ViewRootImpl#invalidate
void invalidate() {
// 代码省略 ..
// 触发 View 工作流程(并不一定会执行测量、布局、绘制所有流程)
scheduleTraversals();
}
从上面最开始的分析已经知道 scheduleTraversals 会在下一次 Vsync 信号来的时候调用 performTraversals 再调用 performDraw 触发重绘,如果开启了硬件加速则继续执行到 android.view.ThreadedRenderer#draw
// android.view.ThreadedRenderer#draw
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
// 代码省略 ..
updateRootDisplayList(view, callbacks);
// 代码省略 ..
}
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
// 代码省略 ..
updateViewTreeDisplayList(view);
// 代码省略 ..
}
private void updateViewTreeDisplayList(View view) {
view.mPrivateFlags |= View.PFLAG_DRAWN;
// 因为在调用 invalidate 时设置了 PFLAG_INVALIDATED 所以 mRecreateDisplayList 为 true
view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
== View.PFLAG_INVALIDATED;
view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
view.updateDisplayListIfDirty();
view.mRecreateDisplayList = false;
}
// android.view.View#updateDisplayListIfDirty
public RenderNode updateDisplayListIfDirty() {
// 代码省略 ..
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
|| !renderNode.hasDisplayList()
|| (mRecreateDisplayList)) {
if (renderNode.hasDisplayList()
&& !mRecreateDisplayList) {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
// 如果不需要重新绘制则继续往下分发绘制流程
dispatchGetDisplayList();
return renderNode; // no work needed
}
mRecreateDisplayList = true;
// 代码省略 ..
final RecordingCanvas canvas = renderNode.beginRecording(width, height);
try {
if (layerType == LAYER_TYPE_SOFTWARE) {
buildDrawingCache(true);
Bitmap cache = getDrawingCache(true);
if (cache != null) {
canvas.drawBitmap(cache, 0, 0, mLayerPaint);
}
} else {
// 代码省略 ..
// 与软件绘制一样同样判断是否需要跳过绘制自己
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
// 跳过的话则直接去分发绘制子控件
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
} else {
// 执行完整的绘制流程
draw(canvas);
}
}
}
// 代码省略 ..
} else {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return renderNode;
}
// 此方法在 View 中是空方法因为 View 不需要去分发绘制
// android.view.ViewGroup#dispatchGetDisplayList
protected void dispatchGetDisplayList() {
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
recreateChildDisplayList(child);
}
}
// 代码省略 ..
}
// android.view.ViewGroup#recreateChildDisplayList
private void recreateChildDisplayList(View child) {
child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
child.mPrivateFlags &= ~PFLAG_INVALIDATED;
child.updateDisplayListIfDirty();
child.mRecreateDisplayList = false;
}
总结一下当通过 invalidate 请求重绘时调用 invalidate 的子控件会添加 PFLAG_INVALIDATED 标记并且通过控件的 parent 逐级请求重绘直到调用到 ViewRootImpl 的 scheduleTraversals 方法在下一次 Vsync 信号来的时候执行真正的重绘操作 并且因为只有直接调用 invalidate 的控件设置了 PFLAG_INVALIDATED 标记 view.mRecreateDisplayList 为 true 会执行重绘,其他控件调用 dispatchGetDisplayList 分发子控件的重绘操作并且同样会根据是否添加了 PFLAG_INVALIDATED 标记判断是否执行重绘直至完成所有控件的重绘动作,如果是首次打开页面直接通过 android.view.ViewRootImpl#requestLayout 触发整个流程应该是所有的控件都会被重绘
简单来说当关闭硬件加速时所有子 View 都会被重新绘制,开启硬件加速时只有调用 invalidate 方法的 View 才会重新绘制
requestLayout
// android.view.View#requestLayout
public void requestLayout() {
// 代码省略 ..
// 设置强制布局标志
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
// 调用 parent requestLayout
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
// 代码省略 ..
}
最后看一下 View 的 requestLayout 方法确实都设置了 PFLAG_FORCE_LAYOUT 强制布局标记。invalidate 与 requestLayout 的区别是 invalidate 只会触发绘制操作不会触发测量、布局流程因为 invalidate 方法没有设置 PFLAG_FORCE_LAYOUT 标记,requestLayout 通常情况下只会触发测量、布局流程不会触发重绘流程,但如果控件大小改变则也会触发绘制流程。
总结:首次 View 工作流程是通过 android.view.ViewRootImpl#requestLayout 触发的,它调用了 scheduleTraversals 方法向 Choreographer 发送一个 Runnable 和一个同步屏障消息(保证优先执行这个 Runnable 异步消息)在下一次 Vsync 信号到来后执行这个 Runnable 执行 View 真正的工作测量、布局、绘制。测量的入口是 perfromMeasure 方法它会调用 DecorView 的 measure 从顶层 View 开始整个 View 树的测量,如果 View 只是 View 则只测量自己就结束了如果是 ViewGroup 要先测量所有子控件再测量自己如果子控件还是 ViewGroup 递归执行。布局的入口是 performLayout 方法它会调用 DecorView 的 layout 从顶层 View 开始整个 View 树的布局,如果 View 只是 View 则只布局自己就结束了如果是 ViewGroup 要先布局自己再递归布局所有子控件。绘制的入口是 performDraw 方法它会调用 DecorView 的 draw 开始整个 View 树的绘制,如果 View 只是 View 则只绘制自己就结束了如果是 ViewGroup 要先绘制自己(ViewGroup 默认不绘制自己)再递归绘制所有子控件,并且绘制还有是否开启硬件加速两种情况,当开启硬件加速时只绘制直接调用 invalidate 触发重绘的 View 当未开启硬件加速时绘制所有控件。
参考与感谢
比较一下requestLayout和invalidate方法
边栏推荐
- Basic use of MySQL (it is recommended to read and recite the content)
- User datagram protocol UDP
- Lora gateway Ethernet transmission
- Proof of Stirling formula
- How to solve the problem of slow downloading from foreign NPM official servers—— Teach you two ways to switch to Taobao NPM image server
- Global and Chinese markets for patent hole oval devices 2022-2028: Research Report on technology, participants, trends, market size and share
- Hashcode and equals
- Global and Chinese markets for medical gas manifolds 2022-2028: Research Report on technology, participants, trends, market size and share
- VPP性能测试
- [disassembly] a visual air fryer. By the way, analyze the internal circuit
猜你喜欢
Comprehensive ability evaluation system
About some basic DP -- those things about coins (the basic introduction of DP)
关于进程、线程、协程、同步、异步、阻塞、非阻塞、并发、并行、串行的理解
Record the pit of NETCORE's memory surge
During pycharm debugging, the view is read only and pause the process to use the command line appear on the console input
DM8 archive log file manual switching
[PSO] Based on PSO particle swarm optimization, matlab simulation of the calculation of the lowest transportation cost of goods at material points, including transportation costs, agent conversion cos
Thread sleep, thread sleep application scenarios
SSTI template injection explanation and real problem practice
Solution to the problem that the root account of MySQL database cannot be logged in remotely
随机推荐
VPP性能测试
查询mysql数据库中各表记录数大小
[FPGA tutorial case 12] design and implementation of complex multiplier based on vivado core
Overturn your cognition? The nature of get and post requests
[adjustable delay network] development of FPGA based adjustable delay network system Verilog
[tomato assistant installation]
Solution to the problem that the root account of MySQL database cannot be logged in remotely
Determine which week of the month the day is
Global and Chinese markets for patent hole oval devices 2022-2028: Research Report on technology, participants, trends, market size and share
STC8H开发(十二): I2C驱动AT24C08,AT24C32系列EEPROM存储
【可调延时网络】基于FPGA的可调延时网络系统verilog开发
深入浅出node模板解析错误escape is not a function
How can programmers resist the "three poisons" of "greed, anger and ignorance"?
【leetcode】1189. Maximum number of "balloons"
Basic use of MySQL (it is recommended to read and recite the content)
【FPGA教程案例11】基于vivado核的除法器设计与实现
The global and Chinese market of negative pressure wound therapy unit (npwtu) 2022-2028: Research Report on technology, participants, trends, market size and share
Network security - Security Service Engineer - detailed summary of skill manual (it is recommended to learn and collect)
Basic knowledge of binary tree, BFC, DFS
C (XXIX) C listbox CheckedListBox Imagelist