Handler 源码解析:nativePollOnce阻塞和nativeWake唤醒

Handler 如何做到阻塞
Android篇:2019初中级Android开发社招面试解答(中)

Handler消息机制组成:

  • Message(消息):需要被传递的消息,消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息。
  • MessageQueue(消息队列):负责消息的存储与管理,负责管理由 Handler发送过来的Message。读取后会自动删除消息,单链表维护,插入和删除上有优势。在其next()方法中会无限循环,不断判断是否有消息,有就返回这条消息并移除。
  • Handler(消息处理器):负责Message的发送及处理。主要向消息池发送各种消息事件(Handler.sendMessage())和处理相应消息事件(Handler.handleMessage()),按照先进先出执行,内部使用的是单链表的结构。
  • Looper(消息池/循环机制):负责关联线程以及消息的分发,在该线程下从 MessageQueue获取 Message,分发给Handler,Looper创建的时候会创建一个 MessageQueue,调用loop()方法的时候消息循环开始,其中会不断调用messageQueue的next()方法,当有消息就处理,否则阻塞在messageQueue的next()方法中。当Looper的quit()被调用的时候会调用messageQueue的quit(),此时next()会返回null,然后loop()方法也就跟着退出。
具体流程图:
Handler 源码解析:nativePollOnce阻塞和nativeWake唤醒_第1张图片
Handler工作流程
  • App启动时创建一个主线程(UI),接着UI线程会创建一个Looper,同时也会在在Looper内部创建一个消息队列。而在创键Handler的时候取出当前线程的Looper,并通过该Looper对象获得消息队列(MessageQueue),然后Handler在子线程中通过MessageQueue.enqueueMessage在消息队列中添加一条Message。
  • 通过Looper.loop() 开启消息循环不断轮询调用 MessageQueue.next(),取得对应的Message并且通过Handler.dispatchMessage传递给Handler,最终调用Handler.handlerMessage处理消息。

源码分析:

  1. 插入消息
    MessageQueue.enqueueMessage:Handler调用sendMessage()发送消息,而Handler内部通过Looper对象得到MessageQueue对象后又调用MessageQueue.enqueueMessage方法。
boolean enqueueMessage(Message msg, long when) {
            ...

            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;//指示next()是否被阻止在pollOnce()中以非零超时等待。如果阻塞,则需要唤醒looper
            } 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.
                /*插入队列中间。 通常,除非队列的开头有障碍并且消息是队列中最早的
                  异步消息,否则我们不必唤醒事件队列。(队列中消息不为空,并且next()也没有阻塞)*/
                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;
            }

            // 如果looper阻塞/休眠中,则唤醒looper循环机制处理消息
            if (needWake) {
                nativeWake(mPtr);//唤醒
            }
        }
        return true;
    }

调用nativeWake唤醒(这部分内容出自头部连接,详细源码分析可看前辈的)

//android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr){
  NativeMessageQueue* nativeMessageQueue = reinterpret_cast(ptr);
  nativeMessageQueue->wake();
}

void NativeMessageQueue::wake(){
    mLooper->wake();
}

void Looper::wake(){
    ...
    //往mWakeEventFd 中write 1,用以唤醒 looper
    ssize_t mWrite = TEMP_FAILURE_READY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
}

既然有写入消息,那必定要把消息处理掉,所以唤醒了epoll_wait(),然后继续方法调动awoken(),这个方法就是将之前写入的1读出,表示消费这个事件

void Looper::awaken(){
    ...
    //读取头部消息,静默处理掉
    TEMP_FAILURE_READY(read(mWakeEventFd, &counter, sizeof(uint64_t)));
}

随后在Java 层的next()@MessageQueue 就被唤醒,读取在enqueueMessage()@MessageQueue 插在队头的消息进行处理

  1. Looper循环读取消息
    当looper循环机制在MessageQueue的next()读取消息时发现消息队列中没有消息时,就会调用nativePollOnce(ptr, nextPollTimeoutMillis);将next()阻塞在PollOnce中。looper也就进入了休眠期。
@UnsupportedAppUsage
    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();
            }
            //就是在这里根据nextPollTimeoutMillis判断是否要阻塞
            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) {//队列中拿到的消息不为null
                    if (now < msg.when) {
                        // 下一条消息尚未准备好。 设置超时以使其在准备就绪时醒来。
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 正常返回处理
                        ...
                } else {
                    // 队列中没有消息,标记阻塞looper循环进入休眠
                    nextPollTimeoutMillis = -1;
                }

                // 现在已处理所有挂起的消息,处理退出消息。
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // 空闲句柄仅在队列为空或将来要处理队列中的第一条消息(可能是屏障)时才运行。
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                ...
            }

            ...

            // 将空闲处理程序计数重置为0,这样我们就不会再次运行它们。
            pendingIdleHandlerCount = 0;

            // 在调用空闲处理程序时,可能已经传递了一条新消息,
            //因此返回并再次查找未处理消息,而无需等待。
            nextPollTimeoutMillis = 0;
        }
    }

你可能感兴趣的:(Handler 源码解析:nativePollOnce阻塞和nativeWake唤醒)