你真的懂Android(安卓)Handler吗?(三)
在你真的懂Android Handler吗?(一)和你真的懂Android Handler吗?(二)这两篇文章中我们了解了Handler是如何跟线程绑定的,也知道了Handler中消息传递的机制是通过无限for循环。但是我们还遗留了一些问题,比如MessageQueue中的数据结构是怎样的?是一个先进先出的队列吗?在主线程中使用了无限for循环,为什么消息队列为空时没有引起ANR呢?这些问题需要我们通过分析MessageQueue源码来寻找答案。
对于第一个问题,MessageQueue是一个先进先出的队列吗?其实并不是,而是一个Message的单链表,MessageQueue中有一个mMessages属性,它是一个Message类型的对象,它指向了单链表的头。Message类中有一个next属性,它也是Message类型的,它指向一个message的下一个节点。下面是MessageQueue和Message的部分源码:
public final class MessageQueue { ...... //消息队列的头 Message mMessages; //标示next()方法是否被阻塞在pollOnce()处一段时间,具体时间由non-zero-timeout决定 // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout. private boolean mBlocked; ......}public final class Message implements Parcelable { public int what; public int arg1; public int arg2; public Object obj; ...... /** * Optional field indicating the uid that sent the message. This is * only valid for messages posted by a {@link Messenger}; otherwise, * it will be -1. */ public int sendingUid = -1; ...... /*package*/ int flags; /*package*/ long when; /*package*/ Bundle data; /*package*/ Handler target; /*package*/ Runnable callback; // sometimes we store linked lists of these things /*package*/ Message next;}
下面我们通过分析向MessageQueue中插入消息和从MessageQueue中获取消息的两个方法来探索为什么MessageQueue不是一个先进先出的队列,以及在哪一步会发生阻塞。
先看插入消息的代码,向MessageQueue中插入代码要调用enqueueMessage()方法,下面是源码:
boolean enqueueMessage(Message msg, long when) { //检查msg的有效性 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) { //如果当前messageQueue已经退出了,就回收消息,同时返回false 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) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; //如果当前消息队列证被阻塞,那么就需要唤醒它 needWake = mBlocked; } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. //只有当当前消息队列被阻塞了,而且列表头部消息被消息屏障阻塞(消息屏障也是一个消息,但是它的target为空),而且要插入的消息是个异步消息(即它不会被消息屏障阻塞)时才需要唤醒 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. //如果需要唤醒消息队列就调用nativeWake()方法 if (needWake) { nativeWake(mPtr); } } return true; }
通过enqueueMessage()方法可以看到,Handler发送的消息并不是插在链表最后的,而是按消息应该被处理的时间顺序插入到链表中,所以MessageQueue并不是真正的队列。
而且通过这个方法我们知道在向MessageQueue中插入消息时会根据需要唤醒MessageQueue取消息的操作,这个操作是通过调用nativeWate()方法实现的,具体逻辑要看native代码,我们暂时先不考虑。
下面我们看一下从MessageQueue中取消息的代码,看看哪一步操作会被阻塞。取消息调用的是MessageQueue的next()方法,以下是源码:
Message next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. final long ptr = mPtr; if (ptr == 0) { return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration //下一次阻塞的超时时间 int nextPollTimeoutMillis = 0; //下面是获取消息的操作,它是在无限for循环中执行的 for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } //这一步是调用的native层的代码,有可能会被阻塞,nextPollTimeoutMillis表示最多阻塞多长时间,0-不阻塞,马上返回,-1表示无限阻塞,直到被唤醒 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; //如果当前头节点被屏障阻塞了,就寻找下一个异步消息 if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { //如果找到了异步消息,而且还没有到答该消息需要执行的时间,就计算还需要多长时间t,然后进行下一轮循环,在下一轮循环中将会被阻塞时间t if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { //如果找到了要执行的消息,就返回该消息,同时将该消息从链表中移除 // Got a message. 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 { //如果链表中没有要执行的消息,就设置无限等待,然后进入下一个循环 // No more messages. nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); return null; } // If first time idle, then get the number of idlers to run. // Idle handles only run if the queue is empty or if the first message // in the queue (possibly a barrier) is due to be handled in the future. if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the idle handlers. // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0; } }
从上面的代码可以看到,取消息的操作是在一个无限for循环中进行的,每次循环中都会先调用nativePollOnce(long ptr, int nextPollTimeOutMillis)方法,这个方法可能会被会被阻塞,阻塞多长时间由nextPollTimeoutMillis参数决定,0表示不阻塞,-1表示无限阻塞,直到被唤醒(被唤醒的操作也是在native进行的)。当这个方法执行完毕就会从消息链表中寻找消息,消息链表的头是由mMessages标记的,所以我们从mMessages开始依次查找,如果找到了一条消息,而且这条消息需要马上执行,就返回该消息,同时将该消息从列表中删除,如果找到的消息需要过一段时间再执行,那么就会计算还需要多长时间(假设时长为t毫秒)才能执行,然后开始下一轮循环,再次调用nativePollOnce()方法,等待t毫秒,然后再返回该消息。如果消息队列为空,那么进入下一轮循环,再次调用nativePollOnce()方法,此时该方法会一直被阻塞,直到它被唤醒。
nativePollOnce()方法被唤醒的具体逻辑是在native层的,但是我们通过前面的enqueueMessage()方法可以知道,唤醒该nativePollOnce的一个入口就是enqueueMessage()方法。当我们向消息队列中插入消息时,如果消息队列被阻塞了,就需要调用native层的方法唤醒它。
至此我们知道了,无限Looper中的无限for循环中的代码不是一直执行的,而是会在native层被阻塞,这大概也是为什么消息列表为空时没有引起ANR的原因。
这里又引出一个新问题,如果我们在Handler的消息处理中执行长时间操作会引起ANR吗?显然会,所以我们应该尽可能避免这种操作。
更多相关文章
- android中常见的类(三)
- Android线程通信之Handler
- android中非阻塞socket通信
- Android手掌抑制功能的实现
- 【移动开发】Android中异步加载数据(一)Handler + Thread
- 探讨android更新UI的几种方法
- eclipse paho包对于ActiveMQ持久化订阅者的设置
- Android里的Xmpp的理解(消息推送)
- Android中的Handler, Looper, MessageQueue和Thread