深入理解handler

750 阅读3分钟

1 简述

Handler 是Android官方提供跨线程通信工具。
要深入彻底弄懂它,我们需要结合Java与native两个层面进行理解。

2 Java层面

在Java层面,我前面写过一篇handler源码分析的文章,现在看来特别粗糙,嘿~
在这一层面个人认为需要理清几个关键点:

  • Looper
  • MessageQueue
  • Handler类

如上面这个图所画,抽象的理解Handler的发消息取消息,就是将msg放到MQ,然后不断的去读取MQ,如果有消息就分发到对应线程的handleMessage
但里面的细节远不止如此......

// 添加消息
boolean enqueueMessage(Message msg, long when) {
    // 没有handler引用,抛出异常
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }

    // 锁住,线程安全
    synchronized (this) {
        // 已经被使用的消息不能再次放入队列
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        // 正在退出
        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; // true 通常没有消息时是处于阻塞状态的,需要去唤醒
        } 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.
            // 通常不需要通过epoll机制去唤醒阻塞,除非队列头部是同步屏障或最早的是异步消息
            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.
        // 是否需要唤醒
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

// 取消息
@UnsupportedAppUsage
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.
    // 原生nativemessagequeue的指针
    final long ptr = mPtr;
    // native层没有初始化消息队列就退出
    if (ptr == 0) {
        return null;
    }

    // 阻塞的IdleHandler数目
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    // 下次阻塞的时间
    int nextPollTimeoutMillis = 0;
    // 无限循环
    for (;;) {
        // 需要阻塞一定时间
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        // 阻塞开始
        nativePollOnce(ptr, nextPollTimeoutMillis);

        // 锁住,多线程安全
        synchronized (this) {
            // 开始尝试去获取下一条消息
            // 当前时间
            final long now = SystemClock.uptimeMillis();
            // 记录前一结点
            Message prevMsg = null;
            // 消息队列,单向链表
            Message msg = mMessages;
            // handler中有3种消息,普通同步消息、异步消息、同步屏障
            // 如果遇到同步屏障,那么所有普通同步消息都会被屏蔽掉,执行后面的异步消息
            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) {
                // 如果异步消息的执行时间在后面
                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;
            }

            // 在处理了所有挂起的消息之后,是否处理退出
            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();
            }
            // 没有idle消息需要处理了
            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;

        // 当调用一个空闲处理程序时,一个新的消息可能已经被发送,所以返回并再次查找一个挂起的消息,而不是等待。
        nextPollTimeoutMillis = 0;
    }
}

我们可以具体从放消息、取消息方法看出,消息队列(单链表)上的消息大致分为三种类型:

  • 普通同步消息(需要立即发出)
  • 异步消息(在未来某个时间发出)
  • 同步屏障(过滤掉同步消息,找到最近的异步消息)

所有这些类型的msg都以时间为序排列在单链表上,具体的处理逻辑代码已经很清楚了。
另外,在链表上没有需要立即执行的消息时,还会去检查是否有IdleHandler需要处理,这个一般用来做UI的刷新,避免卡顿可能。
但我们仍然无法从这里得知,handler在空闲的时候是如何实现阻塞等待?又如何在有消息进MQ时被唤醒?

  • nativePollOnce(实现阻塞等待)
  • nativeWake(唤醒去读队列中的消息)

这两个实现阻塞和唤醒的方法是在native层实现!

3 native层面

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
// ...

int Looper::pollInner(int timeoutMillis) {

    // 关键 epoll机制
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // Check for poll error.
    // 出错
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        ALOGW("Poll failed with an unexpected error: %s", strerror(errno));
        result = POLL_ERROR;
        goto Done;
    }

    // Check for poll timeout.
    // 超时
    if (eventCount == 0) {
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - timeout", this);
#endif
        result = POLL_TIMEOUT;
        goto Done;
    }

    return result;
}

// nativeWake
void Looper::wake() {
    uint64_t inc = 1;
    // 向fd写数据,上面的epoll_wait就会有返回
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
}

native层初始化MQ和Looper的代码我们就略过,和Java层的类似。
那么从上面代码可以看出,Handler利用epoll机制实现了阻塞与唤醒。

epoll_wait() :出错返回-1,超时返回0,有数据会返回事件数。

当没有事件时(也就是在阻塞期间,没有msg被插入到消息队列),会一直等待,直到达到我们设定的超时时间。
有事件时,向fd里写一个数据(uint64_t),epoll唤醒线程(即退出阻塞状态),在Java层又开始处理消息队列中消息。
下面用一个抽象的图来表达。
handler.png

具体epoll是如何实现阻塞监听的,大家可以去深入研究一下它的实现,大致是epoll_ctl维护等待队列,再调用epoll_wait阻塞进程,这里就不扩展了。

4 最后

理解了Handler Java层与native的协同,就基本理解了handler的整体实现和作用。最后,我们再来回答几个问题。

Handler的Looper死循环为什么不会造成ANR?

ANR是指应用程序未响应,Android系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成ANR。一般有以下四种:

  • Service Timeout
  • BroadcastQueue Timeout
  • ContentProvider Timeout
  • InputDispatching Timeout

所以真正造成ANR的,不是Looper循环本身,而是在某个消息处理的时候操作时间过长。
另外Looper在没有消息需要处理时,会陷入阻塞。

Handler如何保证并发安全?

// 添加消息
boolean enqueueMessage(Message msg, long when) {
    ...

    // 锁住,线程安全
    synchronized (this) {
        ...
    }
    return true;
}

Message next() {
    ...
    // 无限循环
    for (;;) {
        ...

        // 锁住,多线程安全
        synchronized (this) {
            ...
        }
    }
}

在入队和出队方法中都是用了Synchronized锁保证安全。