消息机制
Android异步,线程间通信是消息机制,App架构都是基于消息,ActivityThread类main方法,主线程启动一个Looper和消息队列,组件启动,视图绘制通过主线程从消息队列获取到消息通知实现。
生产者-消费者线程模型,消费者从队列获取,消息处理,同时实现休眠+唤醒,生产者将消息插入队列,实现生产者线程对消费者线程的通知。
一个线程,一旦启动Looper,run方法将不会结束,在ActivityThread的main方法,最后一句Looper.loop(),启动主线程Looper。App主线程会一直运行,(这里的运行指执行和休眠),否则,主线程挂掉将抛出异常。
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
消息机制关键技术是消费者都休眠和唤醒,当消息队列是空时,消费者休眠,防止空跑,占用CPU资源,当消息队列不是空时,消费者唤醒。根据这种设计需求,两个线程+一个阻塞队列可以实现,即利用Java中Condition的await和signal机制。
Android采用Native层epoll方案实现。Android消息系统一共分两层,Java层和Native层,Java层的类,Handler类,负责消息创建、发送、回调,Message类,具体消息体对象。MessageQueue类,消息队列,负责维护和控制消息吞吐。Looper类,负责执行线程循环。
Native层的类,NativeMessageQueue类,Looper类,负责线程休眠唤醒。
消息队列和Looper
启动Looper时,首先调用Looper的prepare方法,创建Looper对象。
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
ThreadLocal类确保每个线程有一个Looper实例,线程间Looper对象不会相互影响。主线程Looper创建是prepareMainLooper方法,在Looper类的sMainLooper静态对象,未使用ThreadLocal类存储。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
创建消息队列,主线程消息队列不允许quit退出。
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
JNI方法,初始化Native层NativeMessageQueue对象,返回mPtr指针,创建Native层Looper。
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);//创建底层Looper实例。
Looper::setForThread(mLooper);
}
}
Native层的Looper对象线程唯一性,它是消息系统的核心,采用epoll机制,实现线程休眠和唤醒。
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
mWakeEventFd = eventfd(0, EFD_NONBLOCK);//epoll监听这个句柄。
AutoMutex _l(mLock);
rebuildEpollLocked();
}
初始化一个文件描述符,rebuildEpollLocked方法,创建epoll句柄,注册事件。
void Looper::rebuildEpollLocked() {
...
//创建epoll句柄
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event));
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
//监听mWakeEventFd
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
for (size_t i = 0; i < mRequests.size(); i++) {
const Request& request = mRequests.valueAt(i);
struct epoll_event eventItem;
request.initEventItem(&eventItem);
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem);
}
}
epoll_create方法,创建mEpollFd句柄,epoll_ctl方法,向句柄注册内核监听的描述符,包括流,管道,文件描述符等。
Native层Looper内部包含一个epoll_ctl方法注册的mWakeEvenFd描述符,epoll机制实现在Linux内核监听多个描述符事件。
Handler类
在Java层,Handler类是消息创建和回调工具类。
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Handler绑定消息队列和Looper,构造参数可以指定Looper对象。
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
Handler发送消息,向消息队列插入消息。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
将消息target设置成Handler本身,调用消息队列enqueueMessage方法。任何线程都可以通过它发送消息到内部消息队列,队列由消费者线程访问处理,实现任何线程向消费者线程的消息通知。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
消费者线程Looper.loop()等待,从消息队列获取消息,从消息获取target(发送者Handler)回调。消息处理优先级,消息体Callback,Handler内部Callback,重写handleMessage方法。
工作原理
启动Looper,消费者线程将在Looper.loop()循环,通过消息队列next方法,等待消息。
public static void loop() {
//当前线程Looper。
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
//无限循环
for (;;) {
Message msg = queue.next(); // 这里有可能休眠
if (msg == null) {
// 没有消息,代表消息队列已经退出,将结束循环。
return;
}
//Message消息处理
try {
msg.target.dispatchMessage(msg);
} finally {
}
...
}
}
当消息队列是空时,MQ.next()方法将进入休眠,
Message next() {
final long ptr = mPtr;
//底层消息队列已经销毁,直接退出循环。
//Looper.loop收到null时,也将退出for循环,结束线程。
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;//第一次进入时,不设置休眠等待时间。
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//JNI方法,底层休眠,第一次循环休眠时间是0。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 如果队列第一个是同步栅栏消息,则跳过后续的同步消息,直接找到异步消息执行,确保异步消息的优先级高。
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
//若不是同步栅栏,msg就是队列的第一个消息。
if (msg != null) {
if (now < msg.when) {
// 发现此时还未到消息的执行时间,设置差值,将继续循环,休眠差值时间。
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 此刻已经到达消息设定执行时间,消息返回给loop方法。
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 若消息队列已经空了,设置无限等待休眠,直到手动唤醒,在插入时。
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
...
}
...
}
}
JNI#方法,nativePollOnce方法,第一个参数Native层消息队列指针,第二个参数nextPollTimeoutMillis休眠时间,根据当前时间和队列中消息delay延迟执行的目标时间,计算nextPollTimeoutMillis,通知Native层Looper,最终调用Native层Looper的pollInner方法。
int Looper::pollInner(int timeoutMillis) {
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
//休眠
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
//遍历事件数量
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeEventFd) {//若是mWakeEventFd句柄发生的事件,如向其写入了数据。
if (epollEvents & EPOLLIN) {
awoken();
}
}
}
Done: ;
// 处理mMessageEnvelopes中的message,拿到handler。
// 这里面的消息是在底层发送的,底层Looper#sendMessage方法。
mNextMessageUptime = LLONG_MAX;
while (mMessageEnvelopes.size() != 0) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
if (messageEnvelope.uptime <= now) {
{ // obtain handler
sp handler = messageEnvelope.handler;
Message message = messageEnvelope.message;
mMessageEnvelopes.removeAt(0);
mSendingMessage = true;
mLock.unlock();
//执行MessageHandler的handleMessage消息。
handler->handleMessage(message);
} // release handler
mLock.lock();
mSendingMessage = false;
result = POLL_CALLBACK;
} else {
mNextMessageUptime = messageEnvelope.uptime;
break;
}
}
mLock.unlock();
return result;
}
epoll_wait方法,线程休眠。第一个参数mEpollFd,epoll_create方法创建的epoll句柄,第二个参数eventItems,监听的事件集合,第四个参数,休眠时间,返回值eventCount,监听到的事件数量。
举例,Java层消息队列第一个消息的执行时间when超过当前时间1分钟,表示消息延迟执行,间隔1分钟,通过JNI的nativePollOnce方法,通知Native层Looper在epoll_wait方法处休眠1分钟。
自动唤醒,一旦设定休眠时间,到达时将自动唤醒,继续执行,遍历事件数量,回到上层消息队列获取消息。因休眠时间是经上层计算的,这次回去会达到第一个消息的执行时间。
非自动唤醒,如果是mWakeEventFd描述符事件,代表监听到事件,不是自动唤醒,是Java层插入消息的唤醒,调用awoken方法。
当上层消息队列是空时,设置timeoutMillis值是-1,epoll_wait方法的唤醒时间不确定。
最后,在Done代码块,处理Native层Looper的sendMessage方法发送的消息。
总结:
Looper.loop()方法,不会一直占用CPU资源,当消息队列是空时,不设定休眠唤醒时间,epoll_wait方法一直休眠。
消息队列根据消息执行时间when控制Native层Looper休眠唤醒间隔,一旦Looper启动,MQ.next()方法将一直做这件事情。
在Native层,Looper的pollInner方法并非只通过上层调用,其他需要利用Looper唤醒休眠机制的Native层位置也触发此方法,回调将由Native层MessageHandler负责。
Handler类调用MQ.enqueueMessage()方法,向队列插入消息,是另一种唤醒方式。
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) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//若第一个消息是同步栅栏(即没有派发目标),且插入消息是异步消息。
//不管怎样,都需要唤醒
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
//队里已经有异步消息啦,不需要唤醒。
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
主动唤醒标志,当队列是空或者when是0,代表立即执行,或者执行时间when小于头部消息时间,插入位置在头部,按照时间先后排序,设置主动唤醒标志。
当不满足上述条件时,如果存在同步栅栏,个人理解是遇到SyncBarrier这个消息,优先处理后续的异步消息,屏蔽同步消息,SyncBarrier消息没有target目标,在Android SDK中是hide状态,不会暴漏给App调用,因此,在App中不会发送这类消息。总之,从源码中可知,当队列头部遇到SyncBarrier消息(没有target),且插入消息是异步,将会主动唤醒,除非队列中已经有其他异步消息了。
最后,根据唤醒标志,调用JNI方法,nativeWake方法,立即唤醒。当不需要唤醒,将消息按时间排序插入到队列合适位置,不会执行唤醒方法,消费者线程继续休眠,唤醒由MQ.next()方法决定。
void Looper::wake() {
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t)) {
if (errno != EAGAIN) {
ALOGW("Could not write wake signal, errno=%d", errno);
}
}
}
wake方法,向mWakeEventFd句柄写入一些内容,(插入可能发生在其他任何线程中噢!),这时,Looper.loop()消费者线程正在MQ.next()处,epoll_wait休眠,监听到mWakeEventFd事件。当注册的流发生了事件,(这里写入的内容不是重点)。唤醒,检查是mWakeEventFd事件,awoken方法,读取流的内容,(这里读取的内容也不是重点),关键是线程被唤醒,可以继续执行,从JNI层回到Java层MQ.next(),获取消息并回调。
Native层消息,存储在Vector类型的mMessageEnvelopes对象,唤醒后直接处理,遍历MessageEnvelopes数组内容。上层消息,返回到Java层MQ.next()和Looper.loop(),进行回调处理。
向队列插入消息,唤醒时机,在消息发送到MQ之后,由MQ. enqueueMessage()判断,当不唤醒时,说明消息不需要立即执行。
总结与扩展
Native层Looper,利用epoll提供消费者线程休眠和唤醒的机制。
自动唤醒,根据上层消息队列对消息执行时间when的判断,决策唤醒timeout。
主动唤醒,Handler类主导,消息队列决策,向消费线程epoll监听的文件描述符写入字段,触发流事件,即可唤醒。
按照消息执行时间升序插入队列位置。
Java层功能,消息创建发送,队列插入维护,唤醒时机间隔计算决策,消息处理。
Native层消息,这种场景不经过上层。
扩展,epoll机制有两种模式,ET边缘模式与LT水平模式,(默认模式)。
ET模式,状态发生变化时才会事件通知,例如,向流中写入10个字节,消费者线程唤醒,仅读取5个字节,再次循环到此处时,将不会再收到通知,除非再次写入句柄改变状态。
LT模式,有数据留在buffer未读取,每次循环到此处时,会一直收到事件通知。
任重而道远