Handler native层实现原理
1 概述
想要了解Handler 底层实现原理必须得了解以下知识,不然跳到底层去看源码也是一头雾水:
-
C 与 C++,这个没什么好说的;
-
Linux 的管道,主要是pipe函数 与 eventfd 函数来创建管道,pipe() , eventfd() ,主要了解这两个函数的作用就行,都是用于创建管道的函数;
-
Linux 的epoll 机制;这个机制类似生产者和消费者模式,对文件的IO操作进行监听,当一个线程读取文件时,如果文件没有内容,这时线程就进入等待状态,另一个线程往文件写内容时,就会唤醒等待中的线程;
本文基于Android8.0源码,几乎所有版本的Handler底层源码逻辑都一样,不同的是低版本使用pipe()函数来创建管道,而其他则使用eventfd() 函数来创建管道,其实都差不多,只是管道的创建方式不同而已,8.0源码使用的是eventfd函数;
本文主要说的是Handler 底层实现,java层只会在捋逻辑的时候会带上一点。本文使用的native层文件有:
涉及的类有NativeMessageQueue 与Looper,NativeMessageQueue 在 android_os_MessageQueue.cpp 文件中定义与实现,Looper 的实现就在Looper.cpp 中;
2 职责
- android_os_MessageQueue.cpp,相当于系统帮我们生成的native-lib.cpp 文件一样,文件中创建 NativeMessageQueue对象并返回给java层,本身不保存NativeMessageQueue;
- NativeMessageQueue,该类相当于一个代理对象,构造函数中创建了Looper对象并保存,最终调用到Looper 中去;
- Looper,所有的逻辑都在这个类中进行,非常重要;
3 Looper的初始化
-
首先java层的MessageQueue的构造函数调用了 nativeInit() , 最终调到android_os_MessageQueue.cpp文件 的 android_os_MessageQueue_nativeInit() 方法中,如下:
MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit();//这里mPrt得到的是c++层的 NativeMessageQueue }
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) { NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); if (!nativeMessageQueue) { jniThrowRuntimeException(env, "Unable to allocate native queue"); return 0; } nativeMessageQueue->incStrong(env); return reinterpret_cast<jlong>(nativeMessageQueue); }
创建了NativeMessageQueue对象,然后把它转成long类型返回给jave层的mPtr。android_os_MessageQueue.cpp本身不保存任何东西,所以把NativeMessageQueue对象交给jave层去保存。再看看NativeMessageQueue 构造函数做了什么:
NativeMessageQueue::NativeMessageQueue() : mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) { mLooper = Looper::getForThread(); if (mLooper == NULL) { mLooper = new Looper(false); Looper::setForThread(mLooper); } }
首先调用Looper::getForThread() 来获取Looper对象,至于getForThread() 函数的实现这里不细说,作用相当java层的ThreadLocal 类一样,每个线程保存一个Looper对象,内部的实现关键其实是pthread_once()函数。第一次获取肯定为NULL,所以就new 一个Looper对象并保存起来;且通过Looper::setForThread(mLooper) 设置给当前线程;
-
进入的Looper的构造函数中
Looper::Looper(bool allowNonCallbacks) : mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false), mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false), mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) { 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); rebuildEpollLocked(); }
首先通过 eventfd()函数创建一个文件描述符,就是创建一个管道了,然后调用rebuildEpollLocked():(打印的代码去掉)
void Looper::rebuildEpollLocked() { // 如果有旧的管道存在,则关闭 if (mEpollFd >= 0) { #if DEBUG_CALLBACKS ALOGD("%p ~ rebuildEpollLocked - rebuilding epoll set", this); #endif close(mEpollFd); } // 开始根据epoll机制创建文件监听 mEpollFd = epoll_create(EPOLL_SIZE_HINT); struct epoll_event eventItem; memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union eventItem.events = EPOLLIN; eventItem.data.fd = mWakeEventFd; //这个函数就是创建监听了 int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem); // 这个for循环也是一样注册监听,不过第一次进来mRequests 肯定没值,所有不走这里,低版本压根没这个for for (size_t i = 0; i < mRequests.size(); i++) { const Request& request = mRequests.valueAt(i); struct epoll_event eventItem; request.initEventItem(&eventItem); int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem); if (epollResult < 0) { ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s", request.fd, strerror(errno)); } } }
rebuildEpollLocked 主要功能就是通过 Linux的epoll机制来创建文件监听,不了解的epoll 的先去了解Linux 的epoll 机制;作用是监听mWakeEventFd 这个文件描述符的EPOLLIN 事件,既管道内有内容可读的时候,管道读取端会被唤醒,Looper的初始化到此结束。Looper初始化的工作有:
- 通过android_os_MessageQueue.cpp 文件创建NativeMessageQueue对象并返回给java层;
- NativeMessageQueue 构造函数创建Looper对象并保存起来;
- Looper构造函数内通过 eventfd() 函数创建一个管道,并通过epoll 机制对管道的EPOLLIN 事件进行监听;
4 消息的获取
都知道java层的消息获取都是在MessageQueue.java 类中的next() 方法,next() 方法通过nativePollOnce() 方法调到native层,如下:
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0; //这个变量的意义是,得到下个Message的时间
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
...
}
if (msg != null) {
if (now < msg.when) {
// 如果当前时间比最早的msg要小,就说明还要等待一段时间,所有nextPollTimeoutMillis 得到的值就是需要等待的时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {//不需要等待了,得到一个Message
...
return msg;
}
} else {
// 没有消息的情况下
nextPollTimeoutMillis = -1;
}
。
。
。
// 第一次进来,这个pendingIdleHandlerCount = -1,mMessages 也为NULL
if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {//如果不需要额外处理,mBlocked 赋值为true后,直接 continue
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 处理一些回调
for (int i = 0; i < pendingIdleHandlerCount; i++) {
//...
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
先看一下代码中的注释,第一次进入,首先调用nativePollOnce(ptr, nextPollTimeoutMillis); ptr 就是native层传过来的NativeMessageQueue, java层是如何计算nextPollTimeoutMillis 需要自己的捋流程了,nextPollTimeoutMillis 这个值是非常关键的,关系到线程的是否会进入等待状态。
进入nativePollOnce() 方法,调到android_os_MessageQueue.cpp 文件中的android_os_MessageQueue_nativePollOnce 函数中:
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
//拿到ptr转成NativeMessageQueue,再调到pollOnce()函数去
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
/**
* @paraem pollObj 是java 层的MessageQueue
*/
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
//做一下赋值操作,然后调到Looper的pollOnce() 函数中,注意timeoutMillis 此时的值为0 ;
mPollEnv = env;
mPollObj = pollObj;
mLooper->pollOnce(timeoutMillis);
mPollObj = NULL;
mPollEnv = NULL;
if (mExceptionObj) {//抛异常
env->Throw(mExceptionObj);
env->DeleteLocalRef(mExceptionObj);
mExceptionObj = NULL;
}
}
Looper:
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
// 这个while 暂时不管,看样子是处理请求结果的,但是都还没请求哪来的结果
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;
}
}
//第一次进入result = 0,肯定不走这里,走下面的pollInner() 函数
// 经过pollInner() 函数处理后,result的值赋值为一个小于0的值,然后走下面这个if,清除数据后直接返回
// 这个返回直接就返回到java层了
if (result != 0) {
#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 = pollInner(timeoutMillis);
}
}
pollOnce() 函数中开启了一个死循环,进入pollInner() 函数,注意此时的timeoutMillis 值为0,看看pollInner() 函数;
int Looper::pollInner(int timeoutMillis) {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);
#endif
// timeoutMillis != 0 时,调整一下timeoutMillis的时间,timeoutMillis的值为0 则不走这里
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;
}
}
// Poll.
int result = POLL_WAKE;//POLL_WAKE的值为-1
mResponses.clear();
mResponseIndex = 0;
mPolling = true;
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
//该函数返回需要处理的事件数目,如返回0表示已超时。
//这是个非常重要的函数,所以说需要先去了解epoll机制,
//mEpollFd 就是epoll_create 创建的文件描述符
//eventItems 处理的事件结果存放的地方
//EPOLL_MAX_EVENTS 最大的处理数量
//timeoutMillis 超时时间,0则是立即返回,-1 则是一直阻塞等待,等待被唤醒
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
mPolling = false;
mLock.lock();
// 是否重新初始化,里面调用了rebuildEpollLocked(),上面分析的Looper初始化Looper的构造函数也是调用rebuildEpollLocked()
//一般情况不需要重新初始化
if (mEpollRebuildRequired) {
mEpollRebuildRequired = false;
rebuildEpollLocked();
goto Done;
}
//需要处理的事件数目小于0,返回
if (eventCount < 0) {
if (errno == EINTR) {
goto Done;
}
result = POLL_ERROR;
goto Done;
}
// 超时也直接返回
if (eventCount == 0) {
result = POLL_TIMEOUT;
goto Done;
}
//如果有事件返回,就说明管道中有内容,则调用 awoken() 函数之后返回
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeEventFd) {//mWakeEventFd 是Looper 构造函数中创建的文件描述符,就是管道
if (epollEvents & EPOLLIN) {//判断事件是否是EPOLLIN事件
awoken();
} else {
}
} else {
...
}
}
Done: ;
//这里的代码很多,其实就是做一下清除操作
.
.
.
return result;
}
// result 的结果就是下面的四个枚举值
enum {
POLL_WAKE = -1,
POLL_CALLBACK = -2,
POLL_TIMEOUT = -3,
POLL_ERROR = -4,
};
看代码的注释,注释说得比较清楚了,这个函数非常重要,函数内通过 epoll机制的epoll_wait() 函数来进行线程的阻塞和超时调起;
- 当epoll_wait() 返回,eventCount 小于等于0 说明没有事件,使用goto返回,返回的 result 的值都小于0
- 当epoll_wait() 返回,eventCount大于0则说明有事件,判断事件是否为EPOLLIN事件,是则调到 awoken() 函数
接下来看 awoken() 函数:
void Looper::awoken() {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ awoken", this);
#endif
uint64_t counter;
TEMP_FAILURE_RETRY(read(mWakeEventFd, &counter, sizeof(uint64_t)));
}
使用了宏函数
#define TEMP_FAILURE_RETRY(exp) ({ \
typeof (exp) _rc; \
do { \
_rc = (exp); \
} while (_rc == -1 && errno == EINTR); \
_rc; })
不了解typeof 是什么的可以看一下这里 typeof, 最后一行是返回值,展开后大致如下:
int _rc;
do{
_rc = read(mWakeEventFd, &counter, sizeof(uint64_t));
}while(_rc == -1 && errno == EINTR);
return _rc;// 这里的return 不是返回awoken() 函数,这里其实相当于awoken 里面调用了一个函数,而这个函数返回了_rc
作用是清空管道数据,保证每次处理的事件中,管道内的数据是最新的。至此,消息的获取、线程是如何阻塞与被唤醒的已经说完了,最关键的就是epoll 机制中的epoll_wait函数起到关键作用,所以了解epoll 机制是重中之重。
5 消息的发送
native 是没有消息的发送这种概念的,只有是否需要被唤醒,当没有消息的时候timeoutMillis = -1,epoll_wait() 函数则会处于阻塞状态,当java发送一条消息时,通过MessageQueue.java 的enqueueMessage() 方法把消息放到队列中去,然后判断是否需要唤醒线程,唤醒线程通过调用nativeWake() 方法, 调到android_os_MessageQueue.cpp文件的android_os_MessageQueue_nativeWake 函数:
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}
通过java层传过来的ptr 转成NativeMessageQueue 对象再调用wake()函数:
void NativeMessageQueue::wake() {
mLooper->wake();
}
又调到Looper 的wake() 函数中:
void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ wake", this);
#endif
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));
}
}
}
使用了宏函数:
#define TEMP_FAILURE_RETRY(exp) ({ \
typeof (exp) _rc; \
do { \
_rc = (exp); \
} while (_rc == -1 && errno == EINTR); \
_rc; })
转换后如下:
uint64_t inc = 1;
ssize_t nWrite = ({
int _rc;
do{
_rc = write(mWakeEventFd, &inc, sizeof(uint64_t));
}while(_rc == -1 && errno == EINTR);
_rc; //把_rc 赋值给nWrite变量;
})
唤醒就是往管道中写入内容,前面说了通过epoll_ctl() 函数对管道注册了监听,当有内容往管道写时,epoll_wait() 函数就会被唤醒,唤醒的流程就是这么简单。
Handler 的native层原理至此结束,这里就说了底层代码实现原理,还得配合java层逻辑一起看才能更加理解透彻Handler的流程及原理,至于java层我相信难度不大,结合文章再去阅读Handler的源码才会真正明白Handler的整个流程。