在你真的懂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吗?显然会,所以我们应该尽可能避免这种操作。

更多相关文章

  1. android中常见的类(三)
  2. Android线程通信之Handler
  3. android中非阻塞socket通信
  4. Android手掌抑制功能的实现
  5. 【移动开发】Android中异步加载数据(一)Handler + Thread
  6. 探讨android更新UI的几种方法
  7. eclipse paho包对于ActiveMQ持久化订阅者的设置
  8. Android里的Xmpp的理解(消息推送)
  9. Android中的Handler, Looper, MessageQueue和Thread

随机推荐

  1. 主备机房出口切换 python脚本
  2. 描述类成员的重载、全局成员以及命名空间
  3. 命名空间类名三种引用-命名冲突解决-自动
  4. 第7章 0202-面向对象编程基础,学习心得、
  5. 命名空间类三种引用、类导入命名冲突解决
  6. PHP基础知识:类的拦截器和重载以及命名空
  7. PHP类的重载和命名空间(声明|调用|分解与
  8. 航电oj2016-2017 参考代码
  9. Python解释器种类以及特点?详细介绍!
  10. C++入门