深入理解 Android 消息机制原理
欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~
作者:汪毅雄
导语:本文讲述的是Android的消息机制原理,从Java到Native代码进行了梳理,并结合其中使用到的Epoll模型予以介绍。
Android的消息传递,是系统的核心功能,对于如何使用相信大家都已经相当熟悉了,这里简单提一句。我们可以粗糙的认为消息机制中关键的几个类的功能如下:
Handler:消息处理者
Looper:消息调度者
MessageQueue:存放消息的地方
使用过程:
Looper.prepare > #$%^^& > Looper.loop(死循环) --- loop到一个消息 > Handler处理
好了,我们直接看源码吧。
Java层
消息机制是伴随线程的,也就是说上面的几个类在可以在任何一个线程中都有实例的。
先看Looper吧。以主线程为例,Android进程在初始化,会调用prepareMainLooper
public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { ... sMainLooper = myLooper(); } }
private static void prepare(boolean quitAllowed) { ... sThreadLocal.set(new Looper(quitAllowed)); }
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
以上几个方法就是Looper初始化,如果是主线程Looper会创建一个不可退出的MessageQueue,并把looper实例放入线程独立(ThreadLocal)变量中。
Looper#loop
public static void loop() { final Looper me = myLooper(); final MessageQueue queue = me.mQueue; for (;;) { Message msg = queue.next(); if (msg == null) { return; } ... try { msg.target.dispatchMessage(msg); } ... msg.recycleUnchecked(); } }
Looper prepare后就可以loop了,loop非常简单,一直去queue中拿消息就好了,拿到了交给target也就是Handler处理。大家有可能会奇怪这种死循环,执行起来不会太sb粗暴了吗?其实这个解决方式在queue.next!!!后面再讲。
Handler#dispatchMessage
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
handler收到后,如果发现message的callback不为空,则只处理callback。(提一句,我们用的很多的handler.post(Runnable),其实这个Runnable就是这里的callback,也就是说post的Runnable实质上是一个优先级很高的Message),如果没有则尝试交给handler本身的callback处理(handler初始化的时候可以用callback方式构造),再没有才到我们常用的handleMessage方法,这里就是我们经常重写的方法。
再说说消息的发送,一般handler会调用sendMessage方法,但是最终这个方法还是会跑到这里
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
再交给MessageQueue
boolean enqueueMessage(Message msg, long when) { 。。。 synchronized (this) { 。。。 Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } 。。。 } msg.next = p; // invariant: p == prev.next prev.next = msg; if (needWake){ nativeWake(mPtr); } } return true; }
MessageQueue会把消息插入队列,并依次改变队列中各个消息的指针。
咦,好像只用Java层貌似就能把整个消息机制说通了,native代码在哪儿?有何用呢?
但是,刚才提到了Looper初始化的时候也会新建一个MessageQueue
MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit(); }
好了,我们第一个native方法出来了。这时候我们可以猜得到,MessageQueue才是整个消息机制的核心!
Native层
接上面Java层的代码,MessageQueue构造的时候会调一个nativeInit。
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) { NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); 。。。 }
NativeMessageQueue::NativeMessageQueue() : mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) { mLooper = Looper::getForThread(); if (mLooper == NULL) { mLooper = new Looper(false); Looper::setForThread(mLooper); } }
native层调用init方法后,会在native层构建一个native Looper!来看看native Looper的初始化
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 | EFD_CLOEXEC); ... rebuildEpollLocked(); }
这里创建了一个eventfd,代码来自最新的8.0,这部分和5.0 pipe管道的mWakeReadPipeFd和mWakeWritePipeFd稍微有点不一样,前者是等待/响应,后者是读取/写入。只是android选取方式的不同而已,这块就不细说。
void Looper::rebuildEpollLocked() { 。。。 mEpollFd = epoll_create(EPOLL_SIZE_HINT); LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno)); struct epoll_event eventItem; memset(& eventItem, 0, sizeof(epoll_event)); eventItem.events = EPOLLIN; eventItem.data.fd = mWakeEventFd; int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem); 。。。 }
再到rebuildEpollLocked这个方法中,可以看到通过epoll_create创建了一个epoll专用的文件描述符,EPOLL_SIZE_HINT表示mEpollFd上能监控的最大文件描述符数。最后调用epoll_ctl监控mWakeEventFd文件描述符的Epoll事件,即当mWakeEventFd中有内容可读时,就唤醒当前正在等待的线程.。
这里不了解的人可能听着晕,上面这么一大段一句话概括就是:Android native层用了Epoll模型。什么是Epoll模型呢?我先简单介绍一下。
Epoll(必看!!!)
为什么要引入呢?
在Looper.loop的时候提到了,android不会简单粗暴地真的执行啥都没干的死循环。刚才说了,问题出在queue.next。Epoll干的事就是: 如果你的queue中没有消息可执行了,好了你可以歇着了,等有消息的我再告诉你。这个queue.next就是“阻塞”(休眠)在这里。
Epoll简单介绍
1、传统的阻塞型I/O(一边写,一边读),一个线程只能处理一个一个IO流。
2、如果一个线程想要处理多个流,可以采用了非阻塞、轮询I/O方式,但是传统的非阻塞处理多个流的时候,会遍历所有流,但是如果所有流都没数据,就会白白浪费CPU。
。。。
于是出现了select和epoll两种常见的代理方式。
3、select就是那种无差别轮询的代理方式。epoll可以理解为Event poll,也就是说代理者会代理流的时候也伴随着事件,因此有了对应事件,就可以避免无差别轮询了。
4、其通常的操作有:epoll_create(创建一个epoll)、epoll_ctl(往epoll中增加/删除某一个流的某一个事件)、epoll_wait(在一定时间内等待事件的发生)
eventItem.events = EPOLLIN; eventItem.data.fd = mWakeEventFd; int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
好了,我们结合Looper初始化的代码来读一下epoll在这里干了什么吧。
我直接翻译了:往mEpollFd代理中、注册、一个叫mWakeEventFd流、的数据流入事件(EPOLLIN)
这样大家应该懂了吧。。。
接上MessageQueue在初始化后,在native创建了一个Looper。
我们继续消息的发送和提取在native层的表现。其实native层主要负责的是消息的调度,比如说何时阻塞、何时唤醒线程,避免CPU浪费。
native发送
发送在native比较简单,handler发送消息后,会到MessageQueue的enqueueMessage,此时在线程阻塞的情况下,会调用nativeWake来唤起线程。
void NativeMessageQueue::wake() { mLooper->wake(); }
void Looper::wake() { 。。。 uint64_t inc = 1; ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t))); if (nWrite != sizeof(uint64_t)) { 。。。。 } }
这里TEMP_FAILURE_RETRY是一个宏定义,顾名思义,就是不断地尝试往mWakeEventFd流里面写一个无用数据直到成功,以此来唤醒queue.next。这部分就不多说了。
native消息提取
也就是queue.next
Message next() { 。。。 for (;;) { 。。。 nativePollOnce(ptr, nextPollTimeoutMillis); 。。。 } }
可以看到,又是一个死循环(阻塞)。继续往下看
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) { 。。。 mLooper->pollOnce(timeoutMillis); 。。。 }
mLooper->pollOnce
mLooper->pollInner
int Looper::pollInner(int timeoutMillis) { 。。。 int result = POLL_WAKE; mPolling = true; struct epoll_event eventItems[EPOLL_MAX_EVENTS]; int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis); mPolling = false; mLock.lock(); 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 { ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents); } } 。。。 } 。。。 mLock.unlock(); 。。。 return result; }
在这里,我们注意到epoll_wait方法,这里会得到一段时间内(结合消息计算得来的)收到的事件个数,这里对于queue来说就是空闲(阻塞)状态。过了这个时间后,看看事件数,如果为0,则意味着超时。否则,遍历所有的事件,看看有没有mWakeEventFd,且是EPOLLIN事件的,有的话就真正唤醒线程、解除空闲状态。
消息机制在native层的主要表现就是这些。
最后,画了一个粗糙、且不太准确图仅供参考学习
相关阅读
Android View和 Window 的关系
Android 7.0 中 ContentProvider 实现原理
云服务器20元/月起,更享千元续费大礼包
此文已由作者授权腾讯云技术社区发布,转载请注明原文出处