在上一篇博文中,我们已经对Android消息处理机制的整体流程有了一个大体上的认识,也熟悉了消息机制中各个对象和对象之间的基本交互,但是讲到MessageQueue对象的时候,我们还有几个内容没有深入去讲(在上一篇博文的MessageQueue部分留下了一些问题),主要关于MessageQueue的本地代码实现部分,在这一篇我们详细的来介绍一下本地实现部分,让大家对消息处理机制有一些更深入的认识。
一、回到MessageQueue
在上一篇的的分析中,我们知道了Looper在构造函数中会创建一个属于自己的的MessgeQueue对象,代码如下:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed); //创建MessageQueue
mThread = Thread.currentThread();
}
让我们再来看一看MessageQueue的具体实现
(1)MessageQueue的构造函数
//属性之一
private long mPtr;
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
原来这里调用了一个本地方法,并且把返回值保存在mPtr对象中,它的具体实现是在android_os_MessageQueue.cpp中,我们来看一下C++的本地实现:
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,之后把创建的NativeMessageQueue的地址进行了返回,正是我们保存为long的mPtr,这样我们就知道了,Java层的mPtr保存的就是C++层所创建的NativeMessageQueue的地址。那这个 NativeMessageQueue又是啥呢,听名字就是本地消息队列,看来好像是C++层的消息队列,怎么C++层又变出一个消息队列了?先别急,我们直接来看源码:
NativeMessageQueue::NativeMessageQueue() : mInCallback(false), mExceptionObj(NULL) {
mLooper = Looper::getForThread(); //尝试获取当前线程的Looper
if (mLooper == NULL) {
mLooper = new Looper(false); //如果没有就新创建一个
Looper::setForThread(mLooper);
}
}
这一看我们又要晕了,怎么这里又多了一个Looper?难道C++层也有一个Looper不成?对,这下我们要清晰的讲一下了,其实Java层我们知道有一个Looper,它具有自己的MessageQueue,这下我们也看出来,MessageQueue中的mPtr也指向了一个C++层的NativeMessageQueue,并且NativeMessageQueue中也保存了一个C++层的Looper,并且我们后面也会看出,它们是相对的,Java层的实现是与C++层的实现有关的。
可能看了这些调用大家都有点摸不着头脑,现在我们在这个阶段先总结一下:首先我们知道了在MessageQueue的构造函数中调用了名为nativeInit
的本地方法,它在C++层创建了一个NativeMessageQueue并且将地址返回存储在MessageQueue的mPtr属性中;第二我们知道NativeMessageQueue的构造函数中也调用了一个C++层的Looper的方法,先判断当前线程是否有C++的Looper对象,如果没有就会新创建一个并且保存为当前线程的Looper,出现的函数代码我们来看一下:
void Looper::setForThread(const sp<Looper>& looper) {
...
pthread_setspecific(gTLSKey, looper.get());
}
sp<Looper> Looper::getForThread() {
...
return (Looper*)pthread_getspecific(gTLSKey);
}
主要看出调用了pthread_setspecific
和pthread_getspecific
两个linux提供的函数来进行线程私有数据的存储,下面让我们进入Looper的构造函数中来看一看:
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
//1、创建管道
int wakeFds[2];
int result = pipe(wakeFds);
...
mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];
...
//2、创建epoll实例
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
...
struct epoll_event eventItem; //创建epollevent
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN; //可读事件
eventItem.data.fd = mWakeReadPipeFd;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
...
}
总的来看,做了两件事:
1、调用linux函数pipe创建了一个管道,并且将管道的读端文件描述符和写端文件描述符保存在了下面两个成员变量中:
int mWakeReadPipeFd; // immutable
int mWakeWritePipeFd; // immutable
2、利用linux函数epoll_create创建了一个epoll实例,将它的文件描述符保存在了下面这个成员变量中
int mEpollFd; // immutable
并且创建了一个epoll_event结构,设置events为EPOLLIN表示用于注册可读事件,最终调用epoll_ctl进行了事件注册,这样epoll就可以对之前所创建的管道的写操作进行监听。
说了这么多终于把MessageQueue的构造函数的本地部分说完了,一个小小的nativeInit
函数竟然创建了这么多内容,我们可能会产生疑惑,这C++部分的这些内容到底有啥用呢?特别是多的NativeMessageQueue和C++层的Looper,Looper中还创建了管道和epoll实例,有啥用呢?慢慢往下看,Google工程师总不会写出没用的东西吧。
(2)MessageQueue 的next函数
前一篇我们说了利用Looper中的loop函数进行队列中消息的不停获取,特别是提到了函数next
,就是用于获取队列中的最新消息的函数,我们说这是一个阻塞方法,在没有新消息的时候会自动阻塞,让我们来看一看我们有什么内容没有关注到:
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; //空闲处理器个数
int nextPollTimeoutMillis = 0; //超时时间
for (;;) {
...
}
我们在上一篇中有两个变量都没有讲解,分别是:
int pendingIdleHandlerCount = -1; //空闲处理器的个数
int nextPollTimeoutMillis = 0; //超时时间
我们先从关键的那个来看
关键变量1:nextPollTimeoutMillis(超时时间)
使用步骤和作用:
首先在next开头处进行创建并初始化为0,代码如下:
int nextPollTimeoutMillis = 0;
步骤1:作为参数传入本地方法nativePollOnce(ptr, nextPollTimeoutMillis)
中,让我们来跟随这个本地方法看一看它的调用过程,首先我们已经知道了参数ptr,它就是C++层的NativeMessageQueue的地址,存储在Java层中;其次就是我们的关键变量,也作为参数传入了native方法。让我们来看一下C++层的函数android_os_MessageQueue_nativePollOnce
:
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jclass clazz,
jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, timeoutMillis);
}
既然知道了ptr是NativeMessageQueue的地址,我们可以看出它获取了NativeMessageQueue的指针,并且调用了NativeMessageQueue的方法pollOnce
,并且又将nextPollTimeoutMillis的值作为参数传入,我们接着看调用方法。
步骤2:
void NativeMessageQueue::pollOnce(JNIEnv* env, int timeoutMillis) {
...
mLooper->pollOnce(timeoutMillis);
...
}
原来这里又调用了NativeMessageQueue所拥有的C++层Looper对象的pollOnce
函数,并且将nextPollTimeoutMillis的值作为参数传入,这样就简单了,继续看。
步骤3:
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
...
if (result != 0) {
...
return result;
}
result = pollInner(timeoutMillis);
}
}
这里看出是一个无限循环,直到result不为0,看过一些解析,基本上这里都说是是否有新的消息,也就是说如果没有新的消息并且pollInner
函数不阻塞的话返回值是0,如果有新的消息返回值就为0,也就是说pollOnce
函数是一个阻塞方法(无论timeoutMillis为什么值),直到有新的消息才会返回,是不是这样的呢?如果仔细看源码你就会发现并不是这样(如果我分析的不对欢迎指正),我们接着往下看:
步骤4:
int Looper::pollInner(int timeoutMillis) {
...
// Poll.
int result = POLL_WAKE;
...
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
...
if (eventCount < 0) {
...
result = POLL_ERROR;//ERROR情况
goto Done;
}
// Check for poll timeout.
if (eventCount == 0) {
...
result = POLL_TIMEOUT; //TIME_OUT情况
goto Done;
}
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeReadPipeFd) {
if (epollEvents & EPOLLIN) {
awoken(); //WAKE情况
} ...
}
}
Done: ;
return result;
}
我们注意到result就是返回结果,初始值为枚举POLL_WAKE,接着往下看,调用了一个linux函数epoll_wait
,针对之前创建的epoll对象做一些操作,这里我们才发现,我们的nextPollTimeoutMillis参数值终于起了作用,作为参数传入了epoll_wait
函数中,简单介绍一下epoll_wait
函数,它会等待IO读写事件的产生,而timeoutMillis参数是干嘛的呢?就是一个超时时间,如果在指定时间内都没有事件的产生,就超时并返回0,而等待期间这个函数是一个阻塞函数;另外如果timeoutMillis为-1,就表示永久阻塞,不会超时,如果没有IO读写事件的产生就一直阻塞在此处。
知道了epoll_wait
函数的作用,让我们看一看result,我们看出来result的结果实际是一个枚举值,让我们直接看一看枚举值不就行了:
enum {
/**
* Result from Looper_pollOnce() and Looper_pollAll():
* The poll was awoken using wake() before the timeout expired
* and no callbacks were executed and no other file descriptors were ready.
*/
POLL_WAKE = -1,
/**
* Result from Looper_pollOnce() and Looper_pollAll():
* One or more callbacks were executed.
*/
POLL_CALLBACK = -2,
/**
* Result from Looper_pollOnce() and Looper_pollAll():
* The timeout expired.
*/
POLL_TIMEOUT = -3,
/**
* Result from Looper_pollOnce() and Looper_pollAll():
* An error occurred.
*/
POLL_ERROR = -4,
};
我们主要关注两个值:
POLL_TIMEOUT:TIME_OUT情况即eventCount == 0
的情况,我们又知道eventCount就是前面介绍的epoll_wait
函数的返回值,这下我们知道了,TIME_OUT就是epoll_wait
函数的超时情况;
POLL_WAKE :可以看一下上面的函数,WAKE作为初始值,执行的部分就是下面的关键部分:
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeReadPipeFd) {
if (epollEvents & EPOLLIN) {
awoken(); //WAKE情况
} ...
}
}
eventCount就是发生了可读事件的数量,如果fd == mWakeReadPipeFd
,即发生的IO读写事件就是在管道的读端文件描述符上的,并且事件epollEvents & EPOLLIN
,即是EPOLLIN事件,表示在此文件描述符上有可读数据的事件,就调用awoken()
函数来处理,接着看一下awoken()
函数的具体实现:
void Looper::awoken() {
...
char buffer[16];
ssize_t nRead;
do {
nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
} while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
}
可以看到仅仅做的就是把管道的读端描述符的数据读出来,即有数据我就读出来,这样管道又可以继续进行监听了,同样我们也必须清楚,这个数据并没有什么用,唤醒操作只不过是在epoll_wait
函数中执行的,它的阻塞结束就是唤醒操作。
分析了这么多,让我们回到刚才的pollOnce
函数
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
...
if (result != 0) {
...
return result;
}
result = pollInner(timeoutMillis);
}
}
通过对result值的分析,我们发现pollOnce
函数并不是一个阻塞函数,真正阻塞的是linux函数epoll_wait
,并且,它的阻塞时间是由我们一直关注的关键变量nextPollTimeoutMillis决定的,有三个情况:
情况1:nextPollTimeoutMillis为0,那么无论是否有读写事件产生,都不会阻塞;
情况2:nextPollTimeoutMillis>0,那么如果没有事件产生,就会阻塞nextPollTimeoutMillis确定的时长,之后超时;
情况3:nextPollTimeoutMillis=-1,那么如果没有事件产生,函数一直阻塞在此,直到有读写事件产生。
赋值情况:
那么下面让我们看一看nextPollTimeoutMillis的赋值情况:
Message next() {
...
for (;;) {
...
nativePollOnce(ptr, nextPollTimeoutMillis); //本地方法调用
synchronized (this) {
...
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); //1.可能赋值处1
} else {
...//得到消息的处理过程,包括取出消息、返回消息。
}
} else {
// No more messages.
nextPollTimeoutMillis = -1; //2.可能赋值处2
}
...
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
//没有空闲处理器,nextPollTimeoutMillis这里肯定大于0,会在native方法阻塞!
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf("MessageQueue", "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;//3.可能赋值处3
}
}
通过对上面代码的分析,我们可以找到三个主要赋值的地方,下面我们一个一个看一看是什么情况以及有什么作用:
情况1:
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); //1.可能赋值处1
}else {
...//得到消息的处理过程,包括取出消息、返回消息。
}
这里我们可以看出来首先是msg != null
的情况,也就是说消息队列中有需要处理的消息(msg就是消息队列中处理时间最近的消息),之后判断如果消息处理的时间大于当前时间,即now < msg.when的情况,也就是说处于最先的消息还是没有到消息处理的时间,那么nextPollTimeoutMillis就被赋值为这个时间的差值,它的作用也就是说,当你调用epoll_wait
函数的时候,即使没有新的消息产生,把阻塞状态的epoll_wait
函数唤醒(后面我们会说如何唤醒的具体过程),它也会在这个差值(也就是需要处理此消息的时间)自动超时,唤醒以进入Java层处理此消息;如果消息时间小于等于当前时间,就是else的情况,那么说明需要处理此消息,就把消息取出并且进行返回,处理消息。
情况2:
if (msg != null) {
...
} else {
// No more messages.
nextPollTimeoutMillis = -1; //2.可能赋值处2
}
看懂了情况1就知道情况2就是msg == null
的情况下,即当前没有需要处理的消息,那么就把nextPollTimeoutMillis赋值为-1,那么epoll_wait
函数在如果没有新的消息产生唤醒它的情况下,就会一直阻塞在此(因为这个时刻没有消息产生,如果之后有自然会唤醒它)。
情况3:
for (;;) {
...
//只有两种可能才会运行到这里
//1、最新消息还未到处理时间,nextPollTimeoutMillis>0
//2、没有消息要处理,nextPollTimeoutMillis=-1
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
//没有空闲处理器,nextPollTimeoutMillis这里肯定大于0,会在native方法阻塞!
continue;
}
...
// Run the idle handlers.
//运行空闲消息处理
...
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;//3.可能赋值处3
}
}
要知道情况3,就要引出我们另一个关键变量pendingIdleHandlerCount,也就是空闲处理器个数,我们先简单了解一下空闲处理器,就是当消息循环没有新的消息需要处理的时候,空闲处理器会做一些不那么重要的工作。
当运行到:if (pendingIdleHandlerCount <= 0)
这句时,首先我们要明确,这个时刻要么是情况1:最新的消息还未到处理时间,nextPollTimeoutMillis>0;要么是情况2:没有消息要处理,nextPollTimeoutMillis=-1,绝对不会是消息返回的情况!明确了这点我们接着往下看:
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
//没有空闲处理器,nextPollTimeoutMillis这里肯定大于0,会在native方法阻塞!
continue;
}
这时没有空闲处理器来进行空闲处理,pendingIdleHandlerCount <= 0
,那么这时我们上面说到的两种情况都有可能,但有一个是共同的,就是epoll_wait
函数一定会阻塞,因为此时nextPollTimeoutMillis>0或等于-1,又调用了continue;
跳出了此次循环,进入下一次循环必然会先调用nativePollOnce
本地方法,那么阻塞就是必然情况。
那如果有空闲处理器的情况呢?也就是赋值情况3:
// Run the idle handlers.
//运行空闲消息处理
...
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;//3.可能赋值处3
运行了空闲消息处理的操作,也就是我省略的部分,之后为什么要把nextPollTimeoutMillis重新赋值为0,也就是epoll_wait
函数会立刻返回,不会阻塞呢?因为我们知道消息处理函数运行的过程中随时会有可能有新的消息产生,那么就不能阻塞,而是马上进入Java层进行消息检查,以防止有新的消息产生而得不到处理,其关键原因是因为空闲消息处理期间,代码并没有进入epoll_wait
函数,那么消息产生的唤醒操作就不会起效果,如果这之后epoll_wait
函数又进入阻塞情况的话,这段“真空期”的消息就得不到及时的处理。
next函数的总结:通过对next的分析可以看出来,next阻塞的原因不是那个无限的for循环!(我看了很多地方的解析都错误的说是因为for的原因,我一开始也理解成这个原因),而是那个nativePollOnce的本地方法调用!相信理解了这个更有助于你对整个消息循环底层实现的理解,也让你真正知道其关键点是如何实现的。
(3)MessageQueue的enqueueMessage函数、
讲清楚了next函数,我们知道next中调用的本地方法,在没有读到管道的读端文件描述符的读写事件时,会进入阻塞状态;同样我也说到,新消息的产生会唤醒函数,如何唤醒?肯定是向管道的读端文件描述符做写入操作对吧!相信大家都应该大概清楚这个操作是怎么调用的,肯定是在Java层的enqueueMessage函数中一步一步调用下去的,下面让我们来看一看这个过程:
final boolean enqueueMessage(Message msg, long when) {
...
boolean needWake;
synchronized (this) {
...
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
// 插入链表头部
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
}
if (needWake) {
nativeWake(mPtr);
}
return true;
}
首先补充一下关于这里的插入操作,上一篇忽略了这个点,我们知道enqueueMessage
函数是在Handler的sendMessage
函数中调用的,我们当时说了三个方法:
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0); //延迟时间为0,即即时消息
}
public final boolean sendMessageDelayed(Message msg, long delayMillis){
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis){
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
//把target设置为自己,这样我们就知道Message的target就是发送它的Handler
sent = queue.enqueueMessage(msg, uptimeMillis);
//队列的添加消息操作
}
else {
RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}
最终调用的都是sendMessageAtTime
函数,而它的参数uptimeMillis就是message的处理绝对时间,因为如果是即时处理的消息,就是调用第一个函数sendMessage
,它其实就是调用了sendMessageDelayed
函数,而它的第二个参数就是处理时间比现在时间延迟多少,函数sendMessage
就是延迟为0,即不延迟处理,但最终sendMessageDelayed
还是会把延迟时间加上当前时间SystemClock.uptimeMillis()
,所以我们就可以知道sendMessageAtTime
函数传入queue.enqueueMessage的参数就是消息处理的绝对时间。
言归正传,我们来看一看enqueueMessage
的操作,
唤醒情况1:
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
}
这里的p就是当前的消息链表头,那么这个判断是什么情况呢?看一下插入操作我们就知道了,就是把插入的消息放在了链表头的位置,也就是最先处理的消息,其实就是两个情况:
情况1:p == null,消息链表中无任何消息,自然放入链表头;
情况2:when == 0
或 when < p.when
,也就是插入消息的处理时间比当前头部消息要小,那么自然放在最前面。
这两种情况下不光做了插入操作,还进行了一个处理:needWake = mBlocked;
,这个mBlocked不就是我们刚说的变量,它就是表示我们之前调用的nativePollOnce
是否阻塞的变量,那么在链表头被改变的情况下,也就是最新的消息发生了变化,那么此时首先有可能此时线程是在阻塞状态下的,那如果阻塞就应该进行唤醒,以使其检查最新的消息是否是需要立刻处理的,是否需要唤醒就取决于mBlocked的值,如果在阻塞状态就进行唤醒。
唤醒情况2:
else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
唤醒情况2首先是链表头没有变化,插入的消息是要在队列中间的某个位置的,那么这种情况下一般是不需要进行唤醒的(毕竟链表头没有变化),只需要找到合适的地方插入即可;但如果Message是最早的异步消息,也是需要进行唤醒的。
分析了这么多是否唤醒的判断,终于来说一说唤醒操作了:
if (needWake) {
nativeWake(mPtr);
}
当确定需要唤醒的时候,就会调用本地方法nativeWake
进行唤醒操作,不用多说,我们已经知道mPtr存储的就是C++层的NativeMessageQueue的地址,让我们沿着它往下看做了哪些步骤:
步骤1:
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
return nativeMessageQueue->wake();
}
第一步就是把ptr转换得到C++层的NativeMessageQueue对象的指针,接着调用它的wake
函数。
步骤2:
void NativeMessageQueue::wake() {
mLooper->wake();
}
wake函数也非常简单,就一句话,调用C++层的Looper对象的wake函数,那我们就去看看它的实现。
步骤3:
void Looper::wake() {
...
ssize_t nWrite;
do {
nWrite = write(mWakeWritePipeFd, "W", 1);
} while (nWrite == -1 && errno == EINTR);
...
}
果然如我们所想的那样,就是调用linux函数write,对管道的写端文件描述符写入一个“w”字符,用于唤醒操作,一旦写入了字符,我们之前说的epoll_wait
函数就从阻塞中唤醒,做一系列操作最终处理新插入的消息,这样一来我们终于把整个本地实现过程走了一遍,也弄懂了与Java层之间的关系。
二、总结
说了这么多,终于搞明白了C++层的NativeMessageQueue和Looper与Java层之间的关联,说了这么多稍微简短的总结一下:(在目前已经分析的消息处理过程中)
C++的NativeMessageQueue就是用于在没有消息的时候进行阻塞(睡眠),而阻塞的方式与其C++层的Looper对象所创建的linux管道有关,如果管道没有写入数据,它就会利用epoll_wait
函数阻塞在管道的读端文件描述符上;当有消息产生的时候通过写入数据来进行唤醒操作。唯一的例外就是当没有消息但运行了空闲处理器,为了避免运行空闲处理的“真空期”这段时间的消息未及时处理,就不会阻塞,以及时处理消息。之前都说Java层的MessageQueue的next操作是一个阻塞操作,没有消息就会阻塞,其实现并不是利用无限for循环来进行的,而是利用了C++层的NativeMessageQueue与Looper,这点我一开始也误解了,无限循环的目的只不过是为了一直进行消息处理。
关于C++层的NativeMessageQueue和Looper我发现不止有这几个方法,还有很多别的用处,需要继续不断学习,如果了解到了我也会及时去总结。
如果觉得我的文章里有任何错误,欢迎评论指正!如果觉得写得好也欢迎大家留言或者点赞,一起进步、一起学习!