Android Handler中的休眠唤醒实现详解
android_greenhand 人气:0Handler中的奇奇怪怪
了解Handler原理时,有一个疑问handler中的休眠/唤醒不用Java中wait和notify呢,而是调用native方法(nativePollOnce/nativeWake)呢,奇奇怪怪 的又得需要学起来,没事找事的一天嘛。
这样不行吗?wait/notify 伪代码
MessageQueue.java //调用MessageQueue的next方法获取消息 Message next() { 、、、 synchronized (this) { //这时候检查队列有没有消息,没有消息调用this.wait()等待 if (message == null) { this.wait(); } if(有消息但消息未到期){ this.wait(time); } } 、、、 } //调用MessageQueue.enqueueMessage()添加消息 enqueueMessage(Message message) { 、、、 synchronized (this) { //消息加入队列后会调用this.notity()唤醒next()方法 if (message != null) { this.notify(); } } 、、、 }
在学习nativePollOnce/nativeWake前,还需要对Linux相关的知识熟悉一下。
Linux相关
eventfd
eventfd 是从内核2.6.22开始支持的一种新的事件等待/通知机制。用来通知事件的文件描述符,它不仅可以用于进程间的通信,还可以用户内核发信号给用户层的进程。简而言之:eventfd 就是用来触发事件通知,它只有一个创建方法:
int eventfd(unsigned int initval, int flags); 表示创建一个 eventfd 文件并返回文件描述符
参数:initval, 初始值
参数:flags
- EFD_CLOEXEC 会自动关闭这个文件描述符。
- EFD_NONBLOCK 执行 read / write 操作时,不会阻塞。
- EFD_SEMAPHORE count 递减 1。
相关操作
- write(): 其实是执行 add 操作,累加 count值。
- read(): 根据设置不同的flags标记,读取到不同的值
EFD_SEMAPHORE:读到的值为 1,同时 count 值递减 1。
其他的都是:读取 count 值后置 0
阿西吧什么乱七八糟的,别急看看这个下面这个Demo;
eventfd demo
#include <cstdlib> #include <inttypes.h> #include <iostream> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string> #include <sys/eventfd.h> #include <unistd.h> using namespace std; int main(int argc, char* argv[]) { int event_fd; if (argc < 2) { std::cout << "please input llegal argv " << endl; exit(EXIT_FAILURE); } event_fd = eventfd(0, EFD_NONBLOCK); if (event_fd == -1) { std::cout << "create evebtFd fail" << endl; exit(EXIT_FAILURE); } switch (fork()) { case 0: for (int j = 1; j < argc; j++) { long u = atoi(argv[j]); printf("Child writing %lu to efd\n", u); write(event_fd, &u, sizeof(long)); } printf("Child completed write loop\n"); exit(EXIT_SUCCESS); default: sleep(2); long u; printf("Parent about to read\n"); read(event_fd, &u, sizeof(long)); printf("Parents first read %lu from efd\n", u); long u2; read(event_fd, &u2, sizeof(long)); printf("Parents second read %lu from efd\n", u2); exit(EXIT_SUCCESS); } }
⚠️ #include <sys/eventfd.h> 是在Linux操作系统中的,在Mac电脑中是找不到包的,需要装虚拟机或者其他的C++开发软件包,这里推荐一个在线免费的编译C++的软件Lightly
Q eventfd和socket、pipe、fd_set、有什么区别和联系?
Epoll
epoll是Linux内核为处理大批量文件描述符而改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中,只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。提高应用程序效率。 来源自百度百科
epoll API
int epoll_create(int size)
创建 eventpoll 对象,返回一个 epfd,即 eventpoll 句柄。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
对eventpoll执行的操作,返回值:成功 0;失败 -1
epfd 对一个 eventPoll 进行操作
op 表示要执行的操作,包括 EPOLL_CTL_ADD (添加)、EPOLL_CTL_DEL (删除)、EPOLL_CTL_MOD (修改);
fd 表示被监听的文件描述符;
event 表示要被监听的事件,包括:
- EPOLLIN(表示被监听的fd有可以读的数据)
- EPOLLOUT(表示被监听的fd有可以写的数据)
- EPOLLPRI(表示有可读的紧急数据)
- EPOLLERR(对应的fd发生异常)
- EPOLLHUP(对应的fd被挂断)
- EPOLLET(设置EPOLL为边缘触发)
- EPOLLONESHOT(只监听一次)
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
返回值:监听到的产生的事件数 等待 epfd 监听的 fd 所产生对应的事件。
- epfd 表示 eventpoll句柄;
- events 表示回传处理事件的数组;
- maxevents 表示每次能处理的最大事件数;
- timeout:等待 IO 的超时时间,-1 表示一直阻塞直到来 IO 被唤醒,大于 0 表示阻塞指定的时间后被唤醒
epoll 使用示例
创建一个管道,使用 epoll 监听管道读端,然后进入阻塞:
#include <iostream> #include <stdio.h> #include <string> #include <sys/epoll.h> #include <sys/eventfd.h> #include <unistd.h> using namespace std; int main(int argc, char* argv[]) { if (argc < 2) { exit(EXIT_FAILURE); } int event_fd; int epoll_fd; event_fd = eventfd(0, EFD_NONBLOCK); if (event_fd == -1) { std::cout << "create evebtFd fail"; exit(EXIT_FAILURE); } epoll_fd = epoll_create(8); if (epoll_fd < 0) { std::cout << "create epollFd fail"; } struct epoll_event read_event; read_event.events = EPOLLIN; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, event_fd, &read_event); switch (fork()) { case 0: for (int j = 1; j < argc; j++) { long u = atoi(argv[j]); sleep(1); printf("Child writing %lu to efd\n", u); write(event_fd, &u, sizeof(long)); } printf("Child completed write loop\n"); exit(EXIT_SUCCESS); default: printf("Parent about to read\n"); struct epoll_event events[16]; int ret; while (1) { ret = epoll_wait(epoll_fd, events, 1, -1); printf("Parent epoll_wait return ret : %d\n", ret); if (ret > 0) { long u; read(event_fd, &u, sizeof(long)); printf("Parents read %lu from efd\n", u); } } exit(EXIT_SUCCESS); } }
结果
Handler 中的 epoll 源码分析
主要分析 MessageQueue.java 中的三个 native 函数:
private native static long nativeInit(); //初始化 private native void nativePollOnce(long ptr, int timeoutMillis); //阻塞 private native static void nativeWake(long ptr); //唤醒
「nativeInit 返回long,这是为什么?」 预知一下,或许这个可以解答我们的问题
nativeInit
首先来看 nativeInit 方法,nativeInit 在 MessageQueue 构造函数中被调用,其返回了一个底层对象的指针:
MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit(); //保存NativeMessageQueue }
//android_os_MessageQueue.cpp static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) { NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); ... return reinterpret_cast<jlong>(nativeMessageQueue); }
einterpret_cast<type-id> (expression)
type-id 必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值
返回值是NativeMessageQueue,而 NativeMessageQueue 初始化时会创建一个底层的 Looper 对象:
NativeMessageQueue::NativeMessageQueue() : mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) { mLooper = Looper::getForThread(); if (mLooper == NULL) { mLooper = new Looper(false); Looper::setForThread(mLooper); } }
Looper 的构造函数如下:
Looper::Looper(bool allowNonCallbacks) : mAllowNonCallbacks(allowNonCallbacks), ...{ mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); ... rebuildEpollLocked(); }
有没有熟悉的感觉,这和我们的Epoll的demo很相似,首先通过创建eventFd, ,专门用于事件通知。接着来看 rebuildEpollLocked 方法:
void Looper::rebuildEpollLocked() { mEpollFd = epoll_create(EPOLL_SIZE_HINT); struct epoll_event eventItem; memset(& eventItem, 0, sizeof(epoll_event)); eventItem.events = EPOLLIN; eventItem.data.fd = mWakeEventFd; int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem); ... }
可以看到我们已经熟悉的 epoll 操作了:通过 epoll_create 创建 epoll 对象,然后调用 epoll_ctl 添加 mWakeEventFd 为要监听的文件描述符。
nativePollOnce
之前学习 Handler 机制时多次看到过 nativePollOnce 方法,也知道它会进入休眠,下面就具体看看它的原理。对应的底层调用同样是在 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) { mLooper->pollOnce(timeoutMillis); ... }
可以看到实现同样是在 Looper.cpp 中,接着来看 Looper 的 pollOnce 方法:
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) { for (;;) { ... result = pollInner(timeoutMillis); } } int Looper::pollInner(int timeoutMillis) { ... struct epoll_event eventItems[EPOLL_MAX_EVENTS]; int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis); ...
至此通过调用 epoll_wait 方法,当前线程进入休眠,等待被唤醒。
nativeWake
最后来看如何通过 nativeWake 唤醒线程,首先是 android_os_MessageQueue.cpp 中:
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) { NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); nativeMessageQueue->wake(); } void NativeMessageQueue::wake() { mLooper->wake(); }
与 nativeInit、nativePollOnce 一样,最终实现都是在 Looper.cpp 中,Looper 的 wake 方法如下:
void Looper::wake() { uint64_t inc = 1; 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)); } } }
其中关键逻辑是对 mWakeEventFd 发起写入操作,从而唤醒 nativePollOnce 中通过 epoll_wait 进入休眠的线程。
结束
回答刚开始的疑问handler中的休眠/唤醒不用Java中wait和notify呢,而是调用nativePollOnce/nativeWake呢?
MessageQueue.java //调用MessageQueue的next方法获取消息 Message next() { 、、、 synchronized (this) { //这时候检查队列有没有消息,没有消息调用this.wait()等待 if (message == null) { this.wait(); } if(有消息但消息未到期){ this.wait(time); } } 、、、 } //调用MessageQueue.enqueueMessage()添加消息 enqueueMessage(Message message) { 、、、 synchronized (this) { //消息加入队列后会调用this.notity()唤醒next()方法 if (message != null) { this.notify(); } } 、、、 }
private native static long nativeInit();
返回值是nativeMessage对象,阻塞时会将mPtr当成参数nativePollOnce();
如果单纯用object.wait,那对于native层的消息是处理不到的,队列空闲时不能只判断Java层的MessageQueue,nativePollOnce去判断Native层,若大家都空闲,方法会阻塞到native的epoll_wait()
方法中,等待唤醒。 单纯用wait和notify,只能处理java层的消息,对于系统的消息不能处理。
加载全部内容