0 导言
本文将从Java层到Native层逐步解析Android的Looepr机制,并且以SystemServer为例子,从源码中剖析整个Looper机制。
1 为什么需要 Looper 机制?它解决了什么问题?
在 Android 中,Looper 机制 是为了管理线程消息队列并实现线程间的高效通信。它主要解决以下核心问题:
- 多线程与UI操作的问题
Android 的 UI 是 线程不安全 的,只有主线程(UI线程)可以更新 UI。如果子线程直接操作 UI,会导致不可预测的行为或崩溃。那么问题来了,子线程如何将数据结果安全地传递给主线程,并由主线程更新 UI?
答案就是Looper 机制通过消息队列让子线程可以安全地将任务或消息发送到主线程,主线程接收后在自身上下文中处理。
- 消息调度与管理的问题
多线程编程中,任务的执行需要按照一定顺序进行调度。如果没有消息队列,线程需要手动管理任务的同步、优先级等,容易出错。那么如何按顺序管理和调度线程任务?
解决方法是Looper 提供了一个消息队列(MessageQueue), 能够按时间顺序管理任务,避免线程调度的混乱。
- 线程的生命周期与循环等待的问题
在子线程中,任务往往需要持续等待新任务的到来(例如监听网络事件)。如果直接用线程阻塞的方式等待,会浪费系统资源。如何高效地让线程处于“等待新任务并处理任务”的循环状态呢?
Looper 通过事件循环(loop)机制, 让线程高效地等待和处理新任务,而不会造成线程阻塞或资源浪费。
- 线程间通信的问题
多个线程之间需要共享数据或相互通知任务完成,但直接使用共享变量容易导致线程安全问题。如何安全地在不同线程之间传递消息?
解决方法是提供一个Handelr机制,Looper 配合 Handler,为线程间提供了一种安全、高效的消息传递机制,避免了同步和锁的复杂性。
这四个问题对应的解决方式其实就是Looper机制实现的四个模块:Looper::prepare()、MessageQueue、Looper::loop()和Handler。接下来的介绍中会不断提到这四个部分。
2 Android Java Looper机制
在aosp源代码中,我们经常可以看到Looper.prepare()函数和Looper.loop()函数。Looper.prepare()函数的功能是创建一个Looper,而Looper.loop()函数则是开启时间循环机制。下面将从SystemServer.java代码中详细介绍这些过程。
2.1 Looper.prepare()
在SystemServer的run函数中,可以看到Looper.prepareMainLooper()函数,该函数的功能就是为当前的thread创建一个Looper。
进入这个函数之后可以看到,里面有一个prepare()函数,它将实际创建一个Looper。
prepare()函数里面new了一个Looper。
这个Looper持有两个私有数据分别是new的MessageQueue和Thread获取的当前线程。
而MessageQueue本身是在Native层建立的。并且我们可以看到最终在Native层又new了一个Looper。
Native层的Looper建立过程将在下一节展开介绍。总结一下就是:Java层创建的一个Looper,实际最终指向的是Native层创建的Looper。
2.2 Looper.loop()
咱们跳回到SystemServer.java代码中,继续往下看的话可以看到run函数的尾部有一个Looper.loop()函数的调用。
loop()函数内部会有一个死循环for(;;),它内部通过loopOnece()函数来处理消息。
loopOnece()函数的功能主要有两个:获取消息和分发消息。
进入next()函数内部又有一个死循环for(;;),它内部调用了一个nativePollOnce()函数来完成一次消息循环,这一步又将进入到Native层进行处理。这一步将在native层获取到消息并保存到mMessages中,它是一个链表。
nativePollOnce处理得到消息后,还要对消息进行筛选。判断是否设置了屏障消息,如果是则进行同步消息和异步消息的筛选,异步消息将优先的到处理,下面这部分代码体现了这一思想。
当需要处理的消息处理完成时,消息队列会处于一个Idle状态,这个状态下我们可以利用起来让它做一些闲时事件(可恶!没活干的时候也要安排些杂活给你)代码中是这样体现的:
2.3 小结
至此已经讲解完了Java层面的Looper机制,但我们留下了两个问题在Native端。分别是如何在Native端创建一个Looper以及如何进行一次loopOnce。
3 Android Native Looper机制
进入到Native端,我们需要关注Looper.cpp这部分代码。
3.1 Looper::Looper()
在构建Looper中,有两样东西是非常重要的,一个是eventfd,另一个是epoll。eventfd 是用来唤醒线程的,epoll用于监听多个文件描述符(包括eventfd)。在 Looper 的事件循环中,线程会调用 epoll_wait() 进入阻塞状态,等待事件的到来。当需要让这个线程退出阻塞时,会向 eventfd 写入一个值,触发文件描述符变为可读,从而唤醒阻塞的线程。
上面图中的rebuildEpollLocked()函数完成了epoll池的创建,以及将用于唤醒的mWakeEventFd加入到epoll池中。
3.2 Looper::pollOnce()
进入pollOnce函数里面,又是一个死循环for(;;)。这个循环里面处理mResponses,那么这些响应是从哪里来的呢?
继续往下看可以看到一个pollInner()函数,它的功能是产生mResponses。
3.3 Looper::pollInner
pollInner函数进去之后会调用epoll_wait()进入等待事件状态,它会阻塞在这里,直到有事件产生。这里会在sendMessage后被唤醒。
当有事件来之后,停止阻塞状态并进入事件处理的循环中。处理完事件将组装成mResponses数据,这些数据将在pollOnce中进一步得到处理。
接下来将会处理Handler Message,注意这里处理的是Native端的Handler Message,和Java端没有关系。
最后会处理 epoll 事件循环中的回调事件
3 Handler Message机制
3.1 Handler Message Java端
Handler里面持有Looper、Callback和MessageQueue这几个比较重要的数据,可以从Handler中的构造函数中看出。
通过Handler的sendMessage()函数可以将消息加入到消息队列中。最终会调用enqueueMessage函数来将消息加入到队列中去。
在enqueueMessage()函数中,将要发送的消息插入到mMessages链表中。
我们可以从这里分析什么情况下消息队列会被唤醒:第一种情况是消息队列为空或者新消息的when时间最早的情况下,需要唤醒阻塞的线程;第二种情况是队列被屏障阻塞,并且插入异步消息的情况下,需要唤醒线程,但是如果队列中已经有异步消息了,则无需再次唤醒。并且从中我们可以知道,屏障消息其实就是一个target为null的特殊消息。
3.2 Handler Message Native端
在Native端,我们想要创建一个Handler需要继承MessageHandler,并且实现它的纯虚函数handlerMessage(const Message& message)。
创建好MessageHandler后,接着初始化Message,它的构造函数如下:
接着我们就可以通过sendMessage()来发送数据了,最终会调用sendMessageAtTime这个函数来发送数据。
在sendMessageAtTime()中,主要是将handler和message封装成MessageEnvelope,并且根据时间顺序将其插入到mMessageEnvelopes队列中去。在pollInner()函数中会去处理这些数据。