前言
从 Android 2.3 开始,Google 把 Handler 的阻塞/唤醒方案从 Object#wait() / notify(),改成了用 Linux epoll 来实现
原因是 Native 层也引入了一套消息管理机制,用于提供给 C/C++ 开发者使用,而现有的阻塞/唤醒方案是为 Java 层准备的,只支持 Java
简述
一、 I/O多路复用: epoll
epoll 提供的三个函数:
// 用于创建一个 epoll 池
int epoll_create(int size);
// 用来执行 fd 的 “增删改” 操作,最后一个参数 event 是告诉内核 需要监听什么事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 使用户线程阻塞的方法,它的第二个参数 events 接受的是一个集合对象,
// 如果有多个事件同时发生,events 对象可以从内核得到发生的事件的集合
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
二、 Linux的eventfd
eventfd 是专门用来传递事件的 fd ,它提供的功能也非常简单:累计计数
理解write() 函数与read() 函数:
int efd = eventfd();
write(efd, 1);//写入数字1
write(efd, 2);//再写入数字2
int res = read(efd);
printf(res);//输出值为 3
write()函数:向 eventfd 中写入一个 int 类型的值,并且,只要没有发生 读 操作,eventfd 中保存的值将会一直累加
read() 函数:将 eventfd 保存的值读了出来,并且,在没有新的值加入之前,再次调用 read() 方法会发生阻塞,直到有人重新向 eventfd 写入值
总结: 只要 eventfd 计数不为 0 ,那么表示 fd 是可读的。再结合 epoll 的特性,我们可以非常轻松的创建出 生产者/消费者模型
Handler 机制的底层逻辑就是利用 epoll + eventfd,其中消费者大部分时候处于阻塞休眠状态,而一旦有请求入队(eventfd 被写入值),消费者就立刻唤醒处理,
流程
一、java层中MessageQueue 类中的几个 jni 方法:
nativeInit()、nativePollOnce() 和 nativeWake()
/frameworks/base/core/java/android/os/MessageQueue.java
class MessageQueue {
// 初始化消息队列
private native static long nativeInit();
// 消息的循环与阻塞
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
// 消息的分送与唤醒
private native static void nativeWake(long ptr);
}
二、消息队列初始化
Java层MessageQueue 构造函数中会调用 nativeInit() 方法
/frameworks/base/core/java/android/os/MessageQueue.java
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
在native层会创建一个消息队列 NativeMessageQueue 对象,用于保存 Native 开发者发送的消息,并且在NativeMessageQueue 构造函数中会创建一个native层的Looper对象
/frameworks/base/core/jni/android_os_MessageQueue.cpp
class android_os_MessageQueue {
void android_os_MessageQueue_nativeInit() {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
}
NativeMessageQueue() {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}
}
接着我们来看Native Looper 初始化流程
/system/core/libutils/Looper.cpp
class looper {
Looper::Looper() {
// 重头戏:mWakeEventFd 是用来监听 MessageQueue 是否有新消息加入
int mWakeEventFd = eventfd();
rebuildEpollLocked();
}
void rebuildEpollLocked(){
// 重头戏:在 Looper 初始化时创建了 epoll 对象
int mEpollFd = epoll_create();
// 把用于唤醒消息队列的eventfd 添加到 epoll 池
epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
}
}
消息队列初始化总结:
重点:mWakeEventFd 与 mEpollFd
1、Looper 的构造函数首先创建了 eventfd 对象 :mWakeEventFd,作用是用来监听 MessageQueue 是否有新消息加入
2、 Looper 初始化时创建了 epoll 对象:mEpollFd
3、把mWakeEventFd 添加到epoll池中
三、消息循环与阻塞
Java层MessageQueue的next方法中会调用 nativePollOnce() 方法。
链路如下:
Looper#loop() -> MessageQueue#next() -> MessageQueue#nativePollOnce()
在native层的nativePollOnce()调用NativeMessageQueue中的pollOnce()方法,最终会进入Looper的poollPnce()方法
/frameworks/base/core/jni/android_os_MessageQueue.cpp
class android_os_MessageQueue {
//jni方法,转到 NativeMessageQueue#pollOnce()
void android_os_MessageQueue_nativePollOnce(){
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
class NativeMessageQueue : MessageQueue {
/转到 Looper#pollOnce() 方法
void pollOnce(){
mLooper->pollOnce(timeoutMillis);
}
}
}
接着我们看native中Looper里的pollOnce()方法
//system/core/libutils/Looper.cpp
class looper {
int pollOnce(int timeoutMillis){
int result = 0;
for (;;) {
if (result != 0) {
return result;
}
result = pollInner(timeoutMillis);//超时
}
}
int pollInner(int timeoutMillis){
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);//调用 epoll_wait() 等待事件的产生
}
}
消息循环与阻塞总结:
整体链路如下:
Looper#loop() -> MessageQueue#next() -> MessageQueue#nativePollOnce()
->NativeMessageQueue#pollOnce() -> Looper#pollOnce()-> Looper#pollInner() -> epoll_wait()
消息队列在初始化成功以后,Java 层的 Looper#loop() 会开始无限轮询,不停的获取下一条消息。如果消息队列为空,调用 epoll_wait 使线程进入到阻塞态,让出 CPU 调度
四、消息的发送/唤醒机制
Java层MessageQueue的enqueueMessage方法中会调用 nativewake() 方法。
链路如下:
Handler #enqueueMessage() -> MessageQueue#enqueueMessage() -> MessageQueue#nativeWake()
Java 和 Native 都各自维护了一套消息队列,所以他们发送消息的入口也不一样
1、Java 开发使用 Handler#sendMessage() / post()
2、C/C++ 开发使用 Looper#sendMessage()
先看Java层:
Handler 发送消息时,不管调用的是 sendMessage 还是 post,最后都是调用到 MessageQueue#enqueueMessage() 方法将消息入列,入列的顺序是按照执行时间先后排序
注:如果我们发送的消息需要马上被执行,那么将 needWake 变量置为 true,接着使用 nativeWake() 唤醒线程
/frameworks/base/core/java/android/os/Handler.java
class Handler {
boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
return queue.enqueueMessage(msg, uptimeMillis);
}
}
/frameworks/base/core/java/android/os/MessageQueue.java
class MessageQueue {
boolean enqueueMessage(Message msg, long when) {
//...按照到期时间将消息插入消息队列
if (needWake) {
nativeWake(mPtr);
}
}
}
在native层的nativeWake()调用NativeMessageQueue中的wake()方法,最终会进入Looper的wake()方法
/frameworks/base/core/jni/android_os_MessageQueue.cpp
class android_os_MessageQueue {
// jni方法,转到 NativeMessageQueue#wake()
void android_os_MessageQueue_nativeWake(){
nativeMessageQueue->wake();
}
class NativeMessageQueue : MessageQueue {
// 转到 Looper#wake() 方法
void wake(){
mLooper->wake();
}
}
}
接着我们看native中Looper里的wake()方法
/system/core/libutils/Looper.cpp
class looper {
void Looper::wake() {
int inc = 1;
write(mWakeEventFd, &inc);
}
}
总结java层唤醒:
链路如下:
Handler #enqueueMessage() -> MessageQueue#enqueueMessage() -> MessageQueue#nativeWake()->NativeMessageQueue#wake() -> Looper#wake() -> write()
Java 发送消息链路走完,然后我们看 Native 层如何发送消息
/system/core/libutils/Looper.cpp
class looper {
void Looper::sendMessageAtTime(uptime, handler,message) {
int i = 0;
int messageCount = mMessageEnvelopes.size();
while (i < messageCount && uptime >= mMessageEnvelopes.itemAt(i).uptime) {
i += 1;
}
mMessageEnvelopes.insertAt(messageEnvelope(uptime, handler, message), i, 1);
// Wake the poll loop only when we enqueue a new message at the head.
if (i == 0) {
wake();
}
}
void Looper::wake() {
int inc = 1;
write(mWakeEventFd, &inc);
}
}
总结唤醒:
java与native发消息的方式 、消息类型 、 送达的消息队列 都不相同,但是当需要唤醒线程时,Java 和 Native 都会执行到 Looper#wake() 方法线程如何被唤醒?为什么这样的操作会唤醒?
1、write() 一行方法调用,向 mWakeEventFd 写入了一个 1则会唤醒。
2、mWakeEventFd 被写入值后,状态会从 不可读 变成 可读,内核监听到 fd 的可读写状态发生变化,会将事件从内核返回给 epoll_wait() 方法调用
而 epoll_wait() 方法一旦返回,阻塞状态将会被取消,线程继续向下执行
总结
重点: java层的looper、messageQueue在c++层均有对应的类,然后通过将looper初始化时创建的 eventFd 返回的 wakeEventFd,注册到 epoll_create创建的 epoll对象里,然后通过epoll_ctl去在wakeEventFd上添加一个 ADD类型的事件监听,最后上层 调用nativePollOnce的时候,最终会调用 epoll 的 epoll_wait 通过监听fd的方式来形成阻塞。来消息后,去往wakeEventFd去写一个“1”值,此后epoll_wait监听到值了,就阻塞解除继续执行了,继续取message执行去了。