在 Android 系统中,存在两种基础的通信机制,跨进程通信通过 Binder 机制,而进程内部的通信则是通过消息机制,理解消息机制的原理在日常开发中非常重要,从 App 创建开始,到点击,滑动更新页面 ,这些操作都离不开底层的消息机制,因此,这篇我们来就说说 Android 平台的消息机制设计与实现,文章中的 AOSP 分析的源码是基于当前最新的 Android 12.0 版本
1 前提概述
1.1 需求
消息机制是一个比较常见的机制,各个操作系统都有,那如果是你,你会如何设计这个机制呢?我们不妨来想想,首先既然是设计消息机制,那总得有消息吧,有了消息,总得有存放消息的队列(消息队列)吧,那假设这两现在都有了,那咋用呢? 既然有了消息队列,那需要的时候,那就从消息队列取一个一个取出来消费不就完了(实现上就整个消息队列的循环呗),有消费那就要有生产啊,什么情况下生产呢? 当然是有需要的业务线程逻辑负责需求,谁来做消息的搬运呢?(插入消息队列),要设计个插入消息的执行者。
好了,消息队列有了(里面有一条条消息),那消息队列跑在哪里? 线程是最小的执行单元,肯定跑在线程上啊,那一个线程能否设计多个消息队列? 好像也没必要,一个线程就一个消息队列就好,每个线程只从一个地方取消息,新产生的消息也放到唯一一个消息队列中,这样确保简单。
总结下之前的想法,就是要设计一个线程唯一的消息队列,然后在该线程中将其循环跑起来,会有一个执行者不断的从消息队列中取出消息执行,当有需求时,执行者也可以插入到消息队列中,这样有消息生产者,有消息消费者,消息循环就可以正常运转起来了。
我们在想下几个细节:
- 如果消息队列为空了,没有消息了,怎么办?
- 直观的,消息队列没消息那就一直等到有消息来呗,那有消息来了,你消息队列怎么感知呢? 消息队列本身具备监听的功能吗? 靠什么来实现这个监听消息到来?从而可以从等待状态切回执行状态取消息,靠死循环轮询? 效率太差,所以这里需要一个高效的消息底层的唤醒机制,即当一个消息队列为空时,需要一种机制来唤醒通知消息此时到来了,起来继续拿消息执行,其实这里对应的就是
linux epoll机制 - 如果消息队列没有消息要执行,等待前能否做些清理工作呢?反正也是要闲着,不如趁着要闲着,做些其他的清理事情,这个时机就是
idleHanlder执行时机
- 直观的,消息队列没消息那就一直等到有消息来呗,那有消息来了,你消息队列怎么感知呢? 消息队列本身具备监听的功能吗? 靠什么来实现这个监听消息到来?从而可以从等待状态切回执行状态取消息,靠死循环轮询? 效率太差,所以这里需要一个高效的消息底层的唤醒机制,即当一个消息队列为空时,需要一种机制来唤醒通知消息此时到来了,起来继续拿消息执行,其实这里对应的就是
- 消息存在特权消息吗? 消息的执行顺序是按照时间顺序来的,都是要排队,一个一个执行的,既然有排队,那能插队吗?当前可以,可以插入到队首,那消息有分类吗?是否存在一类消息可以在某些情况跳过之前的,优先被执行?答案是有的,这就是
同步消息屏障
好了,上述我们所说的,就基本涵盖消息机制的原理,我们在逐一看下Android 中是怎么对应的
1.2 相关数据结构
Android 中,通过Message,MessageQueue,Looper,Handler 数据结构来完成实际的工作,整体组合关系如下:
Message: 就是最终需要处理的消息MessageQueue: 对应消息的容器,是一个消息队列,通过链表结构存储,通过它不断来获取消息,或是往里插入消息Looper: 字如其意,就是消息的循环体,它是对应到线程角度,算是 MessageQueue 的一个封装,线程中就是通过这个对象来开启消息的循环Handler:消息的打工仔,通过它发送消息和处理消息
从上面消息机制的设计和数据结构设计,我们大致可以知道整个轮廓,下面我们就通过实际的源码,来梳理下核心的流程
1.3 消息模型
其实Andoid中,消息机制的核心原理是非常简单的,非常好理解,类似于要把大象放冰箱分几步:
- 创建消息队列并开启消息循环
- 然后不断地产生事件发往到消息队列,然后进行处理
核心就这两步,Android 中为了编程方便,所以很多都封装好了,你如果看过Android官方文档 Looper 中关于消息机制的典型用法,一看就会用。
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare(); // 1. 准备好 looper 对象
mHandler = new Handler() { //2. 创建一个handler对象,并指明消息处理函数
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop(); //3.开始该线程内部消息循环
}
}
这个例子定义了一个线程,当线程被系统调度起来执行run()方法后,先调用 Looper.prepare() 方法准备好该线程的Looper 对象,然后在某个线程中初始化好一个 handler 并复写了一个处理消息的方法,最后调用 Looper.loop() 方法将消息队列循环起来,通过 mHanlder 发送和处理消息。
通过上面可以清楚知道,开发角度如何使用消息机制,下面针对源码分析时,我们还需要知道的一个linux 底层机制,就是 linux epoll机制,之前说过,消息队列的数据结构就是一个普通的单链表,当消息队列为空时,是无法感知消息队列插入消息,所以需要底层有一种通知机制 来保证,有消息到来了,要立即唤醒来执行,这就是linux epoll机制。
1.4 消息的分类
其实消息并没有严格的分类,都是根据需要执行的时间来排位置的,但是为了一些场合下,可以走绿色通道,优先某一类的消息执行,便产生出了"特异的消息",也就是异步消息,异步消息通常和同步屏障消息配合食用,基本分类如下:
- 消息分类:
- 同步消息(普通消息) : 放入到消息队列中,按照时间顺序逐一执行的消息
- 异步消息(
async msg): 和普通消息没什么差异,仅有一个flag被设置为异步,当有同步屏障被设置时,这类消息才会走"绿色通道",以便更快被执行
1.5 linux epoll 机制简介
linux epoll 机制,它是 linux 中最高效的多路 IO 复用机制,也就是一个老师( epoll 文件描述符fd ) 可以同时管十几个学生(需要监听的 event 事件),当某个学生有问题时,都可以举手问老师,老师可以回答问题(响应事件),它适用于大量并发少量活跃的情况下,使用 epoll 的步骤有3步:
-
首先通过
epoll_create()函数创建一个 epoll 文件描述符,参数 size 为能够监听的最大数量int epoll_create(int size); // 创建一个epoll文件描述符,size 为能够监听的个数 -
通过
epoll_ctl方法告诉 epoll fd 需要监听哪个文件描述符以及它的什么事件int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); -
调用
epoll_wait等待监控的事件到来,当有监控的事件发生,会放到 events 数据组中, timeout 为等待的时间,若未到来,则一直阻塞int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
其实对应就这个3个API,而这3个API 会出现在后面的源码分析中,这里有个眼熟就好,下面就进入到实际的流程分析中,这里仅会摘录核心流程,我们也仅需要知道核心流程,其他细枝末节的不会列出:
2. 核心流程分析
2.1 消息队列创建与初始化
消息队列的创建,核心就是初始化两个数据结构:
Looper对象MessageQueue对象
我们先来看 Looper 对象的初始化
2.1.1 Looper.prepare()
根据前面的消息的模型,可以看到,要初始化一个 Looper对象,其实就调用一个它的静态方法 Looper.prepare() 方法就可以了,我们看下它的实现细节
- Looper.prepare()
/frameworks/base/core/java/android/os/Looper.java
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {//1.一个线程只能对应一个looper对象,否则抛异常
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));//2.构造一个Looper存于线程私有变量中
}
private Looper(boolean quitAllowed) {//该对象实际内核是一个 MessageQueue 消息队列对象
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
从上面 Looper的初始化流程看,非常简单,几个信息:
- Looper的实质是消息队列,核心成员就是消息队列,所以
Looper的初始化,更核心的是MessageQueue的初始化 - Looper 是跟线程绑定的,一个线程仅有一个Looper(代码中会检查,之前创建过直接抛异常),这样建立一个统一的消息王国,实现上通过
线程私有变量操作
好了,既然Looper 对象的核心是 MessageQueue 的创建,我们在继续看 消息队列的初始化过程
2.1.2 MessageQueue 初始化
MessageQueue 构造函数如下
/frameworks/base/core/java/android/os/MessageQueue.java)
package android.os;
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();//调用 android_os_MessageQueue.cpp 中native方法初始化
}
private long mPtr; // used by native code
private native static long nativeInit();// native 方法
Message mMessages;//消息队列的head msg
从上面可以看到 MessageQueue 初始化核心是通过 JNI 方法初始化,初始化后,保留一个 mPtr 的指针在 Java层的 MessageQueue 对象中,我们在继续看下 nativeInit() 的代码,对应文件android_os_MessageQueue.cpp::android_os_MessageQueue_nativeInit()
/frameworks/base/core/jni/android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();//创建一个native 层消息队列
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
return reinterpret_cast<jlong>(nativeMessageQueue);//返回native层消息队列的指针
}
在 JNI 层也创建一个 native 的消息队列(和 Java层的消息队列没关系),然后返回一个 native层消息队列的指针存储在 MessageQueue.mPtr中,也就是 Java层的消息队列持有 native层的消息队列的指针,接下来我们看下 nativeMessageQueue的构造
/frameworks/base/core/jni/android_os_MessageQueue.cpp
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}
native 层消息队列中构造中,和 Java层一样,也会创建一个 Looper对象,将该对象存储在线程私有变量中,我们继续看下 native 层 Looper 构造
/system/core/libutils/Looper.cpp
Looper::Looper(bool allowNonCallbacks)
: mAllowNonCallbacks(allowNonCallbacks),
mSendingMessage(false),
mPolling(false),
mEpollRebuildRequired(false),
mNextRequestSeq(0),
mResponseIndex(0),
mNextMessageUptime(LLONG_MAX) {
mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));//初始化了一个 wakeEventfd 对象
...
rebuildEpollLocked(); //构建 epoll 事件
}
void Looper::rebuildEpollLocked() {
...
// Allocate the new epoll instance and register the wake pipe.
//1. 创建一个 epoll fd mEpollFd 对象
mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
...
struct epoll_event eventItem;// 构造一个监听事件描述
memset(& eventItem, 0, sizeof(epoll_event)); // 初始化 eventItem
eventItem.events = EPOLLIN; //监听读入事件,也就是当管道中有被写内容时,唤醒去读取
eventItem.data.fd = mWakeEventFd.get();
// 2. 将监听的wakefd 加入到 epoll fd 中,并且监听读入事件
int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);
...
好了,我们来总结下,目前已有的环境准备工作,
- 线程开始时,通过
Looper.prepare()初始化一个线程唯一的Looper对象, - 该Looper对象内部持有一个Java层的消息队列,在Java层消息队列创建时,又在 native层创建了一个 native 层的消息队列和native层的Looper, 同时准备好了epoll机制环境,添加了对应需要监听的管道事件
- Java层保存了 Native层的消息队列指针,以便后续操作
好了,消息队列环境准备好了,我们就可以让 消息队列循环起来了
2.2 消息循环
消息的循环,就通过 Looper.Loop() 方法,就将消息循环起来了,我们看下具体实现
2.2.1 Looper.Loop()
调用 Looper.loop()方法后,消息队列就开始循环跑起来了
/frameworks/base/core/java/android/os/Looper.java
public static void loop() {
final Looper me = myLooper();//1.获取该线程的 Looper对象
...
final MessageQueue queue = me.mQueue;//获取该线程的 MsgQueue
...//省略
for (;;) {//开始死循环获取msg
Message msg = queue.next(); // might block,取出下一条消息执行,没消息时阻塞等待
if (msg == null) {
// No message indicates that the message queue is quitting.
return; //退出消息循
}
...
msg.target.dispatchMessage(msg);//msg.target 就是 handler,通过 handler 派发出去
...
msg.recycleUnchecked();// msg 是一个消息池,用完后回收
}
}
整个逻辑比较简单,
- 通过
myLooper()方法获取到Looper对象(存储在线程私有变量中),取到消息队列 - 然后就是死循环该消息队列(链表组成),不断的取消息(
queue.next())执行- 如果消息队列没有消息,则会等待消息到来,阻塞在
queue.next()方法 - 当有消息时,通过 Handler 进行分发出去,
msg.target就是一个Handler对象,后面会讲到,发送出去之后,对消息进行回收。
- 如果消息队列没有消息,则会等待消息到来,阻塞在
消息的循环的核心就是 MessageQueue.next() 方法,我们后面重点会分析这个函数,到此,消息队列的循环就跑起来了,我们下来在看下,消息的发送,处理和获取流程
2.3 消息收发处理流程
消息的发送 和 处理,都是通过 Handler 完成,我们先看下 Handler的构造
2.3.1 消息处理者 Hanlder 构造
Handler 的构造函数有好几个,但核心就是拿到该线程对应的 Looper对象,有了 Looper对象就有了消息队列,这里以默认构造函数为例看下流程
frameworks/base/core/java/android/os/Handler.java
public Handler() {
this(null, false);
}
public Handler(@Nullable Callback callback, boolean async) {
...
mLooper = Looper.myLooper(); // 通过 Looper.mylooper静态方法获取当前线程的消息队列
if (mLooper == null) {//若没有获取到,则说明之前没有初始化过,抛异常
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//赋值其他变量
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
通过 Handler 可以绑定到一个线程的 Looper 对象和消息队列,接下来就利用 Handler 来收发消息了
2.3.2 Handler.SendMessage()
- Handler.SendMessage() --> Hanlder.sendMessageAtTime()
发送消息,一般通过 Handler.sendMessage 方法发送,但最终会调用到 Handler.sendMessageAtTime() 方法,如下
frameworks/base/core/java/android/os/Handler.java
//发送消息,最终将消息入到消息队列中
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue; //获取消息队列
...//处理异常
return enqueueMessage(queue, msg, uptimeMillis);//消息插入消息队列
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this; // 这句非常重要,这里将 handler的赋值给了 msg.target,以便能让 handler来处理该消息
...
return queue.enqueueMessage(msg, uptimeMillis); //让该消息入队列
}
可以看到,实际上发送消息,就是通过 Handler 往线程的消息队列中插入一个消息而已,上面需要注意的是:
enqueueMessage()时,msg.target本身就是个handler对象,也就是说,发送消息入队列时,就要指定该消息被哪个 handler 执行,后面派发消息处理时,就会通过该 handler 来处理消息
接下来我们继续看下消息插入队列的过程
2.3.3 消MessageQueue.enqueueMessage()
/frameworks/base/core/java/android/os/MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
...//异常处理
synchronized (this) {
...//异常处理
msg.markInUse();
msg.when = when;
Message p = mMessages;//获取当前消息队列
boolean needWake; //是否要唤醒消息队列
if (p == null || when == 0 || when < p.when) {// 分支[1]
// p=null 表示消息队列为空,该消息为首条消息,when=0 表示该消息是插入队首的消息,when<p.when 说明该消息的执行时间应该立刻执行了
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked; //mBlocked为true则表示此时消息队列获取端时阻塞了,而此时的消息应该被立即获取执行
} else { //分支[2] 正常插入消息到队列的常规case
needWake = mBlocked && p.target == null && msg.isAsynchronous();//这句是指消息队列读取端阻塞时(没可用消息)并且当前队首的消息为同步屏障消息时,假如本条消息又为异步消息,那么需要唤醒
Message prev;
for (;;) {// 按照msg执行时间插入到对应的位置,单链表插入操作
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
//参考上面needwake 为true的条件,当needwake=True时,当前插入的msg一定是异步的消息,而此时p为遍历过程中的消息,也就是若该条件为True,当前插入的msg不是第一条异步消息,没必要立即唤醒
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {//分支[3] 假如需要唤醒,就立即唤醒
nativeWake(mPtr);
}
}
return true;
}
我们来仔细看下这个过程,本质是往一个消息队列(单链表)中插入一个消息,消息的顺序是通过执行时间when 确定的:
-
获取消息队列
mMesages,并给消息打上执行时间戳when,定义一个needWake变量来确认是否要唤醒消息队列可立即取消息,之前设计部分有讲到过,消息队列本身是一个链表组成,要怎么打通通知机制,也就是某些情况下(比如首条消息插入后)要如何通知消息队列立即去获取when=0表示要该消息要插入到队首,例如通过handler.sendMessageAtFrontOfQueue时赋值when为 0
-
接下来分支[1]:当消息队列为空时,或者该消息为要插入到队首的消息时,又或者是执行时间比消息队列队首消息还早(也就是要最先被执行)时,将当前消息插入到队首上,并且赋值
needWake为mBlocked的状态,通过该状态来表示是否要立即唤醒,这样另一端读取消息的地方,可被唤醒开始读取到消息了mBlocked成员变量就是用来标记消息队列的读取是否阻塞了,它被赋值的地方就是MessageQueue.next()方法中,从消息队列获取消息时,此时没有可被执行的消息且无空闲要做的事,会被设置为true,后面分析该方法时会重点讲到
-
分支2:这是大部分时候的路径,往消息队列中插入一个消息,通过时间
when来找到正确的链表位置,然后插入进去 -
分支3:表示要立即唤醒阻塞的消息队列,这样另一端在等待取消息的地方则会立即返回,可取出一条消息执行了,这里的
nativeWake逻辑,我们后面再分析
下面我们看下,消息是如何逐条取出的,我们在回到 Looper.looper() 方法那里,这里通过 MessageQueue.next() 方法获取一条消息
2.3.4 取消息 MessageQueue.next()
这个函数为整个消息机制的核心,核心目的就是从消息队列中取出一条信息来执行,若消息口袋”空空如也“时,也即消息队列为空或没有消息则会阻塞,等待被唤醒,或者做些空闲的清理工作,我们逐一解释下这个函数流程:
/frameworks/base/core/java/android/os/MessageQueue.java
Message next() {
...
final long ptr = mPtr; //native底层的指针,用来调用native层消息
...
//刚开始迭代时初始化参数
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0; //首次默认不等待
for (;;) {
...
nativePollOnce(ptr, nextPollTimeoutMillis);//等消息,时间为nextPollTimeoutMillis 的值,函数进入时该值为0,不会等待
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//【分支1】若该要取出的消息为同步屏障消息时,则取出第一条标记为异步的消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {// 【分支2】msg为可取的消息
if (now < msg.when) {//假设该消息为延迟消息,则计算还要等待的最大timeout时间
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {//立即要执行的消息
// Got a message.
mBlocked = false; //因为此时已经有成功取出一条消息了,此时非blocked 状态,无须唤醒
if (prevMsg != null) { //从链表中删除掉该消息
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg; // 取到了消息,函数立即返回
}
} else {// 如果没取到需要的消息,设置无限等待标志(-1)
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// 逻辑能走到这里,说明没取到符合的消息,只能等,那等之前能做些清理工作呗
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
//若消息队列为空或者首条消息未到执行时间,获取 idlerHandler的大小
pendingIdleHandlerCount = mIdleHandlers.size();
}
//若此时未有pengdingIdleHanlder的任务,说明没有空闲执行的任务,那就直接等下一条消息到来
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true; //阻塞设置为 true
continue; // 直接下一轮,实际是直接应用 nextPollTimeoutMillis 来做等待
}
//下面是有pending的空闲任务,则逐一回调执行,
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// pending 任务做完了,立即设置 空闲任务为0,且等待时间为0,因为可能现在消息队列可能有消息了,直接查看
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
我们总结下关键点:
-
nativePollOnce函数为核心函数,没有消息则会阻塞等待,通过nextPollTimeoutMillis变量控制最大等待时间,核心逻辑是通过这个变量的值确定等待时间,发送延迟消息或者一直等待消息(-1),首次迭代进入的时候,不会等待 -
先确认当前消息是否为
同步屏障消息,若是,则会取出第一条异步消息,【分支1】逻辑 -
然后在确认当前消息执行时间是否到了,没到则设置timeout的 ,赋值给
nextPollTimeoutMillis做下一次"卡位" 等待操作,若是,则正常取出消息,将该消息从链表中删除掉,然后返回- 消息列表基于单链表实现,所以取出并移除头部消息,会用到两个变量 preMsg, mMessages
-
若未取到消息时或者需要执行的消息时间还未到,此时主线程完全是空闲状态,若app有注册
idlehandler接口,则会利用这个空闲时间做回调 ,开放给app做清理,一个实际例子可 参考创建方式: 添加入 IdleHandler 方式
Looper.myQueue.addIdleHandler(new MessageQueue.IdleHanlder() {
@Override
public boolean queueIdle() {
//do something
return false; //false表示执行一次后移除,ture下次msg为空继续回调
}
})
接下来我们在看下 Looper.looper()中取出了一条消息后,是如何派发的
2.3.5 发消息 Handler.dispatchMessage()
在 Looper.loop() 方法利用 msg.target.dispatchMessage(msg) 来派发处理消息,处理优先级为 handler.post方法 -> handler构造callback -> 复写handleMessage方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) { //使用 message.callback.run(),callback 本身为 runnable 类型,其实就是通过 Handler.post(runnable) 方法发送的消息
handleCallback(msg);
} else {
if (mCallback != null) {//使用 Handler(callback)构造
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg); // 回调Handler:handleMessage处理
}
}
按照上面的优先级, Handler:post(runnbale)方式, 将 runnbale封装为msg,然后入队列,执行的时候首先处理。
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r; //将 runable 赋值给msg属性
return m;
}
下面我们在看下消息阻塞与唤醒的流程,我们先看下 nativePollOnce的实现原理
2.3.6 消息阻塞 MessageQueue.nativePollOnce
JNI 层的方法,最后调用的是 native looper 的 pollOnce(timeout)方法,参数就是需要等待的最大 timeout 时间
frameworks/base/core/jni/android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mPollEnv = env;
mPollObj = pollObj;
mLooper->pollOnce(timeoutMillis);//调用 native 的 pollOnce 方法
mPollObj = NULL;
mPollEnv = NULL;
...
}
/system/core/libutils/Looper.cpp
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
while (mResponseIndex < mResponses.size()) {
const Response& response = mResponses.itemAt(mResponseIndex++);
int ident = response.request.ident;
if (ident >= 0) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - returning signalled identifier %d: "
"fd=%d, events=0x%x, data=%p",
this, ident, fd, events, data);
#endif
if (outFd != nullptr) *outFd = fd;
if (outEvents != nullptr) *outEvents = events;
if (outData != nullptr) *outData = data;
return ident;
}
}
if (result != 0) {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - returning result %d", this, result);
#endif
if (outFd != nullptr) *outFd = 0;
if (outEvents != nullptr) *outEvents = 0;
if (outData != nullptr) *outData = nullptr;
return result;
}
result = pollInner(timeoutMillis);//调用的是 looper.pollInner方法
}
}
Looper.poInner 方法实质就是调用epoll wait方法来监听事件,当之前监听的事件到来的时候,就读取里面的内容,然后返回,Java 层就可以往下去获取消息执行了
int Looper::pollInner(int timeoutMillis) {
...
// Poll.
int result = POLL_WAKE;
...
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);//使用 epool_wait方式监听加入监听的事件,没有就阻塞,返回值是监听的事件到来的个数
...//错误处理到 done处
// Handle all events.
...
for (int i = 0; i < eventCount; i++) {//遍历监听到的事件数组
int fd = eventItems[i].data.fd; // 获取 fd
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeEventFd.get()) { //如果 监听的事件是 当时设定的 wake fd事件
if (epollEvents & EPOLLIN) {//监听的是读操作
awoken(); //awoken的实质是去 去读监听事件的内容
} else {
...
}
} else {
...
}
Done: ; //处理 done的逻辑
// 先处理 native 层的 消息
...
// 在处理 respone
...
}
void Looper::awoken() {
...
uint64_t counter;
TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));//awoke的实质是去读取管道中的内容
}
所以从上面流程可以看到,MessageQueue.next() 中未有消息时block时,其实是通过 MessageQueue.nativePollOnce() 方法一路调到 native层,通过 epoll wait 方法进行等待的,在timeout 时间内,监听指定管道内容,若此时写入管道内容时,那么就会立即读出,唤醒返回
2.3.7 消息唤醒 MessageQueue.nativeWake()
/frameworks/base/core/java/android/os/MessageQueue.java
private native static void nativeWake(long ptr);
这里对应的 JNI 方法如下:
/frameworks/base/core/jni/android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake(); //调用 nativeMsgQueue的 wake 方法
}
void NativeMessageQueue::wake() {
mLooper->wake(); // 调用 native 的 looper 方法
}
其实就是往对应的管道中,写入一个 1 ,另一端监听的就可以读取出来,从而可以返回不卡主了
/system/core/libutils/Looper.cpp
void Looper::wake() {
...
uint64_t inc = 1;
// wake方法其实就是向wakefd 里面写了一个 1 进去,另一端epoll机制会监听这有内容可以读取
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
...
}
好了我们总结下,如何阻塞和如何唤醒消息队列的:
- 阻塞端: 当
MessageQueue.next()中因没有消息可取的消息时,会通过MessageQueue.nativePollOnce(timeout)做阻塞,参数为 timeout 时间,内部为通过 epoll 机制wait等待管道另一端写入数据 - 唤醒端: 当
MessageQueue.enqueueMessage()在入队列时,如果当前是消息队列为阻塞状态(needWake为True) 时,会通过MessageQueue.nativeWake()方法唤醒,实质也是往对应的管道写入一个1,然后另外一监听端可以读出
所以底层就是通过 epoll 机制来做到监听和通知的
2.4 消息屏障 barrier
最后我们再来仔细说下同步消息屏障 ,从之前的源码可以看出,同步屏障消息也就是一个消息而已,只是这个消息的标志为 msg.target=null,也就是说不需要处理,只是用来做 flag 使用。它的使用场景就是专门开辟的优先通道,当取出的消息为 同步屏障消息时,就会过滤掉普通消息,
2.4.1 如何发送异步消息
(1) 创建消息msg后,使用 Message.setAsynchronous(true) 对该消息设置异步
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
(2)构造异步 Handler 发送消息 : Handler 有个成员 mAsynchronous ,通过构造会设置这个值,后续通过该hanlder发送的都将是异步消息
/frameworks/base/core/java/android/os/Handler.java
public Handler(boolean async) {
this(null, async); // 设置 mAsynchronous 成员
}
//当通过Hanlder 发送消息时,会检查是否为异步消息,从而将该消息标记为异步消息
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this; //正常消息发送,都要赋值 target
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true); //设置 msg.flags |= FLAG_ASYNCHRONOUS; // 非0
}
return queue.enqueueMessage(msg, uptimeMillis); //正常插入到到消息队列中
}
2.4.2 异步消息何时启效果
只有当被设置了同步屏障时,异步消息才能走绿色通道,如何设置屏障Barrier ? 我们看下系统端是如何使用的,在 ViewRootImpl.java 中当需要更新画面时,会调用到 scheduleTraversals() 这个函数会做设置。代码如下:
/frameworks/base/core/java/android/view/ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//通过MessageQueue.postSyncBarrier()发送 同步屏障消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
发送同步屏障消息,本质就是往消息队列中发送一条消息,只是这个消息的特殊 flag, target=null 而已,意味着仅仅是一个标志,在 Message.next() 方法中有检查该标志,从而使得异步消息先被取出到
/frameworks/base/core/java/android/os/MessageQueue.java
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
// 此函数为 private
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token; // token传递
//按照时间正常插入一条 msg,并无特别,但是这个消息是没有 target字段的赋值的,也就是无需被handler处理,仅用来特殊表示
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
// 被设置了同步屏障后的消息,当被取出时,会自动过滤掉普通消息,直接获取到首条异步消息
Message next() {
...
for (;;) {
...
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//当此条msg为同步屏障消息时 (msg.target=null),会过滤到所有普通消息,指向最近的一条异步消息
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous()); // 找到首个异步消息
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
....
}
}
3 总结
通过设计和源码分析过程,我们应该清楚了消息的机制的原理了,消息循环机制基于 Java层和Native层配合工作,Java层和Native层通过消息队列MessageQueue 连接,而消息的等待,唤醒则是通过 native层的 looper 执行的,handler 游走子线程和主线程之间,不断向绑定线程中的消息队列发送消息和处理消息。
- 消息队列
Looper对象 在 Java 层 和 Native 都对应有创建 - Java 层消息队列无消息时阻塞,有消息来时,则会被唤醒起来取消息派发执行,底层原理就是通过 native 的 Looper 操作,利用
epoll 机制(一个线程去写值1,触发写入操作,另个线程则一直在监听该事件有值可读取了,唤醒后取出消息执行 - 消息的”优先级"设置,配合同步消息屏障和异步消息使用
Java层的消息机制,围绕3个类展开:
- Handler: 线程间消息的发送者和处理者,创建时会绑定到某个Looper对象
- Looper:每个线程拥有一个该对象,内部有一个消息队列,不断的循环消费消息
- MessageQueue: 一个基于链表实现的消息队列,排序为插入队列的时间
各个类的在总体核心方法如下: