GUI系统几乎都是基于消息循环的,从AWT到Swing, 从Android到iOS,UI开发可以说离不开这个概念了。

这个模型给予事件分发,界面绘制的处理带来了极大的方便,下面从原理的角度分析下Android的消息循环系统是如何实现的。

Android 的消息循环机制有几个个重要的角色Looper,Handler,MessageQueue,Message。 他们的关系如下:

其中最关键的MessageQueue,它可以说是这个机制的核心,也是本文分析的重点,我们下面从MessageQueue的角度看下如何实现消息循环。

首先要准备几个问题:

  • 当没有Message的时候,系统是如何工作的?

  • 当收到Message时候,系统进行了那些处理?

  • Message是怎么延时执行的(Handler.postDelay)?

下面开始逐一进行分析。

当没有Message的时候,系统是如何工作的?

先回答下如果没有Message,或者Message的执行时间没到(message.when>now),当前持有MessageQueue的线程会进入阻塞状态。 MessageQueue中有两个关键方法,next()和enqueueMessage()。

先说下next(),next()是MessageQueue取出消息的核心,我们看下如何实现的。

关键点在于调用nativePollOnce和调用时传入的timeout参数。 我们下看下传入的timeout参数的值是什么。

首先就可以看出,第一次调用nativePollOnce时传入的参数是0,这里可以先说下,传0的话nativePollOnce会立刻返回,因为epoll_wait接收0会直接返回,不会阻塞。

然后处理异步消息,MessageQueue中有个同步屏障的概念,如果一个Message的target是null,它就是一个消息屏障,只会执行异步消息,中断所有的普通消息,这个在requestLayout的过程中有用到,以后有时间在分析吧,先了解下。

然后计算timeout时间,timeout时间等于下一个message的时间减当前时间,见这行代码。 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);

然后这个时间就是下次nativePollOnce调用的参数了。

如果MessageQueue已经遍历到队尾了,那么这个nextPollTimeoutMills的值就是-1,-1就是在epoll_wait无限阻塞的意思,直到有监听的fd触发响应,如果传入-1了,那么当前线程就会阻塞,直到有人调用了MessageQueue的enqueueMessage(),稍后分析。

然后看下nativePollOnce的实现。

这里我们画个图看下调用,然后对着代码分析。

我们看下这个NativeMessageQueue是个什么,这个NativeMessageQueue应该是随Java层的MassageQueue一起构造的。

NativeMessageQueue的时候回获取一个Looper,这个Looper是Native层的Looper

我们看下Native层的Looper定义

这个Looper的定义比较多,我们看下重点内容,这个Looper具有发送Native层消息的功能比如使用方法void sendMessage(const sp<MessageHandler>& handler, const Message& message)

还可以使用Looper监听fd,NDK开发中,Android给我们提供了ALooper进行包装,本质其实是一样的,我们稍后介绍。

addFd,removeFd之类的方法就是对fd之类的监听。

到此,我们可以看出类关系如下,有Java层的,有Native层的,这里一并画了。

好,我们继续看NativeMessageQueue的pollOnce方法

NativeMessageQueue又调用到了Looper的pollOnce方法,可以看出,Native层的MessageQueue最终还是依赖于Native层的Looper。

继续跟踪Looper的pollOnce

继续跟踪pollInner

我们可以看到,pollInner真正的调用了epoll_wait去监听fd的变化,如果观察到fd变化或者超时,那么epoll_wait就会返回,这个正好表示线程从阻塞开始唤醒。

epoll_wait之后,处理fd也是有先后顺序的。 我画了个图,这样看起来比较清晰。

从图中可以看出,如果收到fd响应后,会将fd包装到mResponses中,等待执行。

然后是先处理的NativeMessage,没有可以处理的NativeMessage后,才会去处理fd。

Message的定义和Java层的非常像

最后是处理响应的fd了,这个也很简单。

其实就是调用了callback->handleEvent。 pollInner逐层返回就调用到nativePollOnce了,至此nativePollOnce分析完毕。

总结下,MessageQueue在next()中如果下个消息的时间没到,或者没有消息了,就会进入阻塞状态,阻塞状态根据传入的timeout决定时间,可能是具体的间隔时间或者是-1(永远阻塞)。

当收到Message时候,系统进行了那些处理?

假设当前线程已经阻塞了,这个时候,别的线程往MessageQueue中放入了消息,会调用到MessageQueue的enqueueMessage(),这个时候可能会把MessageQueue给唤醒。

可以看出,如果入队时MessageQueue没有消息,或者需要执行的时间小于当前Message的时间,那么可能需要唤醒。 是根据’mBlocked’决定的,mBlocked是在next()中进行赋值,如果没有可以执行的Message和IdleHandler,那么mBlocked就是true,enqueueMessage()时需要将线程唤醒。

我们看下nativeWake()的实现

jni层只是包装,核心还是NativeMessageQueue的wake(),NativeMessageQueue的wake会直接调用Looper的wake()

可以看出,只是向mWakeEventFd中写入了一个1,然后就唤醒了。 其实这是利用了epoll机制,epoll监听了这个mWakeEventFd类型,如果mWakeEventFd变得可读时,epoll_wait会立即返回,当前线程就被唤醒处理消息了。

mWakeEventFd是一个eventFd类型文件,pollOnce时,eventFd是无法读取的,只有等到别的线程或者进程往fd里写入数据,eventFd才可以读取,这个特性正好和epoll_wait结合起来,实现了一套阻塞唤醒机制。

epoll监听mWakeEventFd的逻辑在Looper的构造方法中。

Message是怎么延时执行的(Handler.postDelay)

这个问题分析到现在已经很简单了,Handler在postDelay的时候会用当前时间加上delay的时间作为Message的when字段。

然后在enqueueMessage中将该Message插入到何时的位置,如果需要唤醒,则唤醒。其实就是一个简单的遍历链表,找地方插入。

然后MessageQueue中有了该Message(已排好顺序),Looper会一直取Message,等待执行就可以了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注