热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

1.整个View中并没有ViewGroup的身影,而是依靠接口[ViewParent]全权负责这有一
[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  
 

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-] 源码级》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 我们 的支持!


推荐阅读
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
  • 网络请求模块选择——axios框架的基本使用和封装
    本文介绍了选择网络请求模块axios的原因,以及axios框架的基本使用和封装方法。包括发送并发请求的演示,全局配置的设置,创建axios实例的方法,拦截器的使用,以及如何封装和请求响应劫持等内容。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 本文介绍了在MFC下利用C++和MFC的特性动态创建窗口的方法,包括继承现有的MFC类并加以改造、插入工具栏和状态栏对象的声明等。同时还提到了窗口销毁的处理方法。本文详细介绍了实现方法并给出了相关注意事项。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
author-avatar
宛如画中人需_308
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有