转载地址:https://juejin.cn/post/7100121390090551332
背景
Invalidate()
是 Android
中 View
的方法,通常我们使用它来完成UI
的刷新,
@L_404_1@作用
如果这个 View
可见那么 onDraw()
方法将在未来某个时间点被调用。
问题
invalidate()
会触发那些 view
的重绘,invalidate()
绘制流程是如何实现的?
我们带着问题来从源码开始分析:
源码分析
一、View 与 ViewGroup 的层级
在 Android
中 View
是以树形结构组织的,下图相信大家都不陌生,那么我们调用红色View的 invalidate()
会发生什么?
1.1 View.invalidate()
方法入口:
public void invalidate() {
invalidate(true);
}
参数:
-
l,t,r,b
是 View 的大小 -
invalidateCache
: 设置 View 的缓存是否失效,通常情况下是ture
,当View
的大小改变时为false
-
fullInvalidate
: 默认为true
void invalidateInternal(int l,int t,int r,int b,boolean invalidateCache,boolean fullInvalidate) {
...
final AttachInfo ai = mAttachInfo;
// 改 View 的父布局
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
// 记录需要绘制的范围 damge ,该区域为 View 尺寸
final Rect damage = ai.mTmpInvalRect;
damage.set(l,b);
// 调用父布局的 invalidateChild()
p.invalidateChild(this,damage);
}
...
}
说明: 将 View
需要绘制大小 Rect
告诉父ViewGroup
,并调用父 ViewGroup
的 invalidateChild()
1.2 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) {
...
// 这个循环会一直找到父布局的 DecordView invalidateChildInParent()
do {
...
// 标记 View
if (view != null) {
if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
}
}
parent = parent.invalidateChildInParent(location,dirty);
...
} while (parent != null);
}
}
绘制分为两个分支:
- 硬件加速绘制
- 软件绘制
硬件加速绘制不做介绍,主要分析软件绘制。
软件绘制会循环一直到根的 DecorView
中,而 DecorView
是由 ViewRootImp
管理,并维护 mPrivateFlags
mPrivateFlags:
计算需要刷新的View
1.3 ViewRootImp.invalidateChildInParent()
@Override
public ViewParent invalidateChildInParent(int[] location,Rect dirty) {
...
invalidateRectOnScreen(dirty);
return null;
}
在根布局 ViewRootImp
会处理我们传入的 Rect dirty
区域。
1.4 ViewRootImp.invalidateRectOnScreen()
private void invalidateRectOnScreen(Rect dirty) {
final Rect localDirty = mDirty;
// 通过我们传入的区域,计算需要更新的区域
localDirty.union(dirty.left,dirty.top,dirty.right,dirty.bottom);
final float appScale = mAttachInfo.mApplicationScale;
final boolean intersected = localDirty.intersect(0,(int) (mWidth * appScale + 0.5f),(int) (mHeight * appScale + 0.5f));
if (!intersected) {
localDirty.setEmpty();
}
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
}
通过我们 dirty
的区域,计算需要更新的区域,然后调用 scheduleTraversals()
。
1.5 ViewRootImp.scheduleTraversals()
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL,mTraversalRunnable,null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
通过 Handler
开启同步屏障,Post
一个 Callback
,接下来我们来看这个 Callback
的实现。
1.6 ViewRootImp.mTraversalRunnable
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
主要记录绘制时间,并调用 performTraversals()
开始绘制
1.7 核心函数 ViewRootImp.performTraversals()
void performTraversals() {
if (layoutRequested) {
// Clear this Now,so that if anything requests a layout in the
// rest of this function we will catch it and re-run a full
// layout pass.
mLayoutRequested = false;
}
...
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
...
performlayout(lp,mWidth,mHeight);
...
performDraw();
...
}
依次执行 performMeasure(),performlayout(),performDraw()
mLayoutRequested:
默认为 false
,意味着只会执行 onDraw()
,不会执行 onMeasure()
1.8 performMeasure()
private void performMeasure(int childWidthMeasureSpec,int childHeightMeasureSpec) {
try {
mView.measure(childWidthMeasureSpec,childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
调用 View.measure()
。
1.9 View.measure()
public final void measure(int widthMeasureSpec,int heightMeasureSpec) {
if (forceLayout || needsLayout) {
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec,heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
...
}
}
view.measure()
会调用我们重写的 onMeasure()
。
View.layout()
与 View.draw()
方法与 View.measure()
方法执行流程类似不在分析
总结:
invalidate() 整体流程:
Android
中 UI
刷新的逻辑都是按照以上树形结构来运行的,从子 View
把要做的事情传给根布局,再由根布局分布整个 View Tree
,导致整个 View Tree
进行刷新。
invalidate() 性能考虑:
- 子
View
将更新逻辑传递给ViewRootImp
过程中,通过Rect
计算需要更新的View
,并用mPrivateFlags
标记出来。 -
View
从上往下执行onMeasure()、onLayout()、onDraw()
时通过mPrivateFlags
判断是否刷新达到优化的目的
invalidate只是调用 performDraw()
onMeasure()、onLayout()、onDraw()
这三个函数不一定都执行,由 mLayoutRequested
默认是 false
仅会执行 onDraw()
。
如果发现
onMesure()、onLayout()
无效时候,通常我们会调用requestLayout()
,本质上它是将mLayoutRequested
设置为true
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。