android 异步通信机制 Handler 的分析与运用

r4047xgv 4年前
   <p>当我们应用程序启动时,Android系统就会创建一个主线程即UI线程,在这个UI线程中进行对UI控件的管理,如页面的刷新或者事件的响应等过程。同时Android规定在UI主线程不能进行耗时操作,否则会出现ANR现象,对此,我们一般是通过开启子线程来进行耗时操作,在子线程中通常会涉及到页面的刷新问题,这就是如何在子线程进行UI更新,对于这个问题,我们一般通过异步线程通信机制中的Handler来解决,接下来我们就来分析一下Handler机制。</p>    <h3>常规用法</h3>    <pre>  <code class="language-java">public class MainActivity extends AppCompatActivity {        @BindView(R.id.execute)      Button execute;        @BindView(R.id.text)      TextView text;        //处理子线程发过来的消息      Handler handler = new Handler() {          @Override          public void handleMessage(Message msg) {              super.handleMessage(msg);              if (msg.what == 1) {                  text.setText("obj:" + msg.obj.toString() + "\nwhat: " + msg.what + "\narg1: " + msg.arg1);              }          }      };        @Override      public void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);          ButterKnife.bind(this);            //子线程发送消息          new Thread(new Runnable() {              @Override              public void run() {                  Message message = handler.obtainMessage();                  message.what = 1;                  message.obj = "子线程更新UI操作";                  handler.sendMessage(message);              }          }).start();        }    }</code></pre>    <p>以上代码就是我们一般会使用到的,子线程通过Message,给主线程发送消息进行UI操作,接下来我们就一步一步进行深究,看看android是如何实现子线程和主线程如何交互的。</p>    <p>首先,我们在主线程中开启一个子线程,我们用了以下方式:</p>    <pre>  <code class="language-java">new Thread(new Runnable() {                @Override              public void run() {                 //处理事件              }          }).start();</code></pre>    <p>开启一个线程,通常有两种方式:</p>    <ol>     <li>继承Thread类,覆盖run方法</li>     <li>实现runnable接口,实现run方法</li>    </ol>    <p>对于第一种方法 <strong>继承Thread类,覆盖run方法</strong> ,我们查看源码就可以知道,最终还是实现runnable接口,所以没有多大的区别。</p>    <pre>  <code class="language-java">public  class Thread implements Runnable {      //...  }</code></pre>    <p>回归正题:</p>    <pre>  <code class="language-java">Message message = handler.obtainMessage();  message.what = 1;  message.obj = "子线程更新UI操作";  handler.sendMessage(message);</code></pre>    <p>我们在run方法中进行发送消息,对于第一行我们获得一个消息是通过</p>    <p>handler.obtainMessage();</p>    <p>而不是通过</p>    <p>Message message =new Message();</p>    <p>这两者有什么区别呢?还是来进入到源码中一窥究竟吧!我们首先进入Handler类中,进行查看</p>    <pre>  <code class="language-java">public final Message obtainMessage()      {          return Message.obtain(this);      }</code></pre>    <p>继续进入</p>    <pre>  <code class="language-java">public static Message obtain(Handler h) {          Message m = obtain();          m.target = h;            return m;      }</code></pre>    <p>最终来到了Message类中</p>    <pre>  <code class="language-java">/**       * Return a new Message instance from the global pool. Allows us to       * avoid allocating new objects in many cases.       */      public static Message obtain() {          synchronized (sPoolSync) {              if (sPool != null) {                  Message m = sPool;                  sPool = m.next;                  m.next = null;                  m.flags = 0; // clear in-use flag                  sPoolSize--;                  return m;              }          }          return new Message();      }</code></pre>    <p>我们仔细观察一下sPool ,这个sPool 是什么东西呢?pool是池的意思,线程有线程池,那么我们也可以认为Message也有一个对象池,我们分析一下源码可以得知:</p>    <p>如果Message对象池中还存在message的话,我们直接使用message,而不是创建一个新的Message</p>    <p>接下来就是对message进行一些常规的设置,如要传递的消息内容之类的,最后进行消息 的发送。</p>    <p>我们进入到消息的最后一步源码中进行查看:</p>    <p>handler.sendMessage(message);</p>    <p>会调用sendMessageDelayed方法</p>    <pre>  <code class="language-java">//Handler类    public final boolean sendMessage(Message msg)      {          return sendMessageDelayed(msg, 0);      }</code></pre>    <p>继续深入查看</p>    <pre>  <code class="language-java">//Handler.java    public final boolean sendMessageDelayed(Message msg, long delayMillis)      {          if (delayMillis < 0) {              delayMillis = 0;          }          //定时发送消息          return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);      }</code></pre>    <p>如果我们设置了延时时间,那么会计算相应的发送时间,当前时间加上延时就是最终的消息发送时间。</p>    <pre>  <code class="language-java">//Handler.java 定时发送消息  public boolean sendMessageAtTime(Message msg, long uptimeMillis) {          //消息队列          MessageQueue queue = mQueue;          if (queue == null) {              RuntimeException e = new RuntimeException(                      this + " sendMessageAtTime() called with no mQueue");              Log.w("Looper", e.getMessage(), e);              return false;          }          //将消息插入队列中,等待处理          return enqueueMessage(queue, msg, uptimeMillis);      }</code></pre>    <p>我们来看看最后一步,将消息插入队里中是如何实现的。</p>    <pre>  <code class="language-java">//消息入队操作  private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {            //msg.target实际上是Handler          msg.target = this;            //异步          if (mAsynchronous) {              msg.setAsynchronous(true);          }            //消息入队          return queue.enqueueMessage(msg, uptimeMillis);      }</code></pre>    <p>我们来看看大头,消息是如何入队的。</p>    <pre>  <code class="language-java">//MessageQueue.java 消息入队,队列的实现其实单链表的插入和删除操作  boolean enqueueMessage(Message msg, long when) {            //指的是Handler          if (msg.target == null) {              throw new IllegalArgumentException("Message must have a target.");          }            //如果消息正在使用中          if (msg.isInUse()) {              throw new IllegalStateException(msg + " This message is already in use.");          }            synchronized (this) {              if (mQuitting) {                  IllegalStateException e = new IllegalStateException(                          msg.target + " sending message to a Handler on a dead thread");                  Log.w(TAG, e.getMessage(), e);                  msg.recycle();                  return false;              }                msg.markInUse();              msg.when = when;              Message p = mMessages;              boolean needWake;              if (p == null || when == 0 || when < p.when) {                  msg.next = p;                  mMessages = msg;                  needWake = mBlocked;              } else {                  needWake = mBlocked && p.target == null && msg.isAsynchronous();                  Message prev;                  for (;;) {                      prev = p;                      p = p.next;                      if (p == null || when < p.when) {                          break;                      }                      if (needWake && p.isAsynchronous()) {                          needWake = false;                      }                  }                  msg.next = p; // invariant: p == prev.next                  prev.next = msg;              }                // We can assume mPtr != 0 because mQuitting is false.              if (needWake) {                  nativeWake(mPtr);              }          }          return true;      }</code></pre>    <p>当Handler将消息插入到消息队列后,那么重要的问题来了,子线程是如何和主线程通信的呢?按道理讲,既然可以将插入到队列中,那么肯定有一个东西可以从消息队列中去消息然后进行处理,对于这个东西,就是有Looper来承担了。</p>    <p>我们首先来看下Looper这个类:</p>    <pre>  <code class="language-java">public final class Looper {          // sThreadLocal.get() will return null unless you've called prepare().      //用于存放当前线程的looper对象      static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();        //主线程的Looper也就是UI线程Looper      private static Looper sMainLooper;  // guarded by Looper.class        //当前线程的消息队列      final MessageQueue mQueue;        //当前线程      final Thread mThread;        //...  }</code></pre>    <p>我们对Looper这个类进行了简单的介绍,对于消息的获取并处理我们得进入到主线程中即ActivityThread.java类中去</p>    <pre>  <code class="language-java">public static void main(String[] args) {          //省略部分代码...          Looper.prepareMainLooper(); ------------------(1)            ActivityThread thread = new ActivityThread();          thread.attach(false);            if (sMainThreadHandler == null) {              sMainThreadHandler = thread.getHandler();          }            if (false) {              Looper.myLooper().setMessageLogging(new                      LogPrinter(Log.DEBUG, "ActivityThread"));          }            //省略部分代码...          Looper.loop(); ------------------------(2)            throw new RuntimeException("Main thread loop unexpectedly exited");      }</code></pre>    <p>对于 <strong>Looper.prepareMainLooper()</strong> 我们进行分析看看,到底是什么?</p>    <pre>  <code class="language-java">public static void prepareMainLooper() {          //不允许退出          prepare(false);          synchronized (Looper.class) {              //一个线程只能有一个Looper对象              if (sMainLooper != null) {                  throw new IllegalStateException("The main Looper has already been prepared.");              }                //主线程的looper              sMainLooper = myLooper();          }      }</code></pre>    <p>对于myLoop()是什么东东?</p>    <pre>  <code class="language-java">/**       * Return the Looper object associated with the current thread.  Returns       * null if the calling thread is not associated with a Looper.       */      public static @Nullable Looper myLooper() {          return sThreadLocal.get();      }</code></pre>    <p>是我们一开始介绍的looper类中的相关变量,也就是存储Looper对象的东西,类似于一个容器。这里是取的Looper对象,那么我们在哪里进行存呢?我们进入到prepare方法中:</p>    <pre>  <code class="language-java">//保存当前线程的Looper对象   private static void prepare(boolean quitAllowed) {            //一个线程只允许一个Looper对象          if (sThreadLocal.get() != null) {              throw new RuntimeException("Only one Looper may be created per thread");          }            //存入Looper对象          sThreadLocal.set(new Looper(quitAllowed));      }</code></pre>    <p>接下来我们看一下 <strong>Looper.loop()</strong> 的方法:</p>    <pre>  <code class="language-java">/**       * Run the message queue in this thread. Be sure to call       * {@link #quit()} to end the loop.       */      public static void loop() {            //获取当前线程的Looper对象          final Looper me = myLooper();            //主线程中不需要手动调用Looper.prepare()方法,          //当我们使用子线程时需要手动调用Looper.prepare()方法,否则会报异常。          if (me == null) {              throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");          }            //当前线程中的消息队列          final MessageQueue queue = me.mQueue;            //省略部分代码...            //死循环,不断处理消息          for (;;) {              Message msg = queue.next(); // might block              if (msg == null) {                  // No message indicates that the message queue is quitting.                  return;              }                 //省略部分代码...              try {                  //msg.target就是Handler,Handler处理消息                  msg.target.dispatchMessage(msg);              } finally {                  if (traceTag != 0) {                      Trace.traceEnd(traceTag);                  }              }                //省略部分代码...                //消息回收              msg.recycleUnchecked();          }      }</code></pre>    <p>Looper.loop()其实就是不断的从队列中获取消息,然后进行处理。</p>    <p>在上面方法中,有一行代码:msg.target.dispatchMessage(msg);我们进入内部去详细查看一下实现:</p>    <pre>  <code class="language-java">//Handler.java 分发消息  /**       * Handle system messages here.       */      public void dispatchMessage(Message msg) {            // msg.callback== Runnable callback;          if (msg.callback != null) {              //如果有runnable,那么则实现它的run方法里面的内容              handleCallback(msg);          } else {              //否则处理handleMessage方法              if (mCallback != null) {                  if (mCallback.handleMessage(msg)) {                      return;                  }              }              handleMessage(msg);          }      }</code></pre>    <p>handleCallback(msg);方法实现</p>    <pre>  <code class="language-java">private static void handleCallback(Message message) {          //msg.callback== Runnable callback;          message.callback.run();      }</code></pre>    <p>handleMessage方法实现</p>    <pre>  <code class="language-java">public interface Callback {          public boolean handleMessage(Message msg);      }</code></pre>    <p>对于上面那个方法,其实就是我们在主线程中实现的方法:</p>    <pre>  <code class="language-java">//消息处理  Handler handler = new Handler() {          @Override          public void handleMessage(Message msg) {              super.handleMessage(msg);              if (msg.what == 1) {                  text.setText("obj:" + msg.obj.toString() + "\nwhat: " + msg.what + "\narg1: " + msg.arg1);              }          }      };</code></pre>    <p>到此,基本上就已经分析了大概,不过,我们在实际的开发过程中有时候会碰到这个问题:</p>    <pre>  <code class="language-java">Can't create handler inside thread that has not called Looper.prepare()</code></pre>    <p><img src="https://simg.open-open.com/show/70f3fde951ed86049c315653c44056f3.png"></p>    <p>而我们的代码是如何写的呢?</p>    <pre>  <code class="language-java">//自定义一个Thread继承自Thread  private class MyThread extends Thread {          private Handler myThreadHandler;            @Override          public void run() {                myThreadHandler = new Handler() {                  @Override                  public void handleMessage(Message msg) {                      super.handleMessage(msg);                      if (msg.what == 1) {                        //todo...                      }                  }              };                  Message message = new Message();              message.what = 1;              message.obj = "MyThread Message Handler";              myThreadHandler.sendMessage(message);            }      }</code></pre>    <p>上述代码很简单,就是自定义一个Thread,然后申明一个Handler来使用,然后通过下面代码进行线程通信:</p>    <pre>  <code class="language-java">myThreadBtn.setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                  new MyThread().start();              }          });</code></pre>    <p>为什么上面简单的代码会出现这个问题呢?而我们在Activity中申明的Handler就可以直接用,而不会出现上述的error?其实,我们在UI主线程中使用Handler时已经调用过了Looper.prepare()和Looper.loop(),我们返回到上面的 <strong>ActivityThread.java</strong> 类中的main方法处,我们可以发现,其实UI主线程已经调用过了,</p>    <pre>  <code class="language-java">Looper.prepareMainLooper();   //...   Looper.loop();</code></pre>    <p>而在我们子线程却需要我们自己手动调用一下,知道了原因所在,我们来修改一下,再次运行,即可得出正确的答案。</p>    <pre>  <code class="language-java">private class MyThread extends Thread {          private Handler myThreadHandler;            @Override          public void run() {                //注意此处的prepare方法              Looper.prepare();                myThreadHandler = new Handler() {                  @Override                  public void handleMessage(Message msg) {                      super.handleMessage(msg);                      if (msg.what == 1) {                       //todo...                      }                  }              };                  Message message = new Message();              message.what = 1;              message.obj = "MyThread Message Handler";              myThreadHandler.sendMessage(message);                //注意此处的loop方法              Looper.loop();            }      }</code></pre>    <p>以上修改内容即可得出正确的答案,接下来我们来总结一下:</p>    <p>为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到了Handler的handleMessage()方法中,这时的handleMessage()方法已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。</p>    <p>除了通过Handler的sendMessage方法来进行子线程和主线程进行通信外,我们还可以通过以下的方法来达到相同的效果。</p>    <p>1、handler.post</p>    <p>我们来仔细分析一下源代码进行说明。</p>    <pre>  <code class="language-java">public final boolean post(Runnable r)      {         return  sendMessageDelayed(getPostMessage(r), 0);      }</code></pre>    <p>调用handler.post方法,将runnable参数转化为一条message进行发送的。接着我们进入getPostMessage(r)中进行分析看看。</p>    <pre>  <code class="language-java">private static Message getPostMessage(Runnable r) {          Message m = Message.obtain();          m.callback = r;          return m;      }</code></pre>    <p>将传递进来的runnable参数赋值给Message的callback变量,赋值给它有什么用呢?我们还记不记得在Handler的dispatchMessage时会做一个判断???</p>    <pre>  <code class="language-java">public void dispatchMessage(Message msg) {          //判断Message的callback是否为null,这里的callback就是runnable          if (msg.callback != null) {              handleCallback(msg);          } else {              if (mCallback != null) {                  if (mCallback.handleMessage(msg)) {                      return;                  }              }              handleMessage(msg);          }      }</code></pre>    <p>上面的callback是否为null的判断决定着整个流程,如果callback不等于null的话我们进入handleCallback(msg)方法中一窥究竟。</p>    <pre>  <code class="language-java">private static void handleCallback(Message message) {          message.callback.run();      }</code></pre>    <p>看到没?直接调用了run方法,其中的message.callback就是Runnable,所以它会直接执行run方法。所以对于在子线程中更新UI时使用handler.post 方法时,直接在run方法中进行UI更新如:</p>    <pre>  <code class="language-java">mHandler.post(new Runnable() {              @Override              public void run() {                  handlerText.setText("result: this is post method");              }          });</code></pre>    <p>2、view.post</p>    <p>同理,我们也是进入到源码中进行分析一下:</p>    <pre>  <code class="language-java">/**       * <p>Causes the Runnable to be added to the message queue.       * The runnable will be run on the user interface thread.</p>       *       * @param action The Runnable that will be executed.       *       * @return Returns true if the Runnable was successfully placed in to the       *         message queue.  Returns false on failure, usually because the       *         looper processing the message queue is exiting.       *       * @see #postDelayed       * @see #removeCallbacks       */  public boolean post(Runnable action) {          final AttachInfo attachInfo = mAttachInfo;          if (attachInfo != null) {              return attachInfo.mHandler.post(action);          }            // Postpone the runnable until we know on which thread it needs to run.          // Assume that the runnable will be successfully placed after attach.          getRunQueue().post(action);          return true;      }</code></pre>    <p>通过该方法的注释我们就知道,首先会将runnable放进到Message队列中去,然后在UI主线程中运行,调用handler的post方法,本质的原理都是一样的。</p>    <p>3、runOnUiThread</p>    <p>对于runOnUiThread方法,我们从字面上也可以了解到是在主线程中运行的,我们详细分析一下:</p>    <pre>  <code class="language-java">public final void runOnUiThread(Runnable action) {          if (Thread.currentThread() != mUiThread) {              mHandler.post(action);          } else {              action.run();          }      }</code></pre>    <p>代码很简单,就是先判断一下当前线程是否是UI主线程,是的话,直接运行run方法,不是的话通过Handler来实现。</p>    <p>通过以上四种在子线程中更新UI的方法,其内在的本质都是一样的,都是借助于Handler来实现的,本篇分析了异步通信机制中的Handler,通过本篇的学习与了解,对于实际项目中如果遇到相似的问题的话,我想应该可以迎刃而解了。 <strong>知其然而知所以然!</strong></p>    <p>最后我们来总结一下本篇文章中涉及到的各个对象的意思:</p>    <p>1、MessageQueue</p>    <p>消息队列,它的内部存储了一组数据,以队列的形式向外提供了插入和删除的工作。但是它的内部实现并不是队列,而是 <strong>单链表</strong> 。</p>    <p>2、Looper</p>    <p>会不停检查是否有新的消息,如果有就调用最终消息中的Runnable或者Handler的handleMessage方法。对应提取并处理消息。</p>    <p>3、Handler</p>    <p>Handler的工作主要包含消息的发送和接收过程。消息的发送可以通过post的一系列方法以及send的一系列方法来实现,不过最后都是通过send的一系列方法实现的。对应添加消息和处理线程。</p>    <p>4、Message</p>    <p>封装了需要传递的消息,并且本身可以作为链表的一个节点,方便MessageQueue的存储。</p>    <p>5、ThreadLocal</p>    <p>一个线程内部的数据存储类,通过它可以在指定的线程中储存数据,而其它线程无法获取到。在Looper、AMS中都有使用。</p>    <h3>参考</h3>    <p><a href="/misc/goto?guid=4959746551382746993" rel="nofollow,noindex">1、http://www.jianshu.com/p/94ec12462e4e)</a></p>    <p><a href="/misc/goto?guid=4959746551480320640" rel="nofollow,noindex">2、http://blog.csdn.net/woshiwxw765/article/details/38146185</a></p>    <h3>关于作者</h3>    <p><a href="/misc/goto?guid=4959746551564980846" rel="nofollow,noindex">1. 简书 http://www.jianshu.com/users/18281bdb07ce/latest_articles</a></p>    <p><a href="/misc/goto?guid=4959746551647857647" rel="nofollow,noindex">2. 博客 http://crazyandcoder.github.io/</a></p>    <p><a href="/misc/goto?guid=4959746551731653911" rel="nofollow,noindex">3. github https://github.com/crazyandcoder</a></p>    <p> </p>