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

栏目: IOS · Android · 发布时间: 6年前

内容简介:1.整个View中并没有ViewGroup的身影,而是依靠接口[ViewParent]全权负责这有一个问题:ViewParent的实现类是谁? 明面有一个ViewGroup的实现, 但别忘了幕后还有个大佬ViewRootImpl也是实现了ViewParent的,那这个p到底是谁呢?
[1].View#invalidate做了什么,为什么会触发View的重绘?
[2].View是如何被添加到ViewGroup中的?
[3].ViewGroup和ViewRootImpl在invalidate孩子上做了什么?
[4].源码的层次上分析invalidate和postInvalidate的差异与联系?
复制代码

1. View#invalidate 方法

invalidate方法知多少[-View-] 源码级
---->[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到底是谁呢?

invalidate方法知多少[-View-] 源码级
|--可以看到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大佬出场,一招定乾坤

invalidate方法知多少[-View-] 源码级
---->[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的区别

invalidate方法知多少[-View-] 源码级
---->[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/5c6b71…

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----看到这里,我在此感谢你的喜欢与支持

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

以上所述就是小编给大家介绍的《invalidate方法知多少[-View-] 源码级》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Sovereign Individual

The Sovereign Individual

James Dale Davidson、William Rees-Mogg / Free Press / 1999-08-26 / USD 16.00

Two renowned investment advisors and authors of the bestseller The Great Reckoning bring to light both currents of disaster and the potential for prosperity and renewal in the face of radical changes ......一起来看看 《The Sovereign Individual》 这本书的介绍吧!

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具