Android Looper机制解析

192 阅读7分钟

0 导言

  本文将从Java层到Native层逐步解析Android的Looepr机制,并且以SystemServer为例子,从源码中剖析整个Looper机制。

1 为什么需要 Looper 机制?它解决了什么问题?

  在 Android 中,Looper 机制 是为了管理线程消息队列并实现线程间的高效通信。它主要解决以下核心问题:

  1. 多线程与UI操作的问题

  Android 的 UI 是 线程不安全 的,只有主线程(UI线程)可以更新 UI。如果子线程直接操作 UI,会导致不可预测的行为或崩溃。那么问题来了,子线程如何将数据结果安全地传递给主线程,并由主线程更新 UI?

  答案就是Looper 机制通过消息队列让子线程可以安全地将任务或消息发送到主线程,主线程接收后在自身上下文中处理。

  1. 消息调度与管理的问题

  多线程编程中,任务的执行需要按照一定顺序进行调度。如果没有消息队列,线程需要手动管理任务的同步、优先级等,容易出错。那么如何按顺序管理和调度线程任务?

  解决方法是Looper 提供了一个消息队列(MessageQueue), 能够按时间顺序管理任务,避免线程调度的混乱。

  1. 线程的生命周期与循环等待的问题

  在子线程中,任务往往需要持续等待新任务的到来(例如监听网络事件)。如果直接用线程阻塞的方式等待,会浪费系统资源。如何高效地让线程处于“等待新任务并处理任务”的循环状态呢?

  Looper 通过事件循环(loop)机制, 让线程高效地等待和处理新任务,而不会造成线程阻塞或资源浪费。

  1. 线程间通信的问题

  多个线程之间需要共享数据或相互通知任务完成,但直接使用共享变量容易导致线程安全问题。如何安全地在不同线程之间传递消息?

  解决方法是提供一个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。

image.png

  进入这个函数之后可以看到,里面有一个prepare()函数,它将实际创建一个Looper。

image.png

  prepare()函数里面new了一个Looper。

image.png

  这个Looper持有两个私有数据分别是new的MessageQueue和Thread获取的当前线程。

image.png

  而MessageQueue本身是在Native层建立的。并且我们可以看到最终在Native层又new了一个Looper。

image.png

  Native层的Looper建立过程将在下一节展开介绍。总结一下就是:Java层创建的一个Looper,实际最终指向的是Native层创建的Looper。

2.2 Looper.loop()

  咱们跳回到SystemServer.java代码中,继续往下看的话可以看到run函数的尾部有一个Looper.loop()函数的调用。

image.png

  loop()函数内部会有一个死循环for(;;),它内部通过loopOnece()函数来处理消息。

image.png

  loopOnece()函数的功能主要有两个:获取消息和分发消息。

image.png

image.png

  进入next()函数内部又有一个死循环for(;;),它内部调用了一个nativePollOnce()函数来完成一次消息循环,这一步又将进入到Native层进行处理。这一步将在native层获取到消息并保存到mMessages中,它是一个链表。

image.png

  nativePollOnce处理得到消息后,还要对消息进行筛选。判断是否设置了屏障消息,如果是则进行同步消息和异步消息的筛选,异步消息将优先的到处理,下面这部分代码体现了这一思想。

image.png

  当需要处理的消息处理完成时,消息队列会处于一个Idle状态,这个状态下我们可以利用起来让它做一些闲时事件(可恶!没活干的时候也要安排些杂活给你)代码中是这样体现的:

image.png

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 写入一个值,触发文件描述符变为可读,从而唤醒阻塞的线程。

image.png

  上面图中的rebuildEpollLocked()函数完成了epoll池的创建,以及将用于唤醒的mWakeEventFd加入到epoll池中。

image.png

3.2 Looper::pollOnce()

  进入pollOnce函数里面,又是一个死循环for(;;)。这个循环里面处理mResponses,那么这些响应是从哪里来的呢?

image.png

  继续往下看可以看到一个pollInner()函数,它的功能是产生mResponses。

image.png

3.3 Looper::pollInner

  pollInner函数进去之后会调用epoll_wait()进入等待事件状态,它会阻塞在这里,直到有事件产生。这里会在sendMessage后被唤醒。

image.png

  当有事件来之后,停止阻塞状态并进入事件处理的循环中。处理完事件将组装成mResponses数据,这些数据将在pollOnce中进一步得到处理。

image.png

  接下来将会处理Handler Message,注意这里处理的是Native端的Handler Message,和Java端没有关系。

image.png

  最后会处理 epoll 事件循环中的回调事件

image.png

3 Handler Message机制

3.1 Handler Message Java端

  Handler里面持有Looper、Callback和MessageQueue这几个比较重要的数据,可以从Handler中的构造函数中看出。

image.png

  通过Handler的sendMessage()函数可以将消息加入到消息队列中。最终会调用enqueueMessage函数来将消息加入到队列中去。

image.png

  在enqueueMessage()函数中,将要发送的消息插入到mMessages链表中。

image.png

  我们可以从这里分析什么情况下消息队列会被唤醒:第一种情况是消息队列为空或者新消息的when时间最早的情况下,需要唤醒阻塞的线程;第二种情况是队列被屏障阻塞,并且插入异步消息的情况下,需要唤醒线程,但是如果队列中已经有异步消息了,则无需再次唤醒。并且从中我们可以知道,屏障消息其实就是一个target为null的特殊消息。

3.2 Handler Message Native端

  在Native端,我们想要创建一个Handler需要继承MessageHandler,并且实现它的纯虚函数handlerMessage(const Message& message)。

image.png

  创建好MessageHandler后,接着初始化Message,它的构造函数如下:

image.png

  接着我们就可以通过sendMessage()来发送数据了,最终会调用sendMessageAtTime这个函数来发送数据。

image.png

  在sendMessageAtTime()中,主要是将handler和message封装成MessageEnvelope,并且根据时间顺序将其插入到mMessageEnvelopes队列中去。在pollInner()函数中会去处理这些数据。

image.png