前言
前文详细分析了Java层的消息循环机制的工作原理,在分析MessageQueue的过程中,我们遇到了nativePollOnce()
和nativeWake()
方法的调用,下面我们就深入到Native层的消息机制来看看它背后的运作原理。
Native层的消息机制
一、NativeMessageQueue的相关逻辑
1、NativeMessageQueue的构建
首先,我们来看看Java层的MessageQueue的构造函数:
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
这里调用了一个native方法,实际上这里初始化了native层的消息队列,并返回了该消息队列的头部指针地址。由于这个是native方法,这里利用了JNI机制调用本地代码,JNI的相关知识笔者在前面的文章也有说到,这里就不再赘述。我们直接来看本地代码:(frameworks/base/core/jni/android_os_MessageQueue.cpp):
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); //增加强引用指针计数,这里与RefBase,类似于智能指针的概念
return reinterpret_cast<jlong>(nativeMessageQueue); //强制类型转换,返回jlong类型,实际上是指针的值
}
这里引入了NativeMessageQueue
,顾名思义,这应该是native层的消息队列,我们用UML类图来看看它的类结构:
其中,RefBase是基类,它的作用比较特殊,在Android的native代码中,大部分的类都是继承自RefBase,作用有点类似于Java中的Object。实际上,它的作用与实现智能指针有关,便于native层的垃圾回收的实现(因为C++没有GC机制,我们需要手动实现对象的创建和回收操作)。这里不对RefBase做过多的深究,我们把关注点放回NativeMessageQueue
。
NativeMessageQueue
是native层的消息队列,虽然它称作消息队列,但实际上它是一个空壳,它内部并没有维护消息的队列或者链表,它把涉及消息的相关操作都交给了Looper
去处理。与Java层一样的是,一条线程只会有一个Looper
,这是因为native层也有着一套线程独立变量的机制,当前线程的变量只与当前线程有关。我们来看看NativeMessageQueue
的构造方法:
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread(); //获取当前线程的Looper对象
if (mLooper == NULL) {
//如果没有,那么初始化Looper对象
mLooper = new Looper(false);
Looper::setForThread(mLooper); //Looper与当前线程绑定
}
}
通过NativeMessageQueue
的构造方法可以看出,这里实例化了一个Looper
,而这个Looper
则是与线程相关的,因此我们可以推测Looper
实际上承担了消息队列的实际功能,NativeMessageQueue
对外表现为消息队列,也有相关的方法,但实际上它把核心逻辑都移交给了Looper
去处理。
小结:Java层的MessageQueue
被创建的时候,同时会创建一个Native层的NativeMessageQueue
,并初始化一个native的Looper
。经过JNI的调用返回,NativeMessageQueue
对象的指针地址会返回给Java层的MessageQueue
,保存在mPtr
这个成员变量内。
2、NativeMessageQueue#pollOnce
经过上面的NativeMessageQueue的初始化后,我们就能正常使用它了。还记得我们在Java层的MessageQueue#next()
方法内曾经看到过这个调用吗?nativePollOnce(ptr, nextPollTimeoutMillis)
,这里调用了native的方法,我们来看看它的nativec层的源码:
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);
}
方法很简单,这里传进来的ptr参数实际上就是MessageQueue.mPtr
变量,经过强制类型转换后,把ptr转换成了NativeMessageQueue指针,然后调用这个对象的pollOnce(args)
方法。这个过程可以理解为:在某一线程内,Java层持有Native层的NativeMessageQueue的指针地址值,经过JNI调用把该地址传递过来,native层根据这个地址找到了这个对象,强制类型转换成了NativeMessageQueue
对象。简单地说,Java层的MessageQueue对应了Native层的NativeMessageQueue。
下面,我们来看nativeMessageQueue->pollOnce(env, obj, timeoutMillis)
:
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mPollEnv = env;
mPollObj = pollObj;
mLooper->pollOnce(timeoutMillis); //把逻辑交给Looper去处理
mPollObj = NULL;
mPollEnv = NULL;
if (mExceptionObj) {
env->Throw(mExceptionObj); //env是JNI环境,这里的异常会抛给Java层
env->DeleteLocalRef(mExceptionObj);
mExceptionObj = NULL;
}
}
逻辑很简单,这里调用了mLooper->pollOnce(timeoutMillis)
方法,并把超时时间传递了进去,结合函数名字,我们可以合理推测:该函数内部在超时时间内进行native的消息处理,达到了超时时间就会JNI调用返回,以便处理Java层的消息。我们先记住这个推测,待会分析Looper的时候再来看这个推测是否正确。
3、NativeMessageQueue#wake
同样地,我们之前在讨论MessageQueue#enqueueMessage()
的时候,会发现进行了nativeWake()
调用,那么我们直接看源码:
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();
}
显然,根据ptr
找到对应的NativeMessageQueue
,然后调用了mLooper->wake()
,把逻辑交给了Looper去处理。那么到目前为止,一切问题的关键都指向了Looper
,我们开始探究native的Looper吧。
二、native Looper的工作原理
Looper是整个Native层消息机制的核心所在,大部分功能都是Looper完成的。要准确理解native Looper,我们首先就要对它的类结构有个整体的认识,下面笔者给出Looper的UML类图方便读者的理解。
1、native Looper的整体认识
Looper的类结构被定义在/system/core/libutils/include/utils/Looper.h
,源码地址为Looper.h。结合类结构,我们可以清晰地画出如下的UML类图:
结合Looper.h
的源码和上面的UML类图,下面先列举几个与Looper有关的类或成员变量,以便后面的源码阅读。
①mWakeEventFd:用于事件通知的文件描述符,在这里表示唤醒Looper的文件描述符。实际上它指向一个eventfd
对象,该对象可以实现事件的等待和通知机制。
②mEpollFd:epoll
的句柄,指向一个epoll
对象,有关epoll
的使用都要经过该句柄。epoll
简单来说,是Linux下的一种I/O事件通知机制。epoll
监听了eventfd
,当该文件描述符的缓冲区为空时,epoll
发出可写信号,当文件描述符的缓冲区不空时,发出可读信号。利用这样的机制,能实现线程间的通信。
③MessageEnvelop:内部封装了Message
、MessageHandler
和uptime
。其中Message
是native层发送的消息,而MessageHandler
内部有一个回调函数,当消息接收方接收并处理消息后就会回调该函数。uptime
是消息的触发时间。
④Request:封装了fd
、events
、callback
等参数,实际上这是Looper监听的一种特殊消息,可以称之为事件,它是以文件描述符形式存在的。通过addFd(args)
把fd添加到epoll
的监听队列中,然后封装成Request
对象,加入到Looper.mRequest
内,然后等待事件的发生。
⑤Response:当epoll
侦测到某一fd的缓冲池发生了改变后,Looper会找到该fd对应的Request,把它进一步封装成Response,然后加入mResponse
等待Looper处理该消息。
2、Looper的创建与初始化
上面详细讲述了与Looper有关的一些知识,在此基础上我们继续探索Looper的原理,首先,我们从Looper的构造方法看起。(源码位置在:/system/core/libutils/Looper.cpp)
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mPolling(false), mEpollFd(-1)