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()
、setAlpha
、setScrollIndicators()
等也在内部有类似的调用过程,先不管它们真正的操作,光看名字就知道都是改变视图状态的方法。最后来到这里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的参考文档
转载: http://blog.csdn.net/Card361401376/article/details/51486372Cause 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方法。