💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
当一切准备就绪后,Java层的消息循环处理,也就是Looper会在一个循环中提取并处理消息。消息的提取就是调用MessageQueue的next函数。当消息队列为空时,next就会阻塞。MessageQueue同时支持Java层和Native层的事件,那么其next函数该怎么实现呢?具体代码如下: **MessagQueue.java** ~~~ final Message next() { intpendingIdleHandlerCount = -1; intnextPollTimeoutMillis = 0; for(;;) { ...... //mPtr保存了NativeMessageQueue的指针,调用nativePollOnce进行等待 nativePollOnce(mPtr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); //mMessages用来存储消息,这里从其中取一个消息进行处理 final Message msg = mMessages; if (msg != null) { final long when = msg.when; if (now >= when) { mBlocked = false; mMessages = msg.next; msg.next = null; msg.markInUse(); return msg; //返回一个Message给Looper进行派发和处理 } else { nextPollTimeoutMillis =(int) Math.min(when - now, Integer.MAX_VALUE); } } else { nextPollTimeoutMillis = -1; } ...... /* 处理注册的IdleHandler,当MessageQueue中没有Message时, Looper会调用IdleHandler做一些工作,例如做垃圾回收等 */ ...... pendingIdleHandlerCount = 0; nextPollTimeoutMillis = 0; } } ~~~ 看到这里,可能会有人觉得这个MessageQueue很简单,不就是从以前在Java层的wait变成现在Native层的wait了吗?但是事情本质比表象要复杂得多,来思考下面的情况: - nativePollOnce返回后,next函数将从mMessages中提取一个消息。也就是说,要让nativePollOnce返回,至少要添加一个消息到消息队列,否则nativePollOnce不过是做了一次无用功罢了。 - 如果nativePollOnce将在Native层等待,就表明Native层也可以投递Message,但是从Message类的实现代码上看,该类和Native层没有建立任何关系。那么nativePollOnce在等待什么呢? 对于上面的问题,相信有些读者心中已有了答案:nativePollOnce不仅在等待Java层来的Message,实际上还在Native还做了大量的工作。 下面我们来分析Java层投递Message并触发nativePollOnce工作的正常流程。 1. 在Java层投递Message MessageQueue的enqueueMessage函数完成将一个Message投递到MessageQueue中的工作,其代码如下: **MesssageQueue.java** ~~~ final boolean enqueueMessage(Message msg, longwhen) { ...... final boolean needWake; synchronized (this) { if (mQuiting) { return false; } else if (msg.target == null) { mQuiting = true; } msg.when = when; Message p = mMessages; if (p == null || when == 0 || when < p.when) { /* 如果p为空,表明消息队列中没有消息,那么msg将是第一个消息,needWake 需要根据mBlocked的情况考虑是否触发 */ msg.next = p; mMessages = msg; needWake = mBlocked; } else { //如果p不为空,表明消息队列中还有剩余消息,需要将新的msg加到消息尾 Message prev = null; while (p != null && p.when <= when) { prev = p; p = p.next; } msg.next = prev.next; prev.next = msg; //因为消息队列之前还剩余有消息,所以这里不用调用nativeWakeup needWake = false; } } if(needWake) { //调用nativeWake,以触发nativePollOnce函数结束等待 nativeWake(mPtr); } return true; } ~~~ 上面的代码比较简单,主要功能是: - 将message按执行时间排序,并加入消息队。 - 根据情况调用nativeWake函数,以触发nativePollOnce函数,结束等待。 * * * * * **建议** 虽然代码简单,但是对于那些不熟悉多线程的读者,还是要细细品味一下mBlocked值的作用。我们常说细节体现美,代码也一样,这个小小的mBlocked正是如此。 * * * * * 2. nativeWake函数分析 nativeWake函数的代码如下所示: **android_os_MessageQueue.cpp** ~~~ static voidandroid_os_MessageQueue_nativeWake(JNIEnv* env, jobject obj, jint ptr) { NativeMessageQueue*nativeMessageQueue = //取出NativeMessageQueue对象 reinterpret_cast<NativeMessageQueue*>(ptr); returnnativeMessageQueue->wake(); //调用它的wake函数 } void NativeMessageQueue::wake() { mLooper->wake();//层层调用,现在转到mLooper的wake函数 } ~~~ Native Looper的wake函数代码如下: **Looper.cpp** ~~~ void Looper::wake() { ssize_tnWrite; do { //向管道的写端写入一个字符 nWrite = write(mWakeWritePipeFd, "W", 1); } while(nWrite == -1 && errno == EINTR); if(nWrite != 1) { if(errno != EAGAIN) { LOGW("Could not write wake signal, errno=%d", errno); } } } ~~~ wake函数则更为简单,仅仅向管道的写端写入一个字符”W”,这样管道的读端就会因为有数据可读而从等待状态中醒来。