| 注册
请输入搜索内容

热门搜索

Java Linux MySQL PHP JavaScript Hibernate jQuery Nginx
mochihanbing
8年前发布

Android消息处理机制(Handler、Looper、MessageQueue)

   <h3><strong>概述</strong></h3>    <p>Android是基础消息驱动的,Android系统为每一个程序维护了一个消息队列( MessageQueue ),我们可以非常简单的往消息队列中插入消息( Handler ),主线程会循环的从队列中取出消息( Looper ),然后交给消息的发送者来执行( Handler ),这样就实现了通过消息来驱动程序的执行。</p>    <p>这篇文章会详情分析android的消息处理机制,为了让理解更加简单,我们将从日常应用中最常见的情景---子线程中返回了数据,通过handler更新UI开始。</p>    <p>大家都知道在子线程中通过handler来更新UI的步骤吧</p>    <ul>     <li>创建handler</li>     <li>用handler发送消息</li>     <li>用handler处理消息(收到消息,更新UI)</li>    </ul>    <p>what? 好像就用handler就完了,说好的Looper、MessageQueue呢?别急,先来看看Handler的创建</p>    <pre>  <code class="language-java">public Handler() {      this(null, false);  }</code></pre>    <p>再来看看调用的这个this()</p>    <pre>  <code class="language-java">public Handler(Callback callback, boolean async) {      .........              mLooper = Looper.myLooper();      if (mLooper == null) { throw new RuntimeException( "Can't create whitout Looper");}      mQueue = mLooper.mQueue;          .......  }</code></pre>    <p>首先通过 Looper 的静态方法获取对应的 looper ,如果为空,直接抛出异常,由此可以看出消息处理确定是需要 looper 的。这里你可能会有疑问,这个looper是怎么来的?</p>    <pre>  <code class="language-java">public static @Nullable Looper myLooper() {      return sThreadLocal.get();}</code></pre>    <pre>  <code class="language-java">public T get() {      Thread t = Thread.currentThread();      ThreadLocalMap map = getMap(t);      if (map != null) {          ThreadLocalMap.Entry e = map.getEntry(this);          if (e != null)              return (T)e.value;      }      return setInitialValue();  }</code></pre>    <p>原来是通过 ThreadLocal 的 get 方法从 ThreadLocalMap 链表中以当前线程作为 key ,取出的 value 就是我们需要的 looper ,这里的ThreadLocal和ThreadLocalMap我们等下再说。</p>    <p>简单点就是线程中维护了一个HashMap用来存取looper,这里你可能又有疑问了,既然是map表,我们都没有做存looper的操作,那取出来肯定是空啊,那为什么我们创建handler的时候没有出现异常呢?</p>    <p>其实,这是因为程序的主线程(UI线程)在启动的时候,就创建了looper并存入了ThreadLocalMap中。</p>    <pre>  <code class="language-java">public static void main(String[] args) {      ......          Looper.prepareMainLooper();      ActivityThread thread = new ActivityThread();      thread.attach(false);      if (sMainThreadHandler == null) {          sMainThreadHandler = thread.getHandler(); }      ........      Looper.loop();}</code></pre>    <p><strong>Looper</strong></p>    <p>来看下looper是怎么创建的</p>    <pre>  <code class="language-java">public static void prepareMainLooper() {    //创建一个looper并存入ThreadlocalMap中      prepare(false);      synchronized (Looper.class) {          if (sMainLooper != null) {              throw new IllegalStateException("The main Looper has already been prepared.");          }        //取出创建好的looper          sMainLooper = myLooper();      }  }</code></pre>    <pre>  <code class="language-java">private static void prepare(boolean quitAllowed) {      if (sThreadLocal.get() != null) {          throw new RuntimeException("Only one Looper may be created per thread");      }        //存入ThreadLocalMap中      sThreadLocal.set(new Looper(quitAllowed));  }</code></pre>    <p>通过 prepare() 方法创建一个looper,在通过 myLooper() 方法取出来。再来看看Looper的构造</p>    <pre>  <code class="language-java">private Looper(boolean quitAllowed) {      //构造一个消息队列,复制给mQueue变量      mQueue = new MessageQueue(quitAllowed);      mThread = Thread.currentThread();  }</code></pre>    <p>看到这里是不是有一点眉目了?构造 looper 的同时构造了 MessageQueue ,构造 Handler 的时候获取当前线程的 looper 同时获取到了 消息队列 ,这样消息机制就联系起来了。</p>    <p><strong>消息循环</strong></p>    <p>现在消息机制的三大主力(Handler、Looper、MessageQueue)都具备了,那handle是怎么发送消息,looper又是怎么取出消息,消息又是怎么被处理的呢?来看看Looper.loop()方法:</p>    <pre>  <code class="language-java">public static void loop() {      final Looper me = myLooper();      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) {                    return;          }          msg.target.dispatchMessage(msg);          .....        }  }</code></pre>    <p>这个函数就是不断的从消息队列 mQueue 中去取下一个要执行的消息,如果消息为空就退出循环 (其实msg永远不会为空,因为Message.next()也是一个for无限循环,里面判断如果msg==null,continue继续下一次循环) ,不为空就调用 target 的 dispatchMessage() 来执行消息, target 是一个 Handler ,也就是发送这个消息的 handler ,在后续消息发送中我们会看到targer是在哪里赋值的。</p>    <p>这个函数最重要的逻辑是 queue.next() ,它是在MessageQueue中实现的,用来取出消息队列中的消息</p>    <pre>  <code class="language-java">Message next() {      final long ptr = mPtr;      if (ptr == 0) {          return null;      }      int pendingIdleHandlerCount = -1; // -1 only during first iteration      //消息队列等待时长      int nextPollTimeoutMillis = 0;      for (;;) {          if (nextPollTimeoutMillis != 0) {              Binder.flushPendingCommands();          }          //这是一个JNI方法,检查消息队列,取出消息并复制给mMessages                  nativePollOnce(ptr, nextPollTimeoutMillis);          synchronized (this) {              final long now = SystemClock.uptimeMillis();              Message prevMsg = null;              Message msg = mMessages;              if (msg != null && msg.target == null) {       .         do {                      prevMsg = msg;                      msg = msg.next;                  } while (msg != null && !msg.isAsynchronous());              }              if (msg != null) {                  if (now < msg.when) {                      //如果当前时间小于消息的执行时间,消息队列等待                      nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);                  } else {                      //否则就返回消息给looper处理                      mBlocked = false;                      if (prevMsg != null) {                          prevMsg.next = msg.next;                      } else {                          mMessages = msg.next;                      }                      msg.next = null;                      if (DEBUG) Log.v(TAG, "Returning message: " + msg);                      msg.markInUse();                      return msg;                  }              } else {                  //  如果消息队列中没有任何就无限等待,直到下一个消息到来,并唤醒消息队列                                  nextPollTimeoutMillis = -1;              }            if (pendingIdleHandlerCount <= 0) {          // No idle handlers to run.  Loop and wait some more.              mBlocked = true;    continue;          }           .......            pendingIdleHandlerCount = 0;          nextPollTimeoutMillis = 0;      }  }</code></pre>    <p>这个函数稍长,我们把它分解来看。</p>    <p>执行下面的JNI函数会检查队列中是否有消息, nextPollTimeoutMillis 表示队列要等待的时间,初始为0,则不需要等待</p>    <pre>  <code class="language-java">nativePollOnce(ptr, nextPollTimeoutMillis);</code></pre>    <p>等这个函数完成返回后,看消息队列中是否有消息</p>    <pre>  <code class="language-java">if (msg != null) {                  if (now < msg.when) {                      //如果当前时间小于消息的执行时间,消息队列等待                      nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);                  } else {                      //否则就返回消息给looper处理                      mBlocked = false;                      if (prevMsg != null) {                          prevMsg.next = msg.next;                      } else {                          mMessages = msg.next;                      }                      msg.next = null;                      if (DEBUG) Log.v(TAG, "Returning message: " + msg);                      msg.markInUse();                      return msg;                  }              }</code></pre>    <p>如果有消息,并且消息的执行时间大于当前时间, nextPollTimeoutMillis 重新赋值,等下一次 nativePollOnce(ptr, nextPollTimeoutMillis); 执行的时候就要等待到执行时间了。</p>    <pre>  <code class="language-java">nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);</code></pre>    <p>否则,就直接把消息返回,交给looper处理</p>    <pre>  <code class="language-java">else {      mMessages = msg.next;      msg.next = null;      if (DEBUG) Log.v(TAG, "Returning message: " + msg);      msg.markInUse();      return msg;    }</code></pre>    <p>如果整个队列中没有任何消息,就进入无限等待状态,</p>    <pre>  <code class="language-java">// 如果消息队列中没有任何就无限等待,直到下一个消息到来,并唤醒消息队列                      nextPollTimeoutMillis = -1;</code></pre>    <p>这里注意一点,所谓的等待是空闲等待,而不是忙等待。就是说如果应用程序注册了 IdleHandler 接口来处理一些事情,那么就会先执行这里 IdleHandler ,因为在执行的过程中可能有新的消息加入队列,所有需要重置等待时长为0,不在等待</p>    <pre>  <code class="language-java">if (pendingIdleHandlerCount <= 0) {          // No idle handlers to run.  Loop and wait some more.              mBlocked = true;            continue;          }           .......            pendingIdleHandlerCount = 0;          nextPollTimeoutMillis = 0;</code></pre>    <p>是不是有点头晕?这一段逻辑的确是消息机制中最复杂的,没明白的话可以多看一遍源码。把这一部分弄懂了,后面消息的发送和处理就比较简单了</p>    <p><strong>消息发送</strong></p>    <p>发送消息的函数有很多,但是最终都是调用的同一个函数</p>    <pre>  <code class="language-java">public final boolean sendMessageDelayed(Message msg, long delayMillis){      if (delayMillis < 0) {          delayMillis = 0;      }      return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);  }</code></pre>    <p>延时时间+当前时间组合成为这个消息的执行时间,然后调用 enqueueMessage</p>    <pre>  <code class="language-java">private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {      msg.target = this;      if (mAsynchronous) {          msg.setAsynchronous(true);      }      return queue.enqueueMessage(msg, uptimeMillis);  }</code></pre>    <p>这里把当前 Handler 赋值给 message的target 变量,还记得 looper.loop() 函数里面如果 MessageQueue.next() 返回了消息就交给 msg.target.dispatchMessage() 处理,这个 target 就是发送消息的 handler 。这就解释了为什么 handler 发送的消息一定会在 handler 中处理。</p>    <p>之后,消息交给了MessageQueue的enqueueMessage()方法处理</p>    <pre>  <code class="language-java">boolean enqueueMessage(Message msg, long when) {        synchronized (this) {          ......                        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;               prev.next = msg;          }          if (needWake) {              nativeWake(mPtr);          }      }      return true;  }</code></pre>    <p>这个函数会根据消息的执行时间,从小到大的顺序把消息插入到消息队列中。如果消息队列处于等待状态的,还需要唤醒消息队列。唤醒的函数 nativeWake(mPtr); 是一个JNI方法,我们后续再说。现在只要知道唤醒后,消息队列又开始循环检查消息就行了。</p>    <p><strong>消息执行</strong></p>    <p>通过上面我们知道了</p>    <ul>     <li>handler发送消息</li>     <li>Message.next()不断检测消息</li>     <li>检测到消息后返回给looper.loop()</li>     <li>交给msg.target.dispatchMessage()处理消息</li>    </ul>    <p>我们也知道了这个target就是发送消息的那个handler,再来看看它是怎么处理消息的</p>    <pre>  <code class="language-java">public void dispatchMessage(Message msg) {      if (msg.callback != null) {          handleCallback(msg);      } else {          if (mCallback != null) {              if (mCallback.handleMessage(msg)) {                  return;              }          }          handleMessage(msg);      }  }</code></pre>    <p>这个函数就很简单了,如果构造 handler 的时候有 callback 就执行callback,否则就执行 handleMessage() ,执行我们自己的逻辑。</p>    <p>好了,我们已经分析完 Handler、Looper、MessageQueue 的创建和它们在消息机制中扮演的角色,认真阅读这篇文章,我相信你会对android的消息机制有一个比较全面的认识。下面我们看看关于消息机制一些比较有意思的问题</p>    <p><strong>既然 Looper.loop()会阻塞主线程,那为什么不会导致ANR?</strong></p>    <p>这个问题可以通过2个方面来说</p>    <p>1.主线程一定也必须要阻塞。android主线程是伴随着程序的开启就启动,退出才结束的。而不管是进程还是线程,对于Linux系统来说都是一段可执行的代码,如果不阻塞,主线程代码执行完毕就结束了,那我们的程序就可能用着用着就退出了。所以android主线程一定是阻塞的。</p>    <p>2.至于为什么不会ANR。先看看会导致ANR的情况</p>    <p>造成ANR的原因一般有两种:</p>    <p>1.当前的事件没有机会得到处理(即主线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)</p>    <p>2.当前的事件正在处理,但没有及时完成</p>    <p>开篇我们说过android系统是基于消息驱动的,不管是点击一个按钮需要获得反馈还是activity生命周期,其实都是通过发送一个消息交给对应handler处理的。所以,主线程阻塞( <strong>其实是消息队列进入等待状态</strong> ),说明没有任何事件(触摸,点击,生命周期切换)发生,没有消息需要处理,当然不会ANR</p>    <p>当消息队列没有消息了,Looper.loop()中的for()死循环会退出吗</p>    <p>阅读这篇文章后,我们知道了,Looper.loop()是肯定不会退出的,不然下一条消息到来了怎么处理呢?没有消息,它就会阻塞在 Message.next() 里面的 nativePollOnce(ptr, nextPollTimeoutMillis); 本地方法里面,即 nextPollTimeoutMillis==-1</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/ea71e5921d83</p>    <p> </p>    
 本文由用户 mochihanbing 自行上传分享,仅供网友学习交流。所有权归原作者,若您的权利被侵害,请联系管理员。
 转载本站原创文章,请注明出处,并保留原始链接、图片水印。
 本站是一个以用户分享为主的开源技术平台,欢迎各类分享!
 本文地址:https://www.open-open.com/lib/view/open1479799532245.html
消息系统 安卓开发 Android开发 移动开发