Android消息处理机制(Handler、Looper、MessageQueue)
mochihanbing
8年前
<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>