前言
消息机制这种思想可以说是操作系统的基本机制之一,通过消息来驱动事件,满足交互的需求。
常见的操作系统如Windows、Android都有自己的消息机制的实现。
从Android的Handler谈起
通常我们做消息分发时,都是通过Handler帮我们实现,
//直接发送一个消息
mHandler.sendEmptyMessage(YOUR_MSG);
...
//发送一个待执行的Runnable
mHandler.post(new Runnable() {
public void run(){
//待执行的逻辑
}
});
复制代码
上述的这些方法都是一层封装,通过构造Message对象,最终都会走到,
...
public final boolean sendMessageDelayed(Message msg, long delayMillis){
if (delayMillis < 0) {
delayMillis = 0;
}
//将延迟的相对时间变成一个绝对的时间点
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
...
public boolean sendMessageAtTime(Message msg, long uptimeMillis){
//这里出现了我们经常提起的消息队列类MessageQueue
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
//将构造好的Message对象入队
return enqueueMessage(queue, msg, uptimeMillis);
}
...
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis){
//Message的target成员变量是一个Handler(消息分发是通过判断它,进而知道要交给哪个handler去处理消息的)
//这里将当前handler赋值给它
msg.target = this;
//mAsynchronous在Handler构造函数中赋值,交给调用方去选择是否异步
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//调用MessageQueue中的enqueueMessage入队,这个是核心的实现
return queue.enqueueMessage(msg, uptimeMillis);
}
复制代码
以上分析我们知道,Handler最终消息的入队是交由MessageQueue来实现的,我们来看一下具体源码,
//其实MessageQueue虽然是一个队列,但是是通过链表来实现的
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) {
msg.next = p;
mMessages = msg;
//这个布尔值标志着是否需要唤醒事件队列,稍后将会讲到
needWake = mBlocked;
} else {
//按照未来执行的时间点when,插入链表中(这也是为什么用链表实现的原因)
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) {
//唤醒消息队列是native层的逻辑
nativeWake(mPtr);
}
}
return true;
}
复制代码
以上讲解了消息队列的入队的逻辑,我们来看看消息队列是怎么分发消息的。在Android中,是靠着Looper来实现从消息队列中取消息进而分发给对应的Handler的。Looper有两个重要的方法prepare()和loop(),
//通常我们直接调用prepare()方法时,quitAllowed默认是为true的
//quitAllowed最终会传递给MessageQueue的构造函数,通过这个布尔值来控制是否允许退出消息队列
//查看源码发现,如果prepareMainLooper(),即初始化主线程上的Looper,默认quitAllowed是为false的。
//这也符合常理,主线程不允许退出消息队列。如果强行执行MessageQueue.quit()将会抛出异常。
private static void prepare(boolean quitAllowed){
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//初始化Looper并设置给ThreadLocal
sThreadLocal.set(new Looper(quitAllowed));
}
复制代码
在Java中,ThreadLocal为变量在每个线程都创建一个副本,每个线程都可以访问自己的内部副本变量。ThreadLocal 不是用来解决共享对象的多线程访问问题的,而是使用ThreadLocal使得各线程能够保持各自独立的一个对象。
public static void loop(){
...
for (;;) {
//可能会阻塞
Message msg = queue.next();
...
//调用Handler.dispatchMessage方法分发Message消息
msg.target.dispatchMessage(msg);
}
...
}
复制代码
Handler.dispatchMessage, 由源码可知,Handler对Runnable、Handler自定义的Callback和继承Handler分别做了回调函数的适配,对开发者而言,使用Handler变得更为灵活和方便,
public void dispatchMessage(Message msg){
if (msg.callback != null) {
//这里就是你传进来的runnable
handleCallback(msg);
} else {
//mCallback可以在Handler的构造函数中定义
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//自带的handleMessage可以在继承Handler时实现
handleMessage(msg);
}
}
复制代码
MessageQueue.next中实现细节,
Message next(){
...
for (;;) {
...
//稍后会讲到,Linux系统的IO多路复用机制
nativePollOnce(ptr, nextPollTimeoutMillis);
...
//如果消息队列中有异步消息,则优先执行异步消息,然后再顺序执行剩余消息
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) {
// 下一条消息未到执行时间,赋值一个超时的时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
...
msg.markInUse();
//将获取到Message移出队列,并返回
return msg;
}
} else {
// 没有Message了,赋值-1表示无限等待,直到下一条消息到来
nextPollTimeoutMillis = -1;
}
}
}
复制代码
在Message为空的时候,会去处理之前添加过的IdleHandler,因此我们可以利用IdleHandler实现一些功能
for (int i = 0; i < pendingIdleHandlerCount; i++) {
...
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
...
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
public static interface IdleHandler{
//返回true将你的idle handler保留,false则从list中移除
boolean queueIdle();
}
复制代码
总结一下,Android的消息机制就是,通过不断地使用Handler将消息发送给一个消息队列入队,并且有一个无限循环的Looper,不断地从消息队列里取出消息然后分发给对应的目标Handler执行。
待思考的问题
既然Android主线程上的Looper是无限循环,为什么主线程没有被阻塞?
由之前的分析可知Looper.loop()方法中是一个for(;;)的无限循环,并且Android主线程会初始化一个对应的Looper。那么既然是死循环,为什么主线程没有卡死,或者说CPU的占用没有飙升。
首先看一下MessageQueue的构造函数,
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
复制代码
其中nativeInit调用了native层的实现,
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz){
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
...
nativeMessageQueue->incStrong(env);
//将指针nativeMessageQueue再解释为jlong类型,并返回
return reinterpret_cast(nativeMessageQueue);
}
复制代码
NativeMessageQueue的构造函数,
NativeMessageQueue::NativeMessageQueue() : ...省略{
mLooper = Looper::getForThread();
if (mLooper == NULL) {
//可见初始化了一个native的Looper
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}
复制代码
我们进入native的Looper看看它的构造函数,
Looper::Looper(bool allowNonCallbacks) : ...省略 {
...
rebuildEpollLocked();
}
void Looper::rebuildEpollLocked() {
...
// 创建了epoll
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
...
struct epoll_event eventItem;
memset( & eventItem, 0, sizeof(epoll_event));
//设置感兴趣的event进行监听
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
...
}
复制代码
这里的epoll是linux系统上多路IO复用的机制。
Linux 的I/O多路复用模式 select、poll、epoll
select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。
epoll是在2.6内核中提出的,是之前的select和poll的增强版本。当调用epoll_wait()获取就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,之后再去epoll中的一个数组中取得相应的fd即可。避免了频繁的内存拷贝。
之后我们看一下MessageQueue的nativePollOnce的实现,
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
...
//还是调用了native的Looper中的方法
mLooper->pollOnce(timeoutMillis);
...
}
int Looper::pollOnce(int timeoutMillis, int*outFd, int*outEvents, void**outData) {
int result = 0;
for (; ; ) {
...
//调用内部方法
result = pollInner(timeoutMillis);
}
}
//精简了很多方法,主要看流程
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) {
if (epollEvents & EPOLLIN) {
//唤醒
awoken();
} else {
}
} else {
ssize_t requestIndex = mRequests.indexOfKey(fd);
if (requestIndex >= 0) {
pushResponse(events, mRequests.valueAt(requestIndex));
} else {
}
}
}
Done:
int callbackResult = response.request.callback->handleEvent(fd, events, data);
if (callbackResult == 0) {
//移除fd
removeFd(fd, response.request.seq);
}
// Clear the callback reference in the response
response.request.callback.clear();
result = POLL_CALLBACK;
return result;
}
复制代码
这里基本上对native层消息模型有个大致的认识。native层通过system call的epoll机制,在无限循环情况下会进入阻塞休眠,等待时间唤醒,从而避免了阻塞主线程的情况发生。
总结一下,Android通过Java层与native层两层各自的消息机制实现了在主线程非阻塞的消息系统。
最后有一张图描述了两层各自的关系,