深入浅出Handler(三)Epoll 机制

1,026 阅读13分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

一、前言

我们都知道写代码时,任何循环都是需要一个跳出条件的.

MessageQueue.java 

Message next() {
    
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
    ...
    }

        ....

    }

对于MessageQueue的next()方法中的for循环

需要提出一个问题:Android主线程会因为这种死循环阻塞,并且消耗性能吗?

答案当然是不会.

那么在这种死循环里到底是如何做到有消息就处理,没消息就等待的呢?

今天咱们就来分析一下Android底层的Epoll机制.

二、Looper.loop

我们先来看一下ActivityThread的main()函数,一切准备就绪后的最后一行代码

Looper.loop();
public static void loop() {
    final Looper me = myLooper();
    ...
    for (;;) {
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}

这个loop方法其实还在轮询MessageQueue里的next()方法.

如果消息池为空,looper还会轮询吗?如果消息池为空,looper不轮询,阻塞的话.那如果突然来了一个消息呢?

让我们带着这个思考进入今天的Epoll机制

三、MessageQueue

首先来看下MessageQueue的初始化

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

private native static long nativeInit();

定义了底层的nativeInit方法

在/frameworks/base/core/jni/android_os_MessageQueue.cpp里我们看到了nativeInit方法的定义

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    //Native层也声明了一个NativeMessageQueue对象---对应Java的MessageQueue
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
    jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }

    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

可以看到java层也用mPtr指针保存了native的MessageQueue的MessageQueue的引用.

那么Native层又是如何初始化MessageQueue的呢?

NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    //Native层也有自己的Looper,一个线程会有一个相应的Looper用来轮询处理消息
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        //如果looper为空,需要先创建一个Looper
        Looper::setForThread(mLooper);
    }
}

此时,Native层的Looper和MessageQueue都已准备就绪.

再来回顾一下MessageQueue的next()方法

Message next() {
    
    //ptr此时又保留了Native层的MessageQueue的引用
    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();
        }

        //调用nativePollOnce等待
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            
           
            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) {
                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();
                    //返回一一个msg给消息机处理
                    return msg;
                }
            } else {
              ....
    }
}

可以看到在next()方法中,比较神奇的方法就是nativePollOnce(ptr, nextPollTimeoutMillis);

它好像能控制消息的到达时间,nativePollOnce执行结束了,这时正好有一个消息可以处理,否则又进入了一次无效循环。

3.1 enqueueMessage

首先我们来看下消息进入队列时的Java层代码

boolean enqueueMessage(Message msg, long when) {
        ...
        
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // 如果p为空,表明消息队列此时没有消息,那么此时投递进来的message将是第一个消息,needWake根据此时mBlocked被赋值
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
           
            //如果p不为空,此时消息队列中还有需要处理的消息,将msg加到队尾
            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;
        }

        // 调用nativeWake函数
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

接下来我们来看下native层的nativeWake函数

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();//调用wake()函数
}


void NativeMessageQueue::wake() {
    mLooper->wake();//调用looper的wake()函数
}

我们再去看下looper的native层代码 xref: /system/core/libutils/Looper.cpp

void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif

    uint64_t inc = 1;
    //可以看到调用了写入了inc 数值为1的write方法
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
        if (errno != EAGAIN) {
            LOG_ALWAYS_FATAL("Could not write wake signal to fd %d: %s",
                    mWakeEventFd, strerror(errno));
        }
    }
}

通过调用write方法向管道写入字符,将管道从等待状态唤醒

这里的wake()方法主要是通过write函数通过mWakeEventFd往管道写入字符inc,其中TEMP_FAILURE_RETRY是一个宏定义,当执行write方法失败后,会不断重复执行,直到执行成功为止.

3.1 nativePollOnce方法

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);
}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    mLooper->pollOnce(timeoutMillis);//Looper的pollOnce函数
    mPollObj = NULL;
    mPollEnv = NULL;

    if (mExceptionObj) {
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    //可以看到死循环
    for (;;) {
        while (mResponseIndex < mResponses.size()) {
            const Response& response = mResponses.itemAt(mResponseIndex++);
            int ident = response.request.ident;
            if (ident >= 0) {
                int fd = response.request.fd;
                int events = response.events;
                void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE
                ALOGD("%p ~ pollOnce - returning signalled identifier %d: "
                        "fd=%d, events=0x%x, data=%p",
                       this, ident, fd, events, data);
#endif
                if (outFd != NULL) *outFd = fd;
                if (outEvents != NULL) *outEvents = events;
                if (outData != NULL) *outData = data;
                return ident;
            }
        }

        if (result != 0) {
        //当result不为0时返回result
#if DEBUG_POLL_AND_WAKE
            ALOGD("%p ~ pollOnce - returning result %d", this, result);
#endif
            if (outFd != NULL) *outFd = 0;
            if (outEvents != NULL) *outEvents = 0;
            if (outData != NULL) *outData = NULL;
            return result;
        }

        //只要result有返回,就能结束循环
        result = pollInner(timeoutMillis);
    }
}

具体看下pollInner方法

要读懂上面的源码,我们就必须深入理解Linux内核的Epoll机制和pipe管道机制.

四、文件描述符(FD=File Descriptor)

Linux系统中,把一切都看做是文件,当进程打开现有的文件或者创建新文件时,内核会向进程返回一个文件描述符,文件描述符就是内核为了高效管理已被打开的文件所创建的索引,用来指向被打开的文件,所有执行I/O操作的系统调用都会通过文件描述符.

维基百科 :文件描述符在形式上是一个非负整数.实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表.当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符.在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开.

五、Pipe(管/管道)

通常情况下,管道有两个口,而Pipe也常用来实现2个进程之间的通信,这两个进程分别位于管道的两端,一端用来发送数据,一端用来接收数据.

我们可以简单用一个Demo来理解这种管道的数据读写

import time
from multiprocessing import Process, Pipe
import os


class WriteProcess(Process):
    def __init__(self, name, pipe):
        Process.__init__(self)
        self.name = name
        self.pipe = pipe

    def run(self):
        # 把多条数据写入队列中
        print("进程的名字:%s,ID:%s;已经启动" % (self.name, os.getpid()))  # os.getpid() 获取进程识别码
        for i in range(1, 6):
            self.pipe.send(i)  # write 进程负责把数据通过管道发送给另外一个进程
            time.sleep(1)  # 休眠一秒钟
        print("进程的名字:%s,ID:%s;已经结束" % (self.name, os.getpid()))


class ReadProcess(Process):
    def __init__(self, name, pipe):
        Process.__init__(self)
        self.name = name
        self.pipe = pipe

    def run(self):
        print("进程的名字:%s,ID:%s;已经启动" % (self.name, os.getpid()))
        while True:
            # recv 函数是一个阻塞的函数
            value = self.pipe.recv()  # 当管道中没有数据,recv()无需接参数
            print(value)


if __name__ == '__main__':
    # pipe()需要创建两个,创建之后得到管道的两端
    p1, p2 = Pipe()
    pw = WriteProcess('write', p1)
    pr = ReadProcess('read', p2)

    # 启动两个进程
    pw.start()
    pr.start()

    # 让父进程等待子进程结束---等待write进程写数据结束
    pw.join()
    # pr进程是一个死循环
    pr.terminate()  # 强制杀死进程
    print("父进程结束")
进程的名字:write,ID:3331;已经启动
进程的名字:read,ID:3332;已经启动
1
2
3
4
5
进程的名字:write,ID:3331;已经结束
父进程结束

5.1 特点

  • 调用pipe函数即可创建一个管道
  • pipe本质是一个伪文件(实为内核缓冲区)
  • 由两个文件描述符引用,一个表示读端,一个表示写端
  • 规定数据从管道的写端流入管道,从读端流出.

5.2 Handler机制中的使用

首先使用pipe创建两个fd:writeFD,readFD.当线程A想要唤醒线程B的时候,就可以往writeFD中写数据,这样线程B阻塞在readFD中就能返回.

也就是说,当文件描述符指向的文件(内核缓冲区)为空时,则线程进行休眠.当另一个线程向缓冲区写入内容时,则将当前线程进行唤醒.

5.3 select与epoll

阻塞I/O模式下,一个线程只能处理一个流的I/O事件,如果想要同时处理多个流,要么多进程(fork),要么多线程(pthread_create),然而这两种方法效率都不高.阻塞模式下,内核对于I/O事件的处理是阻塞或者唤醒,而非阻塞模式下则把I/O事件交给其他对象(select或者poll)处理或者直接忽略.

可以轮询吗?

如果我们不停的把所有流从头到尾询问一遍,又从头开始.这样就可以处理多个流了.但是如果所有的流都没有数据,这样只会白白浪费CPU.

为了避免CPU空转,我们引入了一个代理(select代理/poll代理),这个代理可以同时观察许多流的I/O事件,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中醒来,程序此时轮询一遍所有的流.

如果没有I/O事件产生时,程序就会阻塞在select处,但是有个问题,我们从select那里仅仅只是知道,有I/O事件,却并不知道那是哪几个流(可能一个or多个),程序只能轮询所有的流,找出能读数据或者写数据的流,再进行操作.

使用select代理,我们的轮询复杂度变成了O(n)

六、Epoll

epoll 叫做Event poll,不同于忙轮询和select的无差别轮询,epoll只会把哪个流发生了怎样的I/O事件通知我们,复杂度降到了O(1)

epoll的系统调用依赖这三个函数

在内核中创建epoll实例并返回一个epoll文件描述符
int epoll_create(int size); 
int epoll_create1(int flags);
向epfd(上面创建返回的)对应的epoll实例添加、修改或删除对fd(第三个入参)上事件event的监听
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//等待在epfd上面的事件,事件从events参数中取出
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

epoll内部用红黑树结构管理fd,双向链表结构管理回调的事件.epoll_ctl函数会把传入的fd和event进行存储,并与相应的设备驱动程序建立关系,当相应的事件发生时,就会调用内部的回调函数将事件添加到链表中,最终通知线程唤醒.

epoll_wait得以返回.没有事件发生时,epoll_wait就是挂起状态.

epfd是epoll程序实例的文件描述符或者索引;fd是想要监听的事件对应的描述符,最终读写管道依赖的也是fd.

此时我们再来看下Native层Looper的初始化

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    
    //event系统函数创建一个文件操作符fd,赋值给mWakeEventFd(唤醒事件描述符),后面
    //管道的读写都会在此fd上进行
    mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd: %s",
                        strerror(errno));

    AutoMutex _l(mLock);
    //构造了epoll实例
    rebuildEpollLocked();
}
void Looper::rebuildEpollLocked() {
    
    //如果已经有epoll实例对应的描述符,先重置
    if (mEpollFd >= 0) {
        ...
        close(mEpollFd);
    }

    //创建新的epoll实例和唤醒管道(wake pipe)
    //此处用epoll_create函数创建了实例并将返回赋值给mEpollFd描述符
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    
    ...

    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    
    //IN,监听管道的输入(即write)操作
    eventItem.events = EPOLLIN;
    //唤醒事件fd要由event数据持有,等会儿唤醒时才能进行查找匹配
    eventItem.data.fd = mWakeEventFd;
    //用epoll_ctl函数注册唤醒事件的监听,EPOLL_CTL_ADD表示添加事件的操作标志
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
    ...
}

此时我们再回过头看Looper的pollInner函数

int Looper::pollInner(int timeoutMillis) {

    ...

    //epoll_wait是整个消息机制阻塞的真正机制,阻塞等待同时可以读取管道的通知.
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    ...

    // 返回eventCount大于0,说明timeout未到,就有新的事件写入管道导致提前唤醒
    

    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd) {
        //查找匹配唤醒事件
            if (epollEvents & EPOLLIN) {
            //被唤醒
                awoken();
            } else {
            ...
            }
        }
    }
Done: ;

   ....
void Looper::awoken() {
    ...
    //进行读操作
    TEMP_FAILURE_RETRY(read(mWakeEventFd, &counter, sizeof(uint64_t)));
}

awoken()函数实际上就是被唤醒进行管道缓冲数据的读操作,既然此时被epoll事件驱动唤醒起来进行读操作,那么肯定就是监听到对应的写操作导致的,而写操作就是在wake函数中

void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif

    //往wake pipe中写了一个1,触发唤醒
    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    ...
}

Looper::wake()函数是哪里触发的呢?

xref: /frameworks/base/core/jni/android_os_MessageQueue.cpp

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

在Native的MessageQueue中调用了looper.wake()函数

再往上追溯

MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
    ...

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            //mPtr保存了Native层的MessageQueue的引用
            nativeWake(mPtr);
        }
    }
    return true;
}

在消息入队列后会唤醒Native层的Looper

最后我们再来看下对于需要延迟处理的消息源码内部是如何处理的,是定时器还是周期性轮询还是上述的驱动呢?

先看下Handler发送延时消息的源码

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}


public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    ...
    return enqueueMessage(queue, msg, uptimeMillis);
}

对于带了需要delayMillis时间的delay的消息,并没有将延时时长直接传入,而是与SystemClock.uptimeMillis()进行了一个加法.

这样做的原因是:后续调用底层方法时,如果再进行延时时长的计算,从应用层到底层调用的时间就无法被计算了,这样可以确保精确.

Handler.java
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}


private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

MessageQueue.java
//这里的when就是上面的uptimeMillis
boolean enqueueMessage(Message msg, long when) {

        ...
        //将uptimeMillis赋值给msg.when
        msg.when = when;
        //message的数据结构是一个链表结构
        Message p = mMessages;
        //是否需要唤醒标志
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // 插入消息到队头,如果阻塞就唤醒
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            
            //插入新消息到链表
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                //for循环 这里的消息排列规则 是按照when进行排序的
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // 链表的插入操作
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            //如果需要唤醒,此时进行唤醒
            nativeWake(mPtr);
        }
    }
    return true;
}

至此,message已经入队成功,我们再去看下looper,因为Looper是负责轮询这些消息的.



private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
    //每个Looper对应一个消息队列
    Message msg = me.mQueue.next(); // might block--可能会阻塞
   0);

    ...

    return true;
}


public static void loop() {
    final Looper me = myLooper();//一个线程对应一个Looper
   
    for (;;) {
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}

Looper的loop方法也没有对时间进行处理,最后还是交给了MessageQueue的next()方法,上面我们已经大致分析了一部分代码了.

@UnsupportedAppUsage
Message next() {
    
    //拿到MessageQueue的native层引用
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    ...
    //下一次轮询的超时时间
    int nextPollTimeoutMillis = 0;
    for (;;) {
        
        //调用一个native方法,把超时时间传递进去.

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // 阻塞结束,开始获取并返回msg给Looper
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                ...
            }
            if (msg != null) {
                if (now < msg.when) {
                   
                   //下一个消息时间未到,更新这个阻塞超时时间,下一个for循环会用到这个超时时间
                   //when-now=等待时间
                    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;
                    ...
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }


            ...
}
  • 当通过postDelay发送延时消息后,传入的事件最终会通过nativePollOnce方法进行相应时间的阻塞,用来计算延时,时间到后阻塞结束,通过next方法得到消息对象
  • 在MessageQueu的enqueueMessage方法中会判断新插入的消息时间是否小于队头消息的时间,以决定要不要立即唤醒.即通过nativeWake方法打断超时未到的阻塞.

我们来看下传递的这个超时时间到native层是如何处理的. xref: /frameworks/base/core/jni/android_os_MessageQueue.cpp

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);
}

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    //调用了looper的pollOnce
    mLooper->pollOnce(timeoutMillis);
    mPollObj = NULL;
    mPollEnv = NULL;

    if (mExceptionObj) {
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
       
       //timeoutMillis被传递到了这里

        result = pollInner(timeoutMillis);
    }
}


int Looper::pollInner(int timeoutMillis) {
#if DEBUG_POLL_AND_WAKE
    
    //这里会根据下一个msg的时间进行修正,如果早于传入的这个timeout,那就以更早的时间进行延时,避免遗漏
    if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
        if (messageTimeoutMillis >= 0
                && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
            timeoutMillis = messageTimeoutMillis;
        }
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - next message in %" PRId64 "ns, adjusted timeout: timeoutMillis=%d",
                this, mNextMessageUptime - now, timeoutMillis);
#endif
    }

    // 初始返回值就是唤醒
    int result = POLL_WAKE;
    ...

    // 这里设置标志位,准备进入idle状态(epoll_waite会挂起释放CPU资源)
    mPolling = true;

    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    //epoll_wait系统调用就是整个消息机制阻塞的真正位置,阻塞等待同时可以读取管道的通知
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // 此时 epoll_wait 返回事件(这里可能是到了timeout了,也可能是中途被唤醒),退出idle状态
    //重新取得CPU执行机会
    mPolling = false;

    ...

    //返回-1的话表示error了,直接goto Done代码执行
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        ALOGW("Poll failed with an unexpected error: %s", strerror(errno));
        result = POLL_ERROR;
        goto Done;
    }

    // 返回0表示timeout时间到了(没有新的消息导致中途唤醒),正常Done
    if (eventCount == 0) {
        ...
        result = POLL_TIMEOUT;
        goto Done;
    }

   
   //返回eventCount大于0,说明超时时间未到,有新的事件写入管道导致提前唤醒

    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd) {
            if (epollEvents & EPOLLIN) {
                awoken();//被唤醒
            } else {
                ...
    }
    return result;
}

Android的消息机制其实在Native层使用了Linux内核的epoll I/O机制,不仅可以处理延时消息,且达到了事件驱动的效果,还不会占用CPU资源

  • 当通过postDelayed发送延时消息后,传入的超时时间会通过nativePollOnce方法进行超时的阻塞,本质上是因为epoll_wait函数的挂起,达到了延时的目的

时间到后阻塞结束,epoll_wait返回,线程被重新唤醒并获得CPU资源,且epoll事件数(event_count)为0,说明中途没被唤醒,然后nativePollOnce直接返回,然后MQ的next方法再返回消息给上层Looper

  • 在MeesageQueue的enqueueMessage方法中会判断新插入的消息的时间是否小于队头消息的时间,以决定是否立即唤醒,通过nativeWake方法打断超时未到的阻塞.

  • 如果需要唤醒,往wake pipe唤醒管道写入数据(一个整数1),由于epoll监听了mWakeEventFd唤醒事件描述符,所以此时epoll_wait结束了挂起状态,返回事件数大于0,进而调用到awoken,最后nativePollOnce返回result为POLL_WAKE,上层消息得以继续处理。因为我们知道Looper的loop是一直在循环调用next的,如果底层继续阻塞,上层也是阻塞状态.

所以阻塞的机制是超时时间到了被唤醒,或者由于新插入的消息往管道写入新消息主动唤醒.

七、总结

当应用进程退到后台,或者程序始终没有需要处理的消息时,此时主线程便阻塞在queue.next()中的nativePollOnce中,并主动释放CPU资源进入休眠状态,并且不会消耗大量CPU资源.这也回答了为什么Looper中在进行一个死循环却不会导致异常耗电...

至此,我们已经从java层级别的源码探究到了Linux内核,把Android消息机制又深入了解到了一个层次(不得不说源码越读越觉得这些coder真的是大神啊~)