关于作者
郭孝星,程序员,吉他手,主要从事Android平台基础架构方面的工作,欢迎交流技术方面的问题,可以去我的Github提issue或者发邮件至guoxiaoxingse@163.com与我交流。
文章目录
- 一 消息队列的创建
- 1.1 建立消息队列
- 1.2 开启消息循环
- 二 消息的添加
- 三 消息的分发和处理
- 3.1 消息分发
- 3.2 消息处理
Android是一个消息驱动型的系统,消息机制在Android系统中扮演者重要的角色,与之相关的Handler也是我日常中常用的工具。今天我们就来聊一聊这个。
Android消息循环流程图如下所示:
主要涉及的角色如下所示:
- Message:消息,分为硬件产生的消息(例如:按钮、触摸)和软件产生的消息。
- MessageQueue:消息队列,主要用来向消息池添加消息和取走消息。
- Looper:消息循环器,主要用来把消息分发给相应的处理者。
- Handler:消息处理器,主要向消息队列发送各种消息以及处理各种消息。
整个消息的循环流程还是比较清晰的,具体说来:
- Handler通过sendMessage()发送消息Message到消息队列MessageQueue。
- Looper通过loop()不断提取触发条件的Message,并将Message交给对应的target handler来处理。
- target handler调用自身的handleMessage()方法来处理Message。
事实上,在整个消息循环的流程中,并不只有Java层参与,很多重要的工作都是在C++层来完成的。我们来看下这些类的调用关系。
注:虚线表示关联关系,实线表示调用关系。
在这些类中MessageQueue是Java层与C++层维系的桥梁,MessageQueue与Looper相关功能都通过MessageQueue的Native方法来完成,而其他虚线连接的类只有关联关系,并没有 直接调用的关系,它们发生关联的桥梁是MessageQueue。
有了上面这些分析,相信我们对Android的消息机制有了大致的理解,对于这套机制,我们很自然会去思考三个方面的问题:
- 消息队列是如何创建的,它们如何实现消息循环的,消息循环为什么不会导致线程卡死??
- 消息是如何添加到队列中的,它们在队列里是如何排序的??
- 消息是如何被分发的,分发以后又是如何被处理的??
我们一一来看一下。
一 消息队列的创建
1.1 建立消息队列
消息队列是由MessageQueue类来描述的,MessageQueue是Android消息机制Java层和C++层的纽带,其中很多核心方法都交由native方法实现。
既然提到对象构建,我们先来看看它的构造函数。
public final class MessageQueue {
private long mPtr; // used by native code
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
}
复制代码
可以看到它调用的是native方法来完成初始化,这个方法定义在了一个android_os_MessageQueue的C++类类。
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
//构建NativeMessageQueue对象
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
//将nativeMessageQueue对象的地址值转成long型返回该Java层
return reinterpret_cast<jlong>(nativeMessageQueue);
}
复制代码
可以看到该方法构建了一个NativeMessageQueue对象,并将NativeMessageQueue对象的地址值转成long型返回给Java层,这里我们知道实际上是mPtr持有了这个 地址值。
NativeMessageQueue继承域MessageQueue.cpp类,我们来看看NativeMessageQueue的构造方法。
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
//先检查是否已经为当前线程创建过一个Looper对象
mLooper = Looper::getForThread();
if (mLooper == NULL) {
//创建Looper对象
mLooper = new Looper(false);
//为当前线程设置Looper对象
Looper::setForThread(mLooper);
}
}
复制代码
可以看到NativeMessageQueue构造方法先检查是否已经为当前线程创建过一个Looper对象,如果没有,则创建Looper对象并为当前线程设置Looper对象。
我们再来看看Looper的构造方法。
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
int wakeFds[2];
//创建管道
int result = pipe(wakeFds);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);
//读端文件描述符
mWakeReadPipeFd = wakeFds[0];
//写端文件描述符
mWakeWritePipeFd = wakeFds[1];
result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",
errno);
result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",
errno);
//创建一个epoll实例,并将它的文件描述符保存在变量mEpollFd中
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeReadPipeFd;
//将前面创建的管道读端描述符添加到这个epoll实例中,以便它可以对管道的写操作进行监听
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",
errno);
}
复制代码
这里面提到两个概念:管道与epoll机制。
关于管道
管道在本质上也是文件,但它不是普通的文件,它不属于任何文件类型,而且它只存在与内存之中且有固定大小的缓存区,一般为1页即4kb。它分为读端和写端,读端负责从 管道读取数据,当数据为空时则阻塞,写端负责向管道写数据,当管道缓存区满时则阻塞。那管道在线程通信中主要用来通知另一个线程。例如:线程A准备好了Message放入 了消息队列,这个时候需要通知线程B去处理,这个时候线程A就像管道的写端写入数据,管道有了数据之后就回去唤醒线程B区处理消息。也正是基于管道来进行线程的休眠与 唤醒,才保住了线程中的loop循环不会让线程卡死。
关于epoll机制
epoll机制用来监听多个文件描述符的IO读写事件,在Android的消息机制用来监听管道的读写IO事件。
关于epool机制,这里有个简单易懂的解释。
epoll一共有三个操作方法:
- epoll_create():创建一个epoll的句柄,size是指监听的描述符个数
- epoll_ctl():对需要监听的文件描述符(fd)执行操作,比如将fd加入到epoll句柄。
- epoll_wait():返回需要处理的事件数目,如返回0表示已超时。
上面Looper的构造方法里,我们已经看到了利用epoll_create()创建一个epoll的实例,并利用epoll_ctl()将管道的读端描述符操作符添加到epoll实例中,以便可以对管道的 写操作进行监听,下面我们还可以看到epoll_wait()的用法。
讲到这里整个消息队列便创建完成了,下面我们接着来看看消息循环和如何开启的。
1.2 开启消息循环
消息循环是建立在Looper之上的,Looper可以为线程添加一个消息循环的功能,具体说来,为了给线程添加一个消息循环,我们通常会这么做:
public class LooperThread extends Thread {
public Handler mHandler;
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
复制代码
可以看到先调用Looper.prepare()初始化一个Looper,然后调用Looper.loop()开启循环。
关于Looper,有两个方法来初始化prepare()/prepareMainLooper(),它们创建的Looper对象都是一样,只是prepareMainLooper() 创建的Looper是给Android主线程用的,它还是个静态对象,以便其他线程都可以获取到它,从而可以向主线程发送消息。
public final class Looper {
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
public static void prepare() {
prepare(true);
}
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));
}
//创建主线程的Looper,应用启动的时候会右系统调用,我们一般不需要调用这个方法。
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
//返回和当前线程相关的Looper
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
}
复制代码
指的一提的是这里使用的是ThreadLocal来存储新创建的Looper对象。
ThreadLocal描述的是线程本地存储区,不同的线程不能访问对方的线程本地存储区,当前线程可以对自己的线程本地存储区进行独立的修改和读取。
之所以会采用ThreadLocal来存储Looper,是因为每个具备消息循环能力的线程都有自己独立的Looper,它们彼此独立,所以需要用线程本地存储区来存储Looper。
我们在接着来看看Looper的构造函数,如下所示:
public final class Looper {
private Looper(boolean quitAllowed) {
//创建消息队列
mQueue = new MessageQueue(quitAllowed);
//指向当前线程
mThread = Thread.currentThread();
}
}
复制代码
Looper的构造函数也很简单,构造了一个消息队列MessageQueue,并将成员变量mThread指向当前线程,这里构建了一个MessageQueue对象,在MessageQueue构建 的过程中会在C++层构建Looper对象,这个我们上面已经说过。
Looper对象创建完成后就可以开启消息循环了,这是由loop()方法来完成的。
public final class Looper {
public static void loop() {
//获取当前线程的Looper
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//获取当前线程的消息队列
final MessageQueue queue = me.mQueue;
//确保当前线程处于本地进程中,Handler仅限于处于同一进程间的不同线程的通信。
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//进入loop主循环方法
for (;;) {
//不断的获取下一条消息,这个方法可能会被阻塞
Message msg = queue.next();
if (msg == null) {
//如果没有消息需要处理,则退出当前循环。
return;
}
// 默认为null,可通过setMessageLogging来指定输出,用于debug
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
//处理消息
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
//把message回收到消息池,以便重复利用。
msg.recycleUnchecked();
}
}
}
复制代码
可以看到,这个方法不断重复着以下三件事:
- 调用MessageQueue的next()方法读取MessageQueue的下一条Message。
- 把Message分发给相应的target。
- 再把分发的Message回收到消息池,以便重复利用。
如此消息循环便建立起来了。
二 消息的添加
在如此开始中,我们通常会调用handler的sendXXX()或者postXXX()将一个Message或者Runnable,这些方法实际上调用的MessageQueue的enqueueMessage()方法,该方法 会给目标线程的消息队列插入一条消息。
注:如何理解这个"目标线程的消息队列",首先要明白Handler、Looper与MessageQueue这三兄弟是全家桶,绑在一起的,你用哪个Handler,消息就被插入到了这个Handler所在线程 的消息队列里。
public final class MessageQueue {
boolean enqueueMessage(Message msg, long when) {
//每个消息都必须有个target handler
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
//每个消息必须没有被使用
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
//正在退出时,回收Message,加入消息池。
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
//mMessages表示当前需要处理的消息,也就是消息队列头的消息
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;
}
}
复制代码
enqueueMessage()以时间为序将消息插入到消息队列中去,以下三种情况下需要插入到队列头部:
- 消息队列为空
- 要插入的消息的执行时间为0
- 要插入的消息的执行时间小于消息队列头的消息的执行时间
上面三种情况很容易想到,其他情况以时间为序插入到队列中间。当有新的消息插入到消息队列头时,当前线程就需要去唤醒目标线程(如果它已经睡眠(mBlocked = true)就执行唤醒操作,否则不需要),以便 它可以来处理新插入消息头的消息。
通过这里的分析,你可以发现,消息队列事实上是基于单向链表来实现的,虽然我们总称呼它为"队列",但它并不是一个队列(不满足先进先出)。
同样利用单向链表这种思路的还有对象池,读者应该有印象,很多文档都提倡通过Message.obtain()方法获取一个Message对象,这是因为Message对象会被缓存在消息池中,它主要利用 Message的recycle()/obtain()方法进行缓存和获取。
具体说来:
recycle()
public final class Message implements Parcelable {
private static final Object sPoolSync = new Object();
private static Message sPool;
public void recycle() {
//判断消息是否正在使用
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
//对于不再使用的消息加入消息池
recycleUnchecked();
}
void recycleUnchecked() {
//将消息标记为FLAG_IN_USE并清空关于它的其他信息
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
//当消息池没有满时,将消息加入消息池
if (sPoolSize < MAX_POOL_SIZE) {
//将sPool存放在next变量中
next = sPool;
//sPool引用当前对象
sPool = this;
//消息池数量自增1
sPoolSize++;
}
}
}
}
复制代码
obtain()
public final class Message implements Parcelable {
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
//sPool当前持有的消息对象将作为结果返回
Message m = sPool;
//将m的后继重新赋值给sPool,这其实一个链表的删除操作
sPool = m.next;
//m的后继置为空
m.next = null;
//清除 in-use 标志位
m.flags = 0;
//消息池大小自减1
sPoolSize--;
return m;
}
}
//当对象池为空时,直接创建新的Message()对象。
return new Message();
}
}
复制代码
这里面有个巧妙的设计,这也给我们如何设计一个对象池提供了一个很好的思路,它是以单向链表具体说来:
- 在类中定义一个该类的静态对象sPool以及它的后继对象next。
- 当对象加入对象池时,将该对象加入到链表中。
- 当对象从对象池中取出时,返回sPool当前持有的对象即可,并将sPool从当前链表中移除。
好了。消息池就聊这么多,我们接着来看消息的分发和处理。
三 消息的分发与处理
3.1 消息分发
消息的分发是建立在消息循环之上的,在不断的循环中拉取队列里的消息,消息循环的建立流程我们上面已经分析过,通过分析得知,loop()方法 不断调用MessageQueue的next()读取消息队列里的消息,从而进行消息的分发。
我们来看看next()方法的实现。
public final class MessageQueue {
Message next() {
final long ptr = mPtr;
//当前消息循环已退出,直接返回。
if (ptr == 0) {
return null;
}
//pendingIdleHandlerCount保存的是注册到消息队列中空闲Handler个个数
int pendingIdleHandlerCount = -1;
//nextPollTimeoutMillisb表示当前无消息到来时,当前线程需要进入睡眠状态的
//时间,0表示不进入睡眠状态,-1表示进入无限等待的睡眠状态,直到有人将它唤醒
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//nativePollOnce是阻塞操作,用来检测当前线程的消息队列中是否有消息需要处理
nativePollOnce(ptr, nextPollTimeoutMillis);
//查询下一条需要执行的消息
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
//mMessages代表了当前线程需要处理的消息
Message msg = mMessages;
//查询第一个可以处理的消息(msg.target == null表示没有处理Handler,无法进行处理,忽略掉)
if (msg != null && msg.target == null) {
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;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
//设置消息的使用状态,即flags |= FLAG_IN_USE。
msg.markInUse();
return msg;
}
} else {
// 如果没有更多消息需要处理,则将nextPollTimeoutMillis置为-1,让当前线程进入无限睡眠状态,直到
//被其他线程唤醒。
nextPollTimeoutMillis = -1;
}
//所有消息都已经被处理,准备退出。
if (mQuitting) {
dispose();
return null;
}
//pendingIdleHandlerCount指的是等待执行的Handler的数量,mIdleHandlers是一个空闲Handler列表
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
//当没有空闲的Handler需要执行时进入阻塞状态,mBlocked设置为true
mBlocked = true;
continue;
}
//mPendingIdleHandler是一个IdleHandler数组
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
//只有在第一次循环时,才会去执行idle handlers,执行完成后重置pendingIdleHandlerCount为0
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
//释放Handler的引用
mPendingIdleHandlers[i] = null;
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
//执行完成后,重置pendingIdleHandlerCount为0,以保证不会再次重复运行。
pendingIdleHandlerCount = 0;
//当调用一个空闲Handler时,新的消息可以立即被分发,因此无需再设置超时时间。
nextPollTimeoutMillis = 0;
}
}
}
复制代码
首先要明确一个概念,MessageQueue是利用对象间的后继关联(每个对象都知道自己的直接后继)实现的链表,其中它的成员变量mMessages变量指的是当前需要被处理消息。
next()方法主要用来从消息队列里循环获取消息,这分为两步:
- 调用nativePollOnce(ptr, nextPollTimeoutMillis)方法检测当前线程的消息队列中是否有消息需要处理(注意不是在这里取消息)。它是一个阻塞操作,可能会引起 线程睡眠,下面我们会说。
- 查询当前需要处理的消息,返回并将其从消息队列中移除。
这个查询当前需要处理的消息可以分为三步:
- 找到当前的消息队列头mMessages,如果它为空就说明整个消息队列为空,就将nextPollTimeoutMillis置为-1,当前线程进入无限睡眠等待,知道别的线程将其唤醒。如果 它不为空,则进入步骤2.
- 如果消息队列头的执行之间大于当前时间,则说明线程需要等待该消息的执行,线程进入睡眠。否则进入步骤3.
- 查找到了当前需要被处理的消息,将该消息从消息队列里移除,并返回该消息。
可以看到这里调用的是native方法nativePollOnce()来检查当前线程是否有消息需要处理,调用该方法时,线程有可能进入睡眠状态,具体由nextPollTimeoutMillis参数决定。0表示不进入睡眠状态,-1表示 进入无限等待的睡眠状态,直到有人将它唤醒。
我们接着来看看nativePollOnce()方法的实现。
nativePollOnce()方法是个native方法,它按照调用链:android_os_MessageQueue#nativePollOnce() -> NativeMessageQueue#pollOnce() -> Looper#pollOnce() -> Looper#pollInner() 最终完成了消息的拉取。可见实现功能的还是在Looper.cpp里。
我们来看一下实现。
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
...
//内部不断调用pollInner()方法检查是否有新消息需要处理
result = pollInner(timeoutMillis);
}
}
int Looper::pollInner(int timeoutMillis) {
...
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
//调用epoll_wait()函数监听前面注册在mEpollFd实例里的管道文件描述符中的读写事件。如果这些管道
//文件描述符没有发生读写事件,则当前线程会在epoll_wait()方法里进入睡眠,睡眠事件由timeoutMillis决定
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
...
//epoll_wait返回后,检查哪一个管道文件描述符发生了读写事件
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
//如果fd是当前线程关联的管道读端描述符且读写事件类型是EPOLLIN
//就说明当前线程关联的一个管道写入了新的数据,这个时候就会调用
//awoken()去唤醒线程
if (fd == mWakeReadPipeFd) {
if (epollEvents & EPOLLIN) {
//此时已经唤醒线程,读取清空管道数据
awoken();
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
}
}
...
}
...
return result;
}
复制代码
可以看到这个方法做了两件事情:
- 调用epoll_wait()函数监听前面注册在mEpollFd实例里的管道文件描述符中的读写事件。如果这些管道文件描述符没有发生读写事件,则当前线程 会在epoll_wait()方法里进入睡眠,睡眠事件由timeoutMillis决定。
- 如果fd是当前线程关联的管道读端描述符且读写事件类型是EPOLLIN就说明当前线程关联的一个管道写入了新的数据,这个时候就会调用awoken()去唤醒线程。
至此,消息完成了分发。从上面的loop()方法我们可以知道,消息完成分发后会接着调用Handler的dispatchMessage()方法来处理消息。
我们接着来聊一聊Handler。
3.1 消息处理
Handler主要用来发送和处理消息,它会和自己的Thread以及MessageQueue相关联,当创建一个Hanlder时,它就会被绑定到创建它的线程上,它会向 这个线程的消息队列分发Message和Runnable。
一般说来,Handler主要有两个用途:
- 调度Message和Runnable,延时执行任务。
- 进行线程的切换,请求别的线程完成相关操作。
我们先来看看Handler的构造函数。
public class Handler {
//无参构造方法,最常用。
public Handler() {
this(null, false);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}
public Handler(Callback callback, boolean async) {
//匿名类、本地类都必须声明为static,否则会警告可能出现内存泄漏,这个提示我们应该很熟悉了。
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
//获取当前线程的Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//获取当前线程的消息队列
mQueue = mLooper.mQueue;
//回调方法,这个Callback里面其实只有个handleMessage()方法,我们实现这个
//接口,就不用去用匿名内部类那样的方式来创建Handler了。
mCallback = callback;
//设置消息是否为异步处理方式
mAsynchronous = async;
}
}
复制代码
对于构造方法而言,我们最常用的是无参构造方法,它没有Callback回调,且消息处理方式为同步处理,从这里我们也可以看出你在哪个线程里创建了Handler,就默认使用当前线程的Looper。
从上面的loop()方法中,我们知道Looper会调用MessageQueue的dispatchMessage()方法进行消息的处理,我们来看看这个方法的实现。
public class Handler {
public void dispatchMessage(Message msg) {
//当Message存在回调方法时,优先调用Message的回调方法message.callback.run()
if (msg.callback != null) {
//实际调用的就是message.callback.run();
handleCallback(msg);
} else {
//如果我们设置了Callback回调,优先调用Callback回调。
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//如果我们没有设置了Callback回调,则回调自身的Callback方法。
handleMessage(msg);
}
}
}
复制代码
可以看到整个消息分发流程如下所示:
- 当Message存在回调方法时,优先调用Message的回调方法message.callback.run()。
- 果我们设置了Callback回调,优先调用Callback回调。
- 如果我们没有设置了Callback回调,则回调自身的Callback方法。
由此我们也可以得知方法调用的优先级,从高到低依次为:
- message.callback.run()
- Handler.mCallback.handleMessage()
- Handler.handleMessage()
大部分代码都是以匿名内部类的形式实现了Handler,所以一般会走到第三个流程。
可以看到所以发送消息的方法最终都是调用MessageQueue的enqueueMessage()方法来实现,这个我们上面在分析MessageQueue的时候已经说过,这里就不再赘述。
理解了上面的内容,相信读者已经对Android的消息机制有了大致的了解,我们趁热打铁来聊一聊实际业务开发中遇到的一些问题。
在日常的开发中,我们通常在子线程中执行耗时任务,主线程更新UI,更新的手段也多种多样,如Activity#runOnUIThread()、View#post()等等,它们之间有何区别呢?如果我的代码了 既没有Activity也没有View,我该如何将代码切换回主线程呢??
我们一一来分析。
首先,Activity里的Handler直接调用的就是默认的无参构造方法。可以看到在上面的构造方法里调用Looper.myLooper()去获取当前线程的Looper,对于Activity而言当前线程就是主线程(UI线程),那主线程 的Looper是什么时候创建的呢??
在03Android组件框架:Android视图容器Activity一文 里我们就分析过,ActivityThread的main()函数作为应用的入口,会去初始化Looper,并开启消息循环。
public final class ActivityThread {
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
复制代码
主线程的Looper已经准备就绪了,我们再调用Handler的构造函数去构建Handler对象时就会默认使用这个Handler,如下所示:
public class Activity {
final Handler mHandler = new Handler();
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
}
复制代码
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
public boolean post(Runnable action) {
//当View被添加到window时会添加一些附加信息,这里面就包括Handler
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
//Handler等相关信息还没有被关联到Activity,先建立一个排队队列。
//这其实就相当于你去银行办事,银行没开门,你们在门口排队等着一样。
getRunQueue().post(action);
return true;
}
}
复制代码
这里面也是利用attachInfo.mHandler来处理消息,它事实上是一个Handler的子类ViewRootHandler,同样的它也是使用Looper.prepareMainLooper()构建出来的Looper。
所以你可以看出Activity#runOnUIThread()、View#post()这两种方式并没有本质上的区别,最终还都是通过Handler来发送消息。那么对于那些既不在Activity里、也不在View里的代码 当我们想向主线程发送消息或者将某段代码(通常都是接口的回调方法,在这些方法里需要更新UI)post到主线程中执行,就可以按照以下方式进行:
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
//TODO refresh ui
}
})
复制代码
好了,到这里整个Android的消息机制我们都已经分析完了,如果对底层的管道这些东西感觉比较模糊,可以先理解Java层的实现。