阅读 470

invalidate方法知多少[-View-] 源码级

零、前言

本文聚焦:
[1].View#invalidate做了什么,为什么会触发View的重绘?
[2].View是如何被添加到ViewGroup中的?
[3].ViewGroup和ViewRootImpl在invalidate孩子上做了什么?
[4].源码的层次上分析invalidate和postInvalidate的差异与联系?
复制代码

1.View#invalidate方法

View刷新.png

---->[View#invalidate]--------------------
public void invalidate() {
    invalidate(true);
}

---->[View#invalidate(boolean)]--------------------
 * @hide
 */
public void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

---->[View#invalidateInternal]--------------------
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
        boolean fullInvalidate) {
    if (mGhostView != null) {
        mGhostView.invalidate(true);
        return;
    }
    ...
        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);
            ///调用父控件的invalidateChild()刷新本View
            p.invalidateChild(this, damage);
        }
        ...
    }
}
|--- 到这里暂停一下
这mGhostView真跟鬼一样,View中出现了8次,竟没有一次对它赋值  
由于是包访问的可能在其他类里吧,这里注意一下,毕竟如果他不为空,就画他然后return复制代码

2.谁是我爸?View的滴血认亲

整个View中并没有ViewGroup的身影,而是依靠接口[ViewParent]全权负责
这有一个问题:ViewParent的实现类是谁? 明面有一个ViewGroup的实现, 但别忘了幕后还有个大佬ViewRootImpl也是实现了ViewParent的,那这个p到底是谁呢?

ViewGroup添加View.png

|--可以看到p是承接mParent的局部变量,全文搜索[mParent =]来查看他何时初始化或被赋值的
---->[View#成员变量]-----------------------------
//The parent this view is attached to. -- 该View添加到的父View
protected ViewParent mParent;  //注意是protected的访问权限

---->[View#assignParent]-----------------------------
|-- 这里可见assignParent是初始化mParent的核心方法
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");
    }
}
|-- 搜索了一下assignParent在View中并未被调用,那只能说是别人调的  
|-- 和View认老爸关系最密切的当属ViewGroup中的addView了,来看一下

---->[ViewGroup#addView(View)]-----------------------------
public void addView(View child) {
--->addView(child, -1);
}

---->[ViewGroup#addView(View,int)]-----------------------------
public void addView(View child, int index) {
    ...
    LayoutParams params = child.getLayoutParams();
    if (params == null) {
        params = generateDefaultLayoutParams();
        ...
    }
--->addView(child, index, params);
}

---->[ViewGroup#addView(View,int,LayoutParams)]-----------------------------
public void addView(View child, int index, LayoutParams params) {
    ...
    requestLayout();
    invalidate(true);
--->addViewInner(child, index, params, false);
}

---->[ViewGroup#addViewInner]-----------------------------
private void addViewInner(View child, int index, LayoutParams params,
        boolean preventRequestLayout) {
    ...
--->addInArray(child, index);//将孩子加入到自己的数组里
    // tell our children -- 告诉我们的孩子们,他们有爹了
    if (preventRequestLayout) {
--->    child.assignParent(this);// 便是我们要寻的
    } else {
--->    child.mParent = this; //这直接让孩子的mParent赋值
    }
    ...
}

|-- 现在再看一下ViewRootImpl,我就单刀直入了,从setView开始,不懂的,看前面几篇相关内容
---->[ViewRootImpl#setView]-------------------------
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ... view.assignParent(this);
}

|-- 此时的View前几篇分析过是DecorView,其中调用了DecorView的assignParent,
所以DecorView是认ViewRootImpl为老爹的,虽然ViewRootImpl不是View,但它却是是个ViewParent
所以当爹是没问题的,那么View的invalidate方法走的是ViewGroup还是ViewRootImpl的invalidateChild?

答:如果是一个ViewGroup,它添加了子View,该子View的爹就是ViewGroup,
走的当然也是ViewGroup#invalidateChild,这是我们日常开发中最常见的

但对于最顶层的DecorView,谁敢当他爹?ViewRootImpl就是他老爸,所以对于DecorView的invalidate方法
当然走的是ViewRootImpl#invalidateChild,所以这就是为什么ViewRootImpl为什么那么厉害的原因
换句话来说,协天子以令诸侯有没有。ViewRootImpl说你们不要在子线程给我刷新UI,View们就乖乖照做
复制代码

3.ViewGroup#invalidateChild方法
---->[ViewGroup#invalidateChild]--------------------
|--- ViewGroup作为ViewParent的实现类, invalidateChild方法我们看到了
public final void invalidateChild(View child, final Rect dirty) {
    ...
    ViewParent parent = this;
    if (attachInfo != null) {
        ...
        }
        do {
            View view = null;
            if (parent instanceof View) {
                view = (View) parent;
            }
            ...
            //循环找到根view,并调用invalidateChildInParent()方法
            parent = parent.invalidateChildInParent(location, dirty);
            if (view != null) {
                ...
            }
        } while (parent != null);
    }
}

|-- 这里通过 while 来遍历 this ,都执行了一个invalidateChildInParent的方法  
该方法返回了一个ViewParent对象,来看一下这个方法:

---->[ViewGroup#invalidateChildInParent]--------------------
public ViewParent invalidateChildInParent(final int[] location, final Rect dir
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
        ...
        return mParent; 
    }
    return null;
}
|-- 这方法看起来只是设置了一下自己的区域和摆位,并没有什么实质性的东西  
|-- 不过亮点是他的返回值mParent,也就是它把自己整理一下,把老爸跑出去了  
|-- 这样看来上面的invalidateChild就是一直抛老爸,直到DecorView  
|-- 因为DecorView 的老爸是ViewRootImpl,所以[parent instanceof View]的条件不满足  
|-- 这时候就调用了ViewRootImpl#invalidateChild(ViewGroup全程打酱油的既视感...)

复制代码

4.绘制更新核心:ViewRootImpl#invalidateChild方法

ViewGroup并不给力,并没有触发孩子绘制方法,ViewRootImpl大佬出场,一招定乾坤

ViewRootImpl刷新孩子.png

---->[ViewRootImpl#invalidateChild]--------------------
 @Override
 public void invalidateChild(View child, Rect dirty) {
     invalidateChildInParent(null, dirty);
 }
 
---->[ViewRootImpl#invalidateChildInParent]--------------------
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    checkThread();//划重点...这里检查线程。曹操说:子线程不能更新UI
    if (dirty == null) {
        invalidate();
        return null;
    } else if (dirty.isEmpty() && !mIsAnimating) {
        return null;
    }
    ...
--->invalidateRectOnScreen(dirty);
    return null;
}

---->[ViewRootImpl#invalidateRectOnScreen]--------------------
private void invalidateRectOnScreen(Rect dirty) {
    ...
    if (!mWillDrawSoon && (intersected || mIsAnimating)) {
--->    scheduleTraversals();//开启了一个遍历的计划
    }
}


---->[ViewRootImpl#scheduleTraversals]--------------------
|---Choreographer 翻译一下:舞蹈指导者?--大佬真会起名字...
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
    --->        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

--------下一段不想看可以跳过,主要追了一下传入的Runnable是什么时候被执行的-------
|---Choreographer的postCallback核心调用的是下面的这个方法:
|--- 主要看入参Runnable的去向,下面的action便是Runnable
---->[Choreographer##postCallbackDelayedInternal]--------------------
private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {
    ...
    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        //这里对action做了处理
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
        if (dueTime <= now) {
            scheduleFrameLocked(now);
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}

public void addCallbackLocked(long dueTime, Object action, Object token) {
    CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
    ...
}

private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) {
    CallbackRecord callback = mCallbackPool;
    if (callback == null) {
        callback = new CallbackRecord();
    } else {
        mCallbackPool = callback.next;
        callback.next = null;
    }
    callback.dueTime = dueTime;
--->callback.action = action;
    callback.token = token;
    return callback;
}
|-- 可见action流转到了CallbackRecord的action字段中了

---->[Choreographer#CallbackRecord]------------------------------------------
|-- 可见CallbackRecord的run方法触发了action的run
private static final class CallbackRecord {
    public CallbackRecord next;
    public long dueTime;
    public Object action; // Runnable or FrameCallback
    public Object token;

    public void run(long frameTimeNanos) {
        if (token == FRAME_CALLBACK_TOKEN) {
            ((FrameCallback)action).doFrame(frameTimeNanos);
        } else {
            ((Runnable)action).run();
        }
    }
}

|-- 全局搜了一下,代码就不贴了,最后[c.run(frameTimeNanos)]在[doCallbacks]方法中触发  
|-- 而[doCallbacks]在[doFrame]触发,[doFrame]在handler接收[MSG_DO_FRAME]时触发  
---------------------------------------------------------------------------------------

|--言归正传:mTraversalRunnable是一个Runnable,通过Choreographer#postCallback最终会被执行
|-- 看一下mTraversalRunnable是什么,干了啥
---->[Choreographer#CallbackRecord]--------------------------------------
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

|-- 简单易懂,执行doTraversal()操作,至于doTraversal()是干嘛的...
|-- 简单讲一下,doTraversal()操作遍历所有节点,进行测量、布局、绘制--(这曹操当得也不容易啊)
|-- 同样的分析我不想写第二遍,详见:所得与所见:[-View周边-] 框架层#三#4  
复制代码

到这里总算解开我:invalidate怎样触发View重绘的谜题了。


5.postInvalidate()和 invalidate的区别

postInvalidate分析.png

---->[View#postInvalidate]-----------------------
public void postInvalidate() {
    postInvalidateDelayed(0);
}

---->[View#postInvalidateDelayed]-----------------------
public void postInvalidateDelayed(long delayMilliseconds) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
    }
}
|-- 感觉挺直爽,直接拿ViewRootImpl#dispatchInvalidateDelayed

---->[ViewRootImpl#dispatchInvalidateDelayed]-----------------------
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
    Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
    mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
|-- Handler通过obtainMessage将view放在一个MSG_INVALIDATE标识的Message中
|-- 如果Handler不熟悉的,还请移驾:Android点将台:烽火狼烟[-Handler-]
|-- 看Handler,首先不是看它的handleMessage是怎么处理的,而是看Handler在哪个线程创建的  
|-- 也就是Handler的Looper是在哪个线程。

---->[ViewRootImpl#mHandler]-----------------------
|-- 并没有在子线程,加上ViewRootImpl是在主线程被创建的(不知道到的看前文),所以mHandler是主线程
final ViewRootHandler mHandler = new ViewRootHandler();

 @Override
 public void handleMessage(Message msg) {
     switch (msg.what) {
     case MSG_INVALIDATE:
         ((View) msg.obj).invalidate();//调用了View的invalidate,已切至主线程
         break;
         
|-- 到这里就到头了,也就是说谷歌的大佬怕我们在子线程invalidate烦神
|-- 就内置的了一个Handler帮我们省去麻烦,至于用invalidate还是postInvalidate?

一条直线能到家,你还非要拐个弯吗?毕竟postInvalidate也是触发了View#invalidate  
还要额外发个消息才能玩。所以主线程用invalidate,在子线程可以用postInvalidate  
当然你觉得postInvalidate太长不好看,可以也无视大佬的一片好心,自己新建Handler,只要你开心...
复制代码

总的看来,View的invalidate方法也并没有我相信中的那么复杂,半天就写完了...


后记:捷文规范

1.本文成长记录及勘误表
项目源码 日期 附录
V0.1-- 2018-2-23

发布名:invalidate方法知多少[-View-] 源码级
捷文链接:juejin.im/post/5c70ce…

2.更多关于我
笔名 QQ 微信
张风捷特烈 1981462002 zdl1994328

我的github:github.com/toly1994328
我的简书:www.jianshu.com/u/e4e52c116…
我的掘金:juejin.im/user/5b42c0…
个人网站:www.toly1994.com

3.声明

1----本文由张风捷特烈原创,转载请注明
2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持

icon_wx_200.png

关注下面的标签,发现更多相似文章
评论