Android Handler(3)消息休眠、唤醒简单分析

1,607 阅读4分钟

要了解handler休眠、唤醒机制,在native层,消息循环的休眠和唤醒使用了Linux内核的epoll机制

首先我们要了解一下linux下的epoll机制。从百度拷贝过来借鉴下。

epoll是什么?

Linux内核为处理大批量文件描述符而作了改进的poll

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著
提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描
述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge 
Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

简单的说就是epoll能够监听文件描述符的变化,效率高,cpu占用率小,最大连接无上限。

为什么使用epoll?

如果只是将 Java 层的阻塞/唤醒移植到 Native 层,倒也不用祭出 epoll 这个大杀器 ,Native 调用 pthread_cond_wait 也能达到相同的效果

选择 epoll 的另一个原因是, Native 层支持监听 自定义 Fd (比如 Input 事件就是通过 epoll 监听 socketfd 来实现将事件转发到 APP 进程的

而一旦有监听多个流事件的需求,那就只能使用 Linux I/O 多路复用技术,epoll 相对 select 、poll 效率更高,理所当然的成为 Google 首选。----转载

线程的睡眠和唤醒

睡眠nativePollOnce

messagequeue.next方法中调用
//native c++实现。参数1:
nativePollOnce(ptr, nextPollTimeoutMillis);

//调用了c++ 中android_os_MessageQueue_nativePollOn,又调用nativeMessageQueue->pollOnce
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

//调用mLooper pollOnce
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    //方法调用
    mLooper->pollOnce(timeoutMillis);
}

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    //循环
    for (;;) {
        //方法调用
        result = pollInner(timeoutMillis);
    }
}

//pollonce会不停的轮训pollInner的返回值,int变量。,就能知道消息的状态
int Looper::pollInner(int timeoutMillis) {
//调用了epoll_wait*****系统监听等待事件的产生
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
}

-1 表示在 “超时时间到期” 之前使用 wake() 唤醒了轮询,通常是有需要立刻执行的新消息加入了队列
-2 表示多个事件同时发生,有可能是新消息加入,也有可能是监听的 自定义 fd 发生了 I/O 事件
-3 表示设定的超时时间到期了
-4 表示错误,不知道哪里会用到
消息队列中没消息,或者 设定的超时时间没到期,再或者 自定义 fd 没有事件发生,
都会导致线程阻塞到 pollInner() 方法调用

消息队列为空,调用epoll_wait使线程阻塞,让出cpu调度

睡眠nativeWake

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    //NativeMessageQueue的wake方法
    nativeMessageQueue->wake();
}

void NativeMessageQueue::wake() {
    //调用looper.cpp的wake方法
    mLooper->wake();
}

void Looper::wake() {
    //往mWakeEventFd写入值  epoll监听到改变唤醒线程
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), 
    &inc, sizeof(uint64_t)));
}

初始化nativeInit

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    //创建NativeMessageQueue
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }

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

//走到方法中
NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    //创建了looper   
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

//eventfd初始化,而eventfd就是整个epoll机制的关键
Looper::Looper(bool allowNonCallbacks) {
    mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
}
//调用了epoll_create1
mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));

//最终调用了
int epoll_create1(int flags) {
  return FDTRACK_CREATE(__epoll_create1(flags));
}

// Java 层初始化消息队列时,同步调用 nativeInit() 方法,在 native 层创建了一个 NativeMessageQueue 对象
// Native 层的消息队列被创建的同时,也会创建一个 Native Looper 对象
// 在 Native Looper 构造函数中,调用 eventfd() 生成 mWakeEventFd,它是后续用于唤醒消息队列的核心
// 最后调用 rebuildEpollLocked() 方法,初始化了一个 epoll 实例 mEpollFd ,然后将 mWakeEventFd 注册到 epoll 池

结语

睡眠、唤醒分析到此为止,可以看出底层是借用了linux的机制来实现的。毕竟对于android来说cpu、内存资源都十分重要,谷歌没有用binder机制来实现handler,是有它独到的想法的。分析道这里就差不多了,我觉得再投入研究就有点没必要了。毕竟要做的事情还有很多,c++有功力的同学可以深入研究一下。

参考:

感兴趣的同学可以参考下面几个,详细解答了epoll机制和底层流程

# epoll详解

# epoll介绍及原理详解

#再谈Handler机制(Native篇)