你真的懂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,Handler消息机制,MessageQueue)