剖析 View 中的 Invalidate, PostInvalidate 和 RequestLayout

文章目录
  1. 1. 引言
  2. 2. 源码分析
  3. 3. 总结
  4. 4. 参考文献

大巧无工,重剑无锋。

View 在 Android 开发中,有着极其重要的地位,甚至比常说的四大组件中的 BroadcastReceiver 和 ContentProvider 都重要。不知大家发现没有,在日常 App 的开发中,大部分时候都是和各种 View 打交道。这次,就来一探开发 View 中用到的 invalidate()、postInvalidate() 和 requestLayout() 的究竟。

引言

先从一道经典的面试题开始,“请描述 invalidate()、postInvalidate() 和 requestLayout() 的异同”,当初我甚至以为 invalidate() 是立即触发重绘,而 postInvalidate() 是延迟一段时间触发重绘,requestLayout() 都不知道是干嘛的,丢脸丢大发了。简略正确的回答应该大致如下:

I. Android 中实现 View 的更新有两种方法,一种是 invalidate(),另一种是 postInvalidate(),invalidate() 是在 UI 线程自身中使用,而 postInvalidate() 是在非 UI 线程中使用。

II. 当 View 确定自身不再适合现有的区域时,会调用 requestLayout() 方法要求 Parent View 重新调用其 onMeasure() 和 onLayout() 方法来重新设置自己的位置,尤其当 View 的 LayoutParams 发生改变,且其值尚未应用到 View 上时。

源码分析

I. invalidate()

调用该方法实现 View 树的重绘,常用于内部调用(如 setVisibility()) 或者需要刷新界面的时候,需要在主线程中(即 UI 线程)中调用该方法。

View#invalidate() 的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void invalidate() {
invalidate(true);
}

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)
{

// ......
// Propagate the damage rectangle to the parent view.
// 将需要重绘的区域传递给父视图
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);
}
// ......
}

大体调用流程为:invalidate() — invalidateInternal() — p.invalidateChild() — ViewRootImpl 中的 invalidateChild() — invalidateChildInParent() — invalidateRectOnScreen() — scheduleTraversals()。

ViewRootImpl#scheduleTraversals() 的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}

在 scheduleTraversals() 方法中,mTraversalRunnable 是一个 Runnable 对象:

1
2
3
4
5
6
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}

另外,mChoreographer 是一个 Choreographer 对象,调用 postCallback() 方法,走到 ViewRootImpl 中的 scheduleFrameLocked() 方法,其内部通过 Handler 发送一个 Message 消息。

ViewRootImpl#scheduleFrameLocked() 的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}

// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
// 发送 Message 消息
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}

其对消息的处理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private final class FrameHandler extends Handler {
public FrameHandler(Looper looper) {
super(looper);
}

@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
}
}
}

doFrame() 中调用 doCallbacks() 方法,根据标志位,后来走到消息对象 mTraversalRunnable 的 run() 方法,在上文TraversalRunnable类中,run() 方法里会执行 doTraversal(),源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}

performTraversals();

if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}

该方法中会调用到 ViewRootImpl 中的 performTraversals() 方法,此方法为 View 绘图中第一个执行的方法,仔细看,方法中依次调用到 performMeasure()、performLayout() 和 performDraw() 方法来进行界面重绘。

II. postInvalidate()

该方法最终也是调用 invalidate() 进行重绘,其在子线程中,通过消息机制回调到主线程中,再调用 invalidate() 方法。

View#postInvalidate() 的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public void postInvalidate() {
postInvalidateDelayed(0);
}

public void postInvalidateDelayed(long delayMilliseconds) {
// We try only with the AttachInfo because there's no point in invalidating
// if we are not attached to our window
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}

这是一个异步方法,在视图尚未被添加到窗口时通知重绘的话,会出现错误。故只有 attachInfo 不为 null 的时候会继续执行,即只有确保视图被添加到窗口的时候,才会通知 View 树重绘。其中,调用到dispatchInvalidateDelayed() 方法:

1
2
3
4
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}

通过 Handler 发送一个异步消息 MSG_INVALIDATE 到主线程,通知主线程刷新视图:

1
2
3
4
5
6
7
8
9
10
11
12
13
final class ViewRootHandler extends Handler {

// ......
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
// ......
}
}
}

很明显,调用到 invalidate() 方法,就继续 invalidate() 流程实现重绘。

III. requestLayout()

当动态移动一个 View 的位置,或者 View 的大小、形状发生变化时,调用该方法:

1
view.requestLayout();

根据方法名字,“请求布局”即调用该方法时,子 View 会重新进行布局流程。然而,真实情况是子 View 调用该方法,会从 View 树重新进行一次测量、布局和绘制三个流程,再会显示子 View 的最终情况。那么,具体实现是什么呢?

首先,看 View#requestLayout() 的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 从源码注释可以看出,如果当前 View 在请求布局的时候,View 树正在进行布局流程的话,
// 该请求会延迟到布局流程完成后或者绘制流程完成且下一次布局发现的时候再执行。
@CallSuper
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();

if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}

// 为当前 View 设置标记位 PFLAG_FORCE_LAYOUT
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;

if (mParent != null && !mParent.isLayoutRequested()) {
// 向父容器请求布局
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}

首先,判断当前 View 树是否正在布局流程。接着,为当前子 View 设置标记位,标记当前的 View 需要重新布局。然后,调用 mParent.requestLayout() 方法,向父视图请求布局,即调用父视图的 requestLayout() 方法,为父视图添加 PFLAG_FORCE_LAYOUT 标记位。该父视图又会调用自己父视图的 requestLayout() 方法,即 requestLayout 事件层层向上传递,直到根 View (DecorView),最后会传递给 ViewRootImpl,也就是说,子 View 的 requestLayout 事件,最终会被 ViewRootImpl 接收并处理。综上所述,整个传递的流程是典型的责任连模式,即不断地向上传递事件,直到找到能处理该事件的上级,这里,ViewRootImpl 处理 requestLayout 事件,重写了 requestLayout() 方法。

ViewRootImpl#requestLayout() 的源码如下:

1
2
3
4
5
6
7
8
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}

又走到了 scheduleTraversals() 方法!同样的分析又可以在上文提到的部分继续下去。

总结

如上流程图所示,一目了然。若 View 自身不再适用于当前区域,比如其 LayoutParams 发生改变,需要父布局对其重新测量、布局和绘制,往往使用 requestLayout()。而 invalidate() 或者 postInvalidate() 则是刷新当前 View,对当前的 View 进行重绘,不会进行测量与布局流程。因此,若 View 只需要重绘而不用测量和布局的时候,使用 invalidate() 比 requestLayout() 更加高效。

至此,从源码角度对 invalidate,postInvalidate 和 requestLayout 的剖析完毕。

本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。

1
Email: [email protected] / WeChat: Wolverine623

您也可以关注我个人的微信公众号码农六哥第一时间获得博客的更新通知,或后台留言与我交流

参考文献

1.http://www.jianshu.com/p/effe9b4333de