invalidate()和postInvalidate()的使用与区别

Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,因为他是违背了单线程模型:

Android UI操作并不是线程安全的,并且这些操作必须在UI线程中调用。 

  invalidate()是用来刷新View的,必须是在UI线程中进行工作。比如在修改某个view的显示时,调用invalidate()才能

看到重新绘制的界面。invalidate()的调用是把之前的旧的view从主UI线程队列中pop掉。

  Android 程序默认情况下也只有一个进程,但一个进程下却可以有许多个线程。在这么多线程当中,把主要是负责控

制UI界面的显示、更新和控件交互的线程称为UI线程,由于onCreate()方法是由UI线程执行的,所以也可以把UI线程理解

为主线程。其余的线程可以理解为工作者线程。invalidate()得在UI线程中被调动,在工作者线程中可以通过Handler来通

知UI线程进行界面更新。而postInvalidate()在工作者线程中被调用。

 

一、利用invalidate()刷新界面

实例化一个Handler对象,并重写handleMessage方法调用invalidate()实现界面刷新;而在线程中通过sendMessage发送界面更新消息。 

复制代码
 1 // 在onCreate()中开启线程
 2 
 3 new Thread(new GameThread()).start();、
 4 
 5 // 实例化一个handler
 6 
 7 Handler myHandler = new Handler() {
 8 // 接收到消息后处理
 9 public void handleMessage(Message msg) {
10     switch (msg.what) {
11         case Activity01.REFRESH:
12         mGameView.invalidate(); // 刷新界面
13         break;
14     }
15 
16     super.handleMessage(msg);
17     }
18 };
19 
20 class GameThread implements Runnable {
21     public void run() {
22         while (!Thread.currentThread().isInterrupted()) {
23             Message message = new Message();
24             message.what = Activity01.REFRESH;
25             // 发送消息
26             Activity01.this.myHandler.sendMessage(message);
27             try {
28             Thread.sleep(100);
29             } catch (InterruptedException e) {
30             Thread.currentThread().interrupt();
31             }
32         }
33     }
34 }
复制代码

二、使用postInvalidate()刷新界面


复制代码
 1 使用postInvalidate则比较简单,不需要handler,直接在工作线程中调用postInvalidate即可。 
 2 
 3 class GameThread implements Runnable {
 4     public void run() {
 5         while (!Thread.currentThread().isInterrupted()) {
 6         try {
 7             Thread.sleep(100);
 8         } catch (InterruptedException e) {
 9         Thread.currentThread().interrupt();
10         }
11 
12         // 使用postInvalidate可以直接在工作线程中更新界面
13         mGameView.postInvalidate();
14         }
15     }
16 }
复制代码
见下面一篇

分析invalidate

首先不管这两个的使用问题,就从源码看。 
先来到android.view.View这个最重要的类。Ctr+F搜索invalidate这个关键词会有大量的检索到的信息,一直向下,会发现标记到的有注释说明和方法内部调用。如果你留心的话就会发现很多关键信息。 
比如:

  • setEnabled改变View可选状态的方法,它内部操作其实就是改变内部Flags标志位状态,刷新内部Drawable显示状态DrawableState,然后就调用invalidate(true),所以invalidate肯定是会导致视图重绘的方法。代码如下。
<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"> <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@RemotableViewMethod</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">setEnabled</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> enabled) {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (enabled == isEnabled()) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span>;

        setFlags(enabled ? ENABLED : DISABLED, ENABLED_MASK);

        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*
         * The View most likely has to change its appearance, so refresh
         * the drawable state.
         */</span>
        refreshDrawableState();

        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Invalidate too, since the default behavior for views is to be</span>
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// be drawn at 50% alpha rather than to change the drawable.</span>
        invalidate(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>);

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (!enabled) {
            cancelPendingInputEvents();
        }
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li></ul>
  • 同样的还有onFocusChanged()setAlphasetScrollIndicators()等也在内部有类似的调用过程,先不管它们真正的操作,光看名字就知道都是改变视图状态的方法。

    最后来到这里invalidate(ture)真正的方法。

<code class="hljs applescript has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">/**
     * This <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">where</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> invalidate() work actually happens. A full invalidate()
     * causes <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> drawing cache <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">to</span> be invalidated, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">but</span> this function can be
     * called <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">with</span> invalidateCache <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">set</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">to</span> <span class="hljs-constant" style="box-sizing: border-box;">false</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">to</span> skip <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">that</span> invalidation step
     * <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> cases <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">that</span> do <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">not</span> need <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">it</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> example, a component <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">that</span> remains <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">at</span>
     * <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> same dimensions <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">with</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> same content).
     *
     * @param invalidateCache Whether <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> drawing cache <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> this view should be
     *            invalidated <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">as</span> well. This <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span> usually <span class="hljs-constant" style="box-sizing: border-box;">true</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> a full
     *            invalidate, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">but</span> may be <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">set</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">to</span> <span class="hljs-constant" style="box-sizing: border-box;">false</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> View's <span class="hljs-property" style="box-sizing: border-box;">contents</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">or</span>
     *            dimensions have <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">not</span> changed.
     */
    void invalidate(<span class="hljs-type" style="box-sizing: border-box;">boolean</span> invalidateCache) {
        invalidateInternal(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, mRight - mLeft, mBottom - mTop, invalidateCache, <span class="hljs-constant" style="box-sizing: border-box;">true</span>);
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li></ul>

先看注释,大概意思就是会使得绘画的缓存(drawing cache)失效,现在没有前后文理解也搞不清楚具体的意思。再看它的参数mRight - mLeft, mBottom - mTop这就是显示范围的宽高。也是和视图显示相关的。 
(坐标原点在屏幕左上角,横为左右值,竖为顶底值)

右-左=当前View的宽显示范围。 
底-高=当前View的高显示范围。

总的来说:invalidate会直接在方法内改变View的显示效果与线程没有明显的关系。 
引用Android官网API的参考文档

Invalidate the whole view. If the view is visible, onDraw(android.graphics.Canvas) will be called at some point in the future.This must be called from a UI thread. To call from a non-UI thread, call postInvalidate(). 
大概翻译:如果当前视图是可见的,onDraw(真正的绘制方法)会未来的某个时刻调用,这个方法必须在UI主线程调用,如果不是主线程需要调用postInvalidate方法。

分析postInvalidate

postInvalidate()方法相对就在View源码中就比较少, 
我检索到的两个方法

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//不带参数 </span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">postInvalidate</span>() {
        postInvalidateDelayed(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>);
    }

    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//带视图范围参数 </span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">postInvalidate</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> left, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> top, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> right, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> bottom) {
        postInvalidateDelayed(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, left, top, right, bottom);
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li></ul>

上面两个方法区别只在于是否带视图信息的参数。最后都的逻辑都是一样的。通过绑定的ViewRootImpl对象发送出消息,参数this就是把自己View对象给发送了出去,这很关键。

<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"> attachInfo<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.mViewRootImpl</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.dispatchInvalidateDelayed</span>(this, delayMilliseconds)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

ViewRootImpl就是当我们视图在子线程更新UI向系统发出异常的类,它的工具就是视图顶级管理类负责管理所有的View对象。

异常长这样:android.view.ViewRootImpl$calledfromwrongthreadexception only 
the original thread

点进去看ViewRootImpl的源码,已经没什么好说的了,就是一个Handler的发送消息方法。把传过来的View对象和int类型的标志位MSG_INVALIDATE打包成Message消息对象,发送延时消息。

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">dispatchInvalidateDelayed</span>(View view, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">long</span> delayMilliseconds) {
        Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
        mHandler.sendMessageDelayed(msg, delayMilliseconds);
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul>

MSG_INVALIDATE这个what标志去找,跳转到ViewRootHandler类

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//省略很多代码</span>
        <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">handleMessage</span>(Message msg) {
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">switch</span> (msg.what) {
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">case</span> MSG_INVALIDATE:
                ((View) msg.obj).invalidate();
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>;
         <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//省略很多代码</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>

真相大白! 
ViewRootHandler的handleMessage处理消息方法就是把Message消息打包的Object强转回View对象再调用invalidate方法。 
转了一大圈postInvalidate最后还是调用了invalidate方法,只是不是在随意线程调用,而是通过ViewRootImpl这个顶级视图检查管理类(The top of a view hierarchy)去负责分发轮询处理,然后在主线程调用View方法,实现View控件的线程安全。

引用Android官网API的参考文档

Cause an invalidate to happen on a subsequent cycle through the event loop. Use this to invalidate the View from a non-UI thread. 
This method can be invoked from outside of the UI thread only when this View is attached to a window. 
大概翻译:通过周期性的轮询使得能够在非UI线程调用得到invalidate方法。

转载: http://blog.csdn.net/Card361401376/article/details/51486372
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值