提到Android开发中的Looper,许多开发者都会将其与消息机制划上等号,认为它负责在不同线程间传递和处理Message。这种理解固然正确,却未能触及它的核心本质。
深入其源码或观察系统行为,你会发现消息机制只是构建在Looper之上的一个高级功能。Looper真正的内核,是一个由epoll驱动的事件循环。它不仅处理Java层的消息,还能处理Native层的消息,并同时监听来自Input、VSync、Binder等多种事件源的文件描述符(fd)。
因此,一个看似简单却至关重要的问题是:Looper空闲时究竟在等待什么?又是如何被唤醒的? 追问下去,你会发现这远非一个单纯的Java问题,而是深入到epoll、调度与唤醒等操作系统内核原理的底层机制。
一切皆文件:Linux的事件抽象
这一问题的根源,可以追溯到Unix的核心设计哲学:一切皆文件。在这一思想下,Linux将各种“可等待的事件”抽象为文件描述符(fd),无论是网络socket、输入设备、定时器还是进程间通知。所谓的“等待”,本质上就是等待这些fd状态的变化。
在实际编程中,我们经常需要在一个线程中同时等待多个事件,即多路复用。Linux处理多路复用的机制经历了从select、poll到epoll的演进。select和poll在每次唤醒后都需要遍历所有被监控的fd,效率随监控数量增加而线性下降(O(n))。epoll的革新在于,每个就绪的事件会主动将自己加入一个就绪列表(ready list),被唤醒的线程只需遍历这个就绪列表即可,将效率提升至近似O(1),使得Linux事件驱动模型能够胜任高并发场景。
epoll的等待与唤醒机制
当一个线程调用epoll_wait()进入等待时,它会被阻塞,进入睡眠状态,并被挂入epoll实例的等待队列。
当某个被监控的fd状态发生变化(如变为可读),触发该变化的线程会通过fd对应的回调函数,将该事件添加到epoll实例的ready list中,并唤醒正在epoll_wait()上睡眠的线程。
内核层面的唤醒过程:
- 从等待队列移除:目标线程从
epoll的等待队列中移除。
- 加入运行队列:目标线程被放入CPU的运行队列(run queue)。
- 触发调度检查:调用
resched_curr()标记当前CPU需要重新调度。
- 可能的CPU迁移:在负载均衡阶段,调度器可能将该线程迁移到其他空闲CPU。
需要明确的是,唤醒并不等同于立刻执行。唤醒只是将线程状态标记为“可运行”,其获得CPU执行权的实际时机,取决于系统的调度策略和当前的CPU负载。唤醒是一种异步的触发行为。
当等待的线程恢复执行时,它会从内核空间取出ready list中的所有就绪事件(最多maxevents个),拷贝到用户空间,供上层逻辑分发处理。
Looper的epoll事件处理流程
对于Looper而言,每次MessageQueue.next()尝试获取下一条Java消息前,都会通过nativePollOnce()进入Native层的事件循环。底层的epoll_wait()在此等待或取回所有就绪事件,随后按序处理:

- 处理到期的Native消息:处理封装在
mMessageEnvelopes队列中的所有到期消息。
- 执行已就绪的fd回调:调用每个就绪fd注册的特定回调函数。
- 返回Java层分发消息:从
MessageQueue中取出下一条到期的Message并执行。
值得注意的是,无论是Java消息还是Native消息,它们共享同一个唤醒机制——一个特殊的EventFd:mWakeEventFd。这个fd同样被epoll监控。当任何一方有新消息需要唤醒线程时,就会向这个fd写入一个uint64_t类型的值1,从而触发epoll_wait()返回。
实际案例:Native消息与Fd回调
Native消息的应用
以动画结束为例。当底层渲染管线中的动画结束时,Native层的RenderThread会通过JniAnimationEndListener发送一条Native消息到目标Looper(通常是主线程)。主线程的Looper被唤醒后处理这条消息,在回调中通过JNI调用Java层的AnimatedImageDrawable.callOnAnimationEnd()。这种方式实现了渲染线程(Native)到UI线程(Java)的安全事件通知。
Fd回调的三种典型场景
Fd回调通常用于进程间的事件传递,以下是三个关键用例:
- Input事件:应用主线程监听来自
InputChannel的socket fd。当用户触摸屏幕,system_server的InputDispatcher写入事件,fd变为可读,唤醒Looper并调用InputEventReceiver回调,从而启动ViewRootImpl的输入事件分发流程。
- VSync信号:作为渲染节拍器,VSync信号通过
DisplayEventReceiver的fd传递。SurfaceFlinger的EventThread将生成的VSync事件写入此fd。fd就绪唤醒主线程Looper,触发Choreographer#doFrame(),开始新一帧的绘制。
- Binder通信:以
ServiceManager为例,这个核心守护进程采用单线程+Looper模型,将Binder驱动的fd也纳入epoll监听。当有Binder请求到达时,驱动标记fd可读并唤醒线程。ServiceManager的主线程随后从epoll中读取事件,通过handlePolledCommands()从驱动中读取并处理请求数据,这种高效的Android进程间通信机制是其核心基础。
通过剖析Looper与epoll协同工作的网络与系统底层原理,我们不仅能够理解Android消息系统的运行机制,更能洞悉其高效处理多种异构事件源的设计精髓。
|