invalidate方法源码分析

在之前分析View的绘制流程中,最后都有调用一个叫invalidate的方法,这个方法是啥玩意?我们来看一下View类中invalidate系列方法的源码(ViewGroup没有重写这些方法),如下:

  1. /**
  2.  * Mark the area defined by dirty as needing to be drawn. dirty代表需要重新绘制的脏的区域
  3.  * If the view is visible, onDraw(Canvas) will be called at some point in the future.
  4.  * This must be called from a UI thread. To call from a non-UI thread, call postInvalidate().
  5.  * <b>WARNING:</b> In API 19 and below, this method may be destructive to dirty.
  6.  * @param dirty the rectangle矩形 representing表示 the bounds of the dirty region地区
  7.  */
  8. public void invalidate(Rect dirty) {
  9. final int scrollX = mScrollX;
  10. final int scrollY = mScrollY;
  11. invalidateInternal(dirty.left - scrollX, dirty.top - scrollY,
  12. dirty.right - scrollX, dirty.bottom - scrollY, true, false);
  13. }
  14. public void invalidate(int l, int t, int r, int b) {
  15. final int scrollX = mScrollX;
  16. final int scrollY = mScrollY;
  17. //实质还是调用invalidateInternal方法
  18. invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
  19. }
  20. /**
  21.  * Invalidate the whole view. 重新绘制整个View
  22.  */
  23. public void invalidate() {
  24. invalidate(true);
  25. }
  26. public void invalidate(boolean invalidateCache) {
  27. invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
  28. }
  29. //这是所有invalidate的终极调用方法
  30. void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) {
  31. ......
  32. // Propagate繁殖、传播 the damage损害的(只需要刷新的) rectangle to the parent view.
  33. final AttachInfo ai = mAttachInfo;
  34. final ViewParent p = mParent;
  35. if (p != null && ai != null && l < r && t < b) {
  36. final Rect damage = ai.mTmpInvalRect;
  37. damage.set(l, t, r, b);//将需要刷新的区域封装到damage中
  38. p.invalidateChild(this, damage);//调用Parent的invalidateChild方法,传递damage给Parent
  39. }
  40. ......
  41. }

由此可知,View的invalidate方法实质是将要刷新区域直接传递给了【父ViewGroup的invalidateChild方法】,这是一个从当前View向上级父View回溯的过程 。


我们看下ViewGroup的invalidateChild方法:
  1. public final void invalidateChild(View child, final Rect dirty) {
  2. ViewParent parent = this;
  3. ......
  4. do {
  5. ......
  6. //循环层层上级调用,直到ViewRootImpl会返回null
  7. parent = parent.invalidateChildInParent(location, dirty);
  8. ......
  9. } while (parent != null);
  10. }

这里面主要是一个循环,循环结束的条件是 parent == null,什么情况下parent为null呢?

这个问题之前有分析过,这里直接说结论了,就是循环当层层上级传递到ViewRootImpl时结束,我们看下ViewRootImpl的invalidateChildInParent的源码:
  1. @Override
  2. public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
  3. ......
  4. //View调用invalidate最终层层上传到ViewRootImpl后最终触发了该方法
  5. scheduleTraversals();
  6. ......
  7. return null;
  8. }

也就是上面说的,当层层上级传递到ViewRootImpl的invalidateChildInParent方法时,返回了null,结束了那个do while循环。


这里调用的scheduleTraversals会通过Handler发送一个异步消息,收到消息后会回调doTraversal方法,最终调用performTraversals执行重绘。

performTraversals方法就是整个View树开始绘制的起始节点,所以,View调用invalidate方法的实质是:层层上传到父级,直到传递到ViewRootImpl后会触发scheduleTraversals方法,然后整个View树就开始重新按照View的绘制流程进行重绘任务。

这里再按正序说下整个View树的绘图流程:
整个View树的绘图流程是在【ViewRootImpl】类的【performTraversals】方法开始的,该函数做的执行过程主要是根据之前设置的【状态】,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重新绘制(draw),其核心也就是通过判断来选择按顺序执行这三个方法中的哪几个。

到此View的invalidate方法原理就分析完成了。

invalidate方法执行的过程图:

postInvalidate方法源码分析

上面分析invalidate方法时注释中说该方法只能在UI Thread中执行,其他线程中需要使用postInvalidate方法,所以我们来分析分析postInvalidate这个方法源码。如下:
  1. public void postInvalidate() {
  2. postInvalidateDelayed(0);
  3. }
  4. public void postInvalidateDelayed(long delayMilliseconds) {
  5. // We try only with the AttachInfo because there's no point in invalidating
  6. // if we are not attached to our window
  7. final AttachInfo attachInfo = mAttachInfo;
  8. //核心,实质就是调用了ViewRootImpl.dispatchInvalidateDelayed方法
  9. if (attachInfo != null) {
  10. attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
  11. }
  12. }
  13. public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
  14. Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
  15. mHandler.sendMessageDelayed(msg, delayMilliseconds);
  16. }

看见没有,通过ViewRootImpl类的Handler发送了一条MSG_INVALIDATE消息,继续追踪这条消息的处理可以发现:

  1. public void handleMessage(Message msg) {
  2.     ......
  3.     switch (msg.what) {
  4.     case MSG_INVALIDATE:
  5.         ((View) msg.obj).invalidate();
  6.         break;
  7.     ......
  8.     }
  9.     ......
  10. }

看见没有,实质就是又在UI Thread中调用了View的invalidate()方法,所以可以说,除了调用所在线程不一样之外,其他的和invalidate()方法都是一样的。


invalidate与postInvalidate方法总结

invalidate系列方法请求重绘View树时,如果View【大小】没有发生变化就不会调用layout过程,并且只绘制那些"需要重绘的"View,也就是哪个View请求invalidate系列方法,就绘制该View。

常见的引起invalidate方法操作的原因主要有:
  • 直接调用invalidate方法,请求重新draw,但只会绘制调用者本身。
  • 触发setSelection方法,请求重新draw,但只会绘制调用者本身。
  • 触发setEnabled方法,请求重新draw,但不会重新绘制任何View包括该调用者本身。
  • 触发requestFocus方法,请求View树的draw过程,只绘制"需要重绘"的View。
  • 触发setVisibility方法, 当View可视状态在INVISIBLE转换VISIBLE时会间接调用invalidate方法,继而绘制该View。当View的可视状态转换为GONE状态时会间接调用requestLayout和invalidate方法,同时由于View树大小发生了变化,所以会请求measure过程以及draw过程,同样只绘制需要"重新绘制"的视图。

补充performTraversals方法调用时机

之前我们说过:整个View绘制流程的最初代码是在ViewRootImpl类的performTraversals()方法中开始的,上面当时只是告诉你了这个结论,至于这个ViewRootImpl类的performTraversals()方法为何会被触发没有说明原因,现在我们就来分析一下这个触发的源头。

我们先来看下之前博文中分析过的,Activity中setContentView方法所调用的PhoneWindow中的setContentView方法的源码:
  1. @Override
  2. public void setContentView(View view, ViewGroup.LayoutParams params) {
  3. ......
  4. //如果mContentParent为空进行一些初始化
  5. if (mContentParent == null) {
  6. installDecor();
  7. ......
  8. //把我们的view追加到mContentParent
  9. mContentParent.addView(view, params);
  10. ......
  11. }

我们继续看下这个方法中所调用的addView方法,也就是ViewGroup的addView方法,如下:

  1. public void addView(View child) {
  2. addView(child, -1);
  3. }
  4. public void addView(View child, int index) {
  5. ......
  6. addView(child, index, params);
  7. }
  8. public void addView(View child, int index, LayoutParams params) {
  9. ......
  10. //该方法稍后后面会详细分析
  11. requestLayout();
  12. //重点关注!!!
  13. invalidate(true);
  14. ......
  15. }

看见addView调用invalidate方法没有?这不就真相大白了。当我们写一个Activity时,我们一定会通过setContentView方法将我们要展示的界面传入该方法,该方法会将我们的View通过addView追加到id为content的一个FrameLayout(ViewGroup)中,然后addView方法中通过调用invalidate(true)去通知触发ViewRootImpl类的performTraversals()方法,至此递归绘制我们自定义的所有布局。


requestLayout方法源码分析

和invalidate类似,之前在分析View绘制流程时或多或少都调用到了这个方法,而且这个方法对于View来说也比较重要,所以我们接下来分析一下他。如下为View的requestLayout源码:
  1. public void requestLayout() {
  2. ......
  3. if (mParent != null && !mParent.isLayoutRequested()) {
  4. //从这个View开始向上一直requestLayout,最终到达ViewRootImpl的requestLayout
  5. mParent.requestLayout();
  6. }
  7. ......
  8. }

看见没有,整个和invalidate类似,当我们触发View的requestLayout时其实质就是:层层向上传递,直到ViewRootImpl为止,最后触发ViewRootImpl的requestLayout方法。

如下就是ViewRootImpl的requestLayout方法:
  1. @Override
  2. public void requestLayout() {
  3. if (!mHandlingLayoutInLayoutRequest) {
  4. checkThread();
  5. mLayoutRequested = true;
  6. //View调用requestLayout最终层层上传到ViewRootImpl后最终触发了该方法
  7. scheduleTraversals();
  8. }
  9. }

看见没有,依旧是和invalidate类似,并且最终调用的也是scheduleTraversals()这个方法,只是在上传过程中所设置的标记不同,最终导致对于View的绘制流程中所触发的方法不同而已。


requestLayout方法总结

对于requestLayout方法来说,总结如下:
  • requestLayout()方法会调用【measure】过程和【layout】过程,不会调用【draw】过程,所以不会重新绘制任何View(包括该调用者本身)。
  • 使用条件:当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求父view重新调用他的onMeasure、onLayout来重新设置自己位置。
  • 用途:有时我们在改变一个view 的内容之后,可能会造成显示出现错误,比如写ListView的时候 重用convertview中的某个TextView 可能因为前后填入的text长度不同而造成显示出错,此时我们可以在改变内容之后调用requestLayout方法加以解决。

Google文档的英文说明:
Call this when something has changed which has invalidated the layout of this view 当View的布局已经无效时调用. This will schedule重新安排 a layout pass of通过 the view tree. This should not be called while the view hierarchy层次 is currently in a layout pass (isInLayout(). If layout is happening, the request may be honored at the end of the current layout pass (and then layout will run again) or after the current frame is drawn and the next layout occurs. 
Subclasses子类 which override this method should call the superclass method to handle possible request-during-layout errors correctly.

invalidate和requestLayout方法源码分析的更多相关文章

  1. Java split方法源码分析

    Java split方法源码分析 public String[] split(CharSequence input [, int limit]) { int index = 0; // 指针 bool ...

  2. Linq分组操作之GroupBy,GroupJoin扩展方法源码分析

    Linq分组操作之GroupBy,GroupJoin扩展方法源码分析 一. GroupBy 解释: 根据指定的键选择器函数对序列中的元素进行分组,并且从每个组及其键中创建结果值. 查询表达式: var ...

  3. 【Java】NIO中Selector的select方法源码分析

    该篇博客的有些内容和在之前介绍过了,在这里再次涉及到的就不详细说了,如果有不理解请看[Java]NIO中Channel的注册源码分析, [Java]NIO中Selector的创建源码分析 Select ...

  4. jQuery实现DOM加载方法源码分析

    传统的判断dom加载的方法 使用 dom0级 onload事件来进行触发所有浏览器都支持在最初是很流行的写法 我们都熟悉这种写法: window.onload=function(){ ... }  但 ...

  5. jQuery.extend()方法和jQuery.fn.extend()方法源码分析

    这两个方法用的是相同的代码,一个用于给jQuery对象或者普通对象合并属性和方法一个是针对jQuery对象的实例,对于基本用法举几个例子: html代码如下: <!doctype html> ...

  6. jQuery.clean()方法源码分析(一)

    在jQuery 1.7.1中调用jQuery.clean()方法的地方有三处,第一次就是在我之前的随笔分析jQuery.buildFramgment()方法里面的,其实还是构造函数的一部分,在处理诸如 ...

  7. HashMap put、get方法源码分析

    HashMap.java的实现是面试必问的问题. JDK版本 java version "1.8.0_91" Java(TM) SE Runtime Environment (bu ...

  8. HashMap主要方法源码分析(JDK1.8)

    本篇从HashMap的put.get.remove方法入手,分析源码流程 (不涉及红黑树的具体算法) jkd1.8中HashMap的结构为数组.链表.红黑树的形式     (未转化红黑树时)   (转 ...

  9. Hystrix微服务容错处理及回调方法源码分析

    前言 在 SpringCloud 微服务项目中,我们有了 Eureka 做服务的注册中心,进行服务的注册于发现和服务治理.使得我们可以摒弃硬编码式的 ip:端口 + 映射路径 来发送请求.我们有了 F ...

随机推荐

  1. ubuntu下 pycharm使用andcoda下的tensorflow

    在ubuntu中 tensorflow是安装在andconda里的一个虚拟环境中,他就相当于一个容器,将tensorflow的整个环境去模拟隔离出来,因为我们直接使用andconda库去作为pycha ...

  2. HDU 6186 CS Course

    保存前缀后缀. 保存一下前缀和后缀,去掉第$i$个位置,就是$L[i-1]$和$R[i+1]$进行运算. #include<bits/stdc++.h> using namespace s ...

  3. Mybatis源码分析之参数处理

    Mybatis对参数的处理是值得推敲的,不然在使用的过程中对发生的一系列错误直接懵逼了. 以前遇到参数绑定相关的错误我就是直接给加@param注解,也稀里糊涂地解决了,但是后来遇到了一些问题推翻了我的 ...

  4. python升级带来的yum异常:File "/usr/bin/yum", line 30

    问题: $ yum File "/usr/bin/yum", line 30 except KeyboardInterrupt, e: ^ SyntaxError: invalid ...

  5. RxSwift 系列(九)

    前言 看完本系列前面几篇之后,估计大家也还是有点懵逼,本系列前八篇也都是参考RxSwift官方文档和一些概念做的解读.上几篇文章概念性的东西有点多,一时也是很难全部记住,大家脑子里面知道有这么个概念就 ...

  6. 【BZOJ 2749】 2749: [HAOI2012]外星人 (数论-线性筛?类积性函数)

    2749: [HAOI2012]外星人 Description Input Output 输出test行,每行一个整数,表示答案. Sample Input 1 2 2 2 3 1 Sample Ou ...

  7. Nginx 502 Bad Gateway 解决方法

    proxy_next_upstream error timeout invalid_header http_500 http_503;或者尝试设置:large_client_header_buffer ...

  8. hdu 1533 KM或费用流

    以前用KM写过,现在再用费用流写. #include <iostream> #include <cstdio> #include <cstring> #includ ...

  9. Java泛型应用总结

    一.泛型的引入原因 在操作集合的时候,之前方法的定义都是Object类型,向集合中添加对象,都自动向上转型,加入的元素可以是任何类型 但是,在取出元素的时候,通常想要使用对象的特有功能,就必须向下转型 ...

  10. AtCoder Beginner Contest 022 A.Best Body 水题

    Best Body Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://abc022.contest.atcoder.jp/tasks/abc02 ...