持续创作,加速成长!这是我参与「掘金日新计划 · 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真的是大神啊~)