乐趣区

View之invalidate,requestLayout,postInvalidate

目录介绍

01.invalidate,requestLayout,postInvalidate 区别
02.invalidate 深入分析
03.postInvalidate 深入分析
04.requestLayout 深入分析
05.ViewRootImpl 作用分析
06. 这几个方法总结

好消息

博客笔记大汇总【16 年 3 月到至今】,包括 Java 基础及深入知识点,Android 技术博客,Python 学习笔记等等,还包括平时开发中遇到的 bug 汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是 markdown 格式的!同时也开源了生活博客,从 12 年起,积累共计 N 篇 [近 100 万字,陆续搬到网上],转载请注明出处,谢谢!
链接地址:https://github.com/yangchong2…
如果觉得好,可以 star 一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!
01.Java 基础问题 (19 个)
02.Java 面向对象问题 (10 个)
03.Java 数据结构问题 (20 个)
04.JavaIO 流问题 (6 个)
05.java 多线程问题 (19)
06.Java 虚拟机问题 (10 个)
07.Java 类加载问题 (8 个)
08.Java 反射问题 (6 个)
10.Java 异常问题 (9 个)

01.requestLayout、invalidate 与 postInvalidate 作用与区别

invalidate() postInvalidate()

共同点:都是调用 onDraw() 方法,然后去达到重绘 view 的目的
区别:invalidate() 用于主线程,postInvalidate() 用于子线程【通过 handler 发送消息,在 handleMessage 中 ((View) msg.obj).invalidate(),】

requestLayout()

也可以达到重绘 view 的目的,但是与前两者不同,它会先调用 onLayout() 重新排版,再调用 ondraw() 方法。
当 view 确定自身已经不再适合现有的区域时,该 view 本身调用这个方法要求 parent view(父类的视图)重新调用他的 onMeasure、onLayout 来重新设置自己位置。特别是当 view 的 layoutparameter 发生改变,并且它的值还没能应用到 view 上时,这时候适合调用这个方法 requestLayout()。requestLayout 调用 onMeasure 和 onLayout,不一定调用 onDraw

02.invalidate 深入分析

看看 invalidate 源码,如下所示

invalidateCache 为 true 表示全部重绘。View 的 invalidate 方法最后调用的是 invalidateInternal 方法,invalidateInternal 方法中的重点逻辑在源码上添加注释呢。

public void invalidate() {
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 (mGhostView != null) {
mGhostView.invalidate(true);
return;
}

if (skipInvalidate()) {
return;
}

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 (fullInvalidate) {
mLastIsOpaque = isOpaque();
mPrivateFlags &= ~PFLAG_DRAWN;
}

mPrivateFlags |= PFLAG_DIRTY;

if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}

// 这个地方是重点逻辑,主要分析这个
// 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);
}

// Damage the entire projection receiver, if necessary.
if (mBackground != null && mBackground.isProjected()) {
final View receiver = getProjectionReceiver();
if (receiver != null) {
receiver.damageInParent();
}
}
}
}

看看 ViewGroup 中的 invalidateChild 方法
在 ViewGroup 的 invalidateChild 方法中有一个循环,循环里面会一直调用父布局的 invalidateChildInParent 方法,而 View 和 ViewGroup 的最终父布局都是 ViewRootImpl。
@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {

@Override
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
// 这是一个从当前的布局 View 向上不断遍历当前布局 View 的父布局,最后遍历到 ViewRootImpl 的循环
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}

// 这里调用的是父布局的 invalidateChildInParent 方法
parent = parent.invalidateChildInParent(location, dirty);
} while (parent != null);
}
}

@Override
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
(mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
FLAG_OPTIMIZE_INVALIDATE) {

// 这里也是一些计算绘制区域的内容
return mParent;
} else {
mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;
// 这里也是一些计算绘制区域的内容
return mParent;
}
}
return null;
}
}

View 中的 invalidateChild 方法和 ViewGroup 中的 invalidateChildInParent 方法最后殊途同归,都会调用到 ViewRootImpl 中的方法
可以看到在 ViewRootImpl 中最后都会调用 scheduleTraversals 方法进行绘制。按照对于 View 的绘制过程的理解,View 的绘制流程是从 ViewRootImpl 的 performTraversals 方法开始的
public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {

// 如果 View 没有父布局,那 invalidateInternal 方法就会调用这个方法
@Override
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
}

//ViewGroup 的 invalidateChild 方法最后会调用到这里
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
// 如果 dirty 为 null 就表示要重绘当前 ViewRootImpl 指示的整个区域
if (dirty == null) {
invalidate();
return null;
// 如果 dirty 为 empty 则表示经过计算需要重绘的区域不需要绘制
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
return null;
}

private void invalidateRectOnScreen(Rect dirty) {
final Rect localDirty = mDirty;

if (!mWillDrawSoon && (intersected || mIsAnimating)) {
// 调用 scheduleTraversals 方法进行绘制
scheduleTraversals();
}
}

// 绘制整个 ViewRootImpl 区域
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
// 调用 scheduleTraversals 方法进行绘制
scheduleTraversals();
}
}
}

下面我们来看看 ViewRootImpl 中的 scheduleTraversals 方法

看到 scheduleTraversals 方法中调用了 mChoreographer.postCallback 方法
Choreoprapher 类的作用是编排输入事件、动画事件和绘制事件的执行,它的 postCallback 方法的作用就是将要执行的事件放入内部的一个队列中,最后会执行传入的 Runnable,这里也就是 mTraversalRunnable。

void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}

来看看 TraversalRunnable 这个类做了什么?
可以看到最终调用了 performTraversals() 方法进行绘制
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

if (mProfile) {
Debug.startMethodTracing(“ViewAncestor”);
}

performTraversals();

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

大概总结一下

invalidate 方法最终调用的是 ViewRootImpl 中的 performTraversals 来重新绘制 View
在自定义 View 时,当需要刷新 View 时,如果是在 UI 线程中,那就直接调用 invalidate 方法,如果是在非 UI 线程中,那就通过 postInvalidate 方法来刷新 View

03.postInvalidate 深入分析

先来看看 View 中的 postInvalidate 方法
@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {

public void postInvalidate() {
postInvalidateDelayed(0);
}

public void postInvalidate(int left, int top, int right, int bottom) {
postInvalidateDelayed(0, left, top, right, bottom);
}

public void postInvalidateDelayed(long delayMilliseconds) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}

}

可以看到,postInvalidate 方法最后调用了 ViewRootImpl 的 dispatchInvalidateDelayed 方法
ViewRootImpl 中的 dispatchInvalidateDelayed 方法就是像 ViewRootHandler 发送了一个 MSG_INVALIDATE 消息,ViewRootHandler 是 ViewRootImpl 中的一个内部 Handler 类
// 发送消息
// 更多内容:https://github.com/yangchong211
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}

// 接收消息
final class ViewRootHandler extends Handler {
@Override
public String getMessageName(Message message) {
switch (message.what) {
case MSG_INVALIDATE:
return “MSG_INVALIDATE”;
}
return super.getMessageName(message);
}

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

大概总结一下

postInvalidate 方法调用了 ViewRootImpl 中的 dispatchInvalidateDelayed 方法向 ViewRootImpl 中的 ViewRootHandler 发送一个消息,最后调用的还是 View 的 invalidate 方法。
因为 ViewRootImpl 是在 UI 线程的,所以 postInvalidate 方法的作用就是将非 UI 线程的刷新操作切换到 UI 线程,以便在 UI 线程中调用 invalidate 方法刷新 View。所以如果我们自定义的 View 本身就是在 UI 线程,没有用到多线程的话,直接用 invalidate 方法来刷新 View 就可以了。而我们平时自定义 View 基本上都没有开起其他线程,所以这就是我们很少接触 postInvalidate 方法的原因。
一句话总结 postInvalidate 方法的作用就是:实现了消息机制,可以使我们在非 UI 线程也能调用刷新 View 的方法。

04.requestLayout 深入分析

源码如下所示

在 View 的 requestLayout 方法中,首先会设置 View 的标记位,PFLAG_FORCE_LAYOUT 表示当前 View 要进行重新布局,PFLAG_INVALIDATED 表示要进行重新绘制。
requestLayout 方法中会一层层向上调用父布局的 requestLayout 方法,设置 PFLAG_FORCE_LAYOUT 标记,最终调用的是 ViewRootImpl 中的 requestLayout 方法。

//View.class
@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;
}

// 设置 PFLAG_FORCE_LAYOUT 标记位,这样就会导致重新测量和布局
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
// 设置 PFLAG_INVALIDATED 就会进行重新绘制
mPrivateFlags |= PFLAG_INVALIDATED;

if (mParent != null && !mParent.isLayoutRequested()) {
// 不断调用上层 View 的 requestLayout 方法
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}

然后看看 ViewRootImpl 中的 requestLayout 方法
可以看到 ViewRootImpl 中的 requestLayout 方法中会调用 scheduleTraversals 方法,scheduleTraversals 方法最后会调用 performTraversals 方法开始执行 View 的三大流程,会分别调用 View 的 measure、layout、draw 方法。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}

然后再看看 measure 测量方法
由于 requestLayout 方法设置了 PFLAG_FORCE_LAYOUT 标记位,所以 measure 方法就会调用 onMeasure 方法对 View 进行重新测量。在 measure 方法中最后设置了 PFLAG_LAYOUT_REQUIRED 标记位,这样在 layout 方法中就会执行 onLayout 方法进行布局流程。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// 调用 onMeasure 方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}

// 设置 PFLAG_LAYOUT_REQUIRED 标记位,用于 layout 方法
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
}

再然后看看 layout 方法
由于 measure 方法中设置了 PFLAG_LAYOUT_REQUIRED 标记位,所以在 layout 方法中 onLayout 方法会被调用执行布局流程。最后清除 PFLAG_FORCE_LAYOUT 和 PFLAG_LAYOUT_REQUIRED 标记位。
public void layout(int l, int t, int r, int b) {
// 由于 measure 方法中设置了 PFLAG_LAYOUT_REQUIRED 标记位,所以会进入调用 onLayout 方法进行布局流程
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);

if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}

// 取消 PFLAG_LAYOUT_REQUIRED 标记位
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}

// 取消 PFLAG_FORCE_LAYOUT 标记位
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

05.ViewRootImpl 作用分析

链接 WindowManager 和 DecorView 的纽带,另外 View 的绘制也是通过 ViewRootImpl 来完成的。

它的主要作用我的总结为如下:
A:链接 WindowManager 和 DecorView 的纽带,更广一点可以说是 Window 和 View 之间的纽带。
B:完成 View 的绘制过程,包括 measure、layout、draw 过程。
C:向 DecorView 分发收到的用户发起的 event 事件,如按键,触屏等事件。

06. 这几个方法总结

requestLayout 方法会标记 PFLAG_FORCE_LAYOUT,然后一层层往上调用父布局的 requestLayout 方法并标记 PFLAG_FORCE_LAYOUT,最后调用 ViewRootImpl 中的 requestLayout 方法开始 View 的三大流程,然后被标记的 View 就会进行测量、布局和绘制流程,调用的方法为 onMeasure、onLayout 和 onDraw。
invalidate 方法我们分析过,它的过程和 requestLayout 方法方法很像,但是 invalidate 方法没有标记 PFLAG_FORCE_LAYOUT,所以不会执行测量和布局流程,而只是对需要重绘的 View 进行重绘,也就是只会调用 onDraw 方法,不会调用 onMeasure 和 onLayout 方法。

其他介绍
01. 关于博客汇总链接

1. 技术博客汇总

2. 开源项目汇总

3. 生活博客汇总

4. 喜马拉雅音频汇总

5. 其他汇总

02. 关于我的博客

我的个人站点:www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211

知乎:https://www.zhihu.com/people/…

简书:http://www.jianshu.com/u/b7b2…

csdn:http://my.csdn.net/m0_37700275

喜马拉雅听书:http://www.ximalaya.com/zhubo…

开源中国:https://my.oschina.net/zbj161…

泡在网上的日子:http://www.jcodecraeer.com/me…

邮箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a… 239.headeruserinfo.3.dT4bcV
segmentfault 头条:https://segmentfault.com/u/xi…

掘金:https://juejin.im/user/593943…

退出移动版