概述
Android的消息机制对我们开发者来说应该是很熟悉的,其中最常见的用法就是利用Handler切换到主线程然后更新UI,消息机制的用法当然不仅仅局限于这个场景,但总的来说,消息机制解决了线程间和线程内的消息通信的问题。Android消息机制是指以Handler为上层接口,MessageQueue和Looper为底层支撑的工作过程。下面简单介绍一下这三个类:
①Handler是我们经常接触的,我们常用它来切换线程;
②MessageQueue,顾名思义,它是一个消息队列(内部实现是单链表),它的工作职责是把消息插入消息队列以及从消息队列获取一条消息;
③Looper,消息循环,它会不断地循环查询MessageQueue是否有新的消息,然后去处理这个消息。
实际上,这三个类是一起协调运作的,它们是一个整体。因此本文将详细介绍Java层的Handler、MessageQueue和Looper的工作原理。由于篇幅限制,Native层的消息机制会在下一篇文章进行解析。(注:本文源码均取自Android P)
Java层的消息机制
我们首先来看Java层的消息机制,因为这是我们开发时经常接触的,先理解Java层的消息机制然后再往底层去探究。我们分别从MessageQueue、Looper、Handler来解析它们的工作原理。
一、消息队列MessageQueue的工作原理
MessageQueue虽然被称作消息队列,但实际上它的内部实现是用单链表来实现的。它主要涉及两个操作:插入和读取。插入表示将一条消息插入消息队列等待处理,而读取则是从消息队列中读取出一条消息返回。
1、MessageQueue#enqueueMessage(Message,long)
该方法用于把一个Message
添加到消息队列中,详细请看代码:
boolean enqueueMessage(Message msg, long when) {
//这里的msg.target 实际上就是Handler实例
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
//判断该Message是否已经被加入了队列
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
//加锁,该代码段同一时间内只允许一条线程进程操作
synchronized (this) {
//如果该Handler被标记为了退出,那么入队失败
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(); //把当前Message标为为使用状体
msg.when = when;
Message p = mMessages; //mMessage实际上是消息链表的头部
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//如果当前消息链表为空或者消息触发时间为0,那么把该消息设置为链表的头部
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
// 同时满足以下3个条件则需要马上唤醒消息队列:
// 1、mBlocked为true
// 2、链表头部的消息是一个barrier(表现为message.target为null)
// 3、该message是一个异步消息
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
//遍历单链表,找出消息需要插入的位置,该位置的when需要比消息的when大,
//即消息在链表中的顺序是以时间排序的,when越大,则排在链表越后面。
//这里的when,可以理解为消息触发的时间,比如:
//handler.sendMessageDelayed()规定了消息的触发时间
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // 插入单链表中恰当的位置
prev.next = msg;
}
// needWake标记位为真,则唤醒消息队列,下面的是native方法,先从Native层开始唤醒。
// mPtr 表示的是Native层消息链表的头部
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
详细的分析以注释的形式写在了代码内,代码的逻辑还是很清晰的,当通过Handler
发送一个消息的时候,就会通过上述方法把该消息插入了消息队列(链表)中。首先先对该消息进行有效性判断,不符合条件的添加失败。然后开始遍历mMessages
,即消息链表;根据触发时间来找到消息需要插入的位置。当消息被插入单链表后,就要根据需要来确定是否需要唤醒该消息队列。所谓的唤醒的意思是指:由于有满足要求的新消息加入了队列,原本处于阻塞状态的消息队列就能够取出该新消息而返回。
我们把关注点放在needWake = mBlocked && p.target == null && msg.isAsynchronous()
唤醒条件的判断上。mBlocked
表示消息队列正在被阻塞;p.target == null
表示队列头部的消息是一个消息屏障,最后一个条件表示当前要插入的消息是一个异步消息。消息屏障是什么东西呢?我们先把这个放在一边,待会再回过头来解释。从上面三个条件可以知道,同时满足这三个条件后,会就调用nativeWake(mPtr)
方法,去唤醒Native Looper。
2、MessageQueue#next()
该方法用于在消息队列中获取一个消息,如果没有消息就会一直阻塞在这里,该方法运行于创建Handler所在的线程。
Message next() {
//如果消息队列已经退出 直接返回
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//调用Native方法,先处理Native层的新消息,根据nextPollTimeoutMillis参数确定超时时间
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
//在消息链表中寻找下一条消息,如果找到就返回处理
final long now = SystemClock.uptimeMillis();//获取当前时间戳,从开机到现在的时间
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//如果该msg是一个消息屏障,那么直接去找下一个异步消息来执行
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);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
// 如果调用了quit()方法,返回null,中止消息循环
if (mQuitting) {
dispose();
return null;
}
// 第一次for循环遍历的时候,如果队列空闲,那么可以去处理Idle Handler
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 运行空闲handlers。可以理解为如果消息队列为空,或者还没到触发时间,那么可以去执行别的功能
// 只有在第一次遍历的时候才会运行该代码块。
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);
}
}
}
// 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;
}
}
从代码逻辑来看,next()方法内部是一个死循环,它不断地遍历消息链表,直到找到一个可执行的消息后返回。同时,在每一次的循环内部,会先调用nativePollOnce(ptr, nextPollTimeoutMillis)
的一个native方法,该方法会在native层进行消息循环的处理。接着,在单链表中寻找下一条消息,并计算超时时间,然后进行下一次循环,直到超时时间到,此时该消息就准备好了,可以返回给Looper进行处理。而在第一次for循环遍历的时候,如果当前消息队列没有可执行的消息而处于空闲状态的时候,MessageQueue会调用IdleHandler
来进行额外的处理。
3、消息屏障的作用
我们查看MessageQueue的源码,可以看到它有以下方法:public int postSyncBarrier(long when) { }
public void removeSyncBarrier(int token){ }
阅读注释可以知道,上述两个方法可以添加一个消息屏障到队列中或从队列中移除消息屏障。而消息屏障的作用在于:如果消息队列遭遇到一个消息屏障,那么在此之后的所有同步消息都会被延迟执行,直到消息屏障被移除后,才会继续处理这些同步消息。而异步消息则不受到任何影响。消息屏障是由target为null的Message来表示。
4、IdleHandler的作用
在next()
方法内,在第一次遍历for循环的时候,如果当前消息列表是空闲状态,那么就会调用IdleHandler来进行空闲情况下的处理。我们来看看它到底是什么:
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
这是一个接口,声明了一个queueIdle()
方法,表示如果消息队列在等待新的消息到来的时候,可以调用该方法。我们来看一个例子:
class ActivityThread{
final class GcIdler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
doGcIfNeeded();
return false;
}
}
}
这是ActivityThread为MessageQueue所添加的一个IdleHandler,显然,这个handler表示了在空闲状态下进行垃圾回收的功能。
因此,这启发了我们可以通过IdleHandler
来对MessageQueue的空闲状态进行进一步的处理。比如这样:
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
return false;
}
});
小结:上面介绍了MessageQueue的几个方法,核心是enqueueMessage()和next(),前者表示将一个消息放在消息链表的正确位置,而后者是一个阻塞式循环直到可以返回一个消息给Looper。一种常见情况是:子线程通过enqueueMessage()把消息添加到了消息队列,而主线程在遍历的过程中会找到刚才插入的消息,从而把该消息返回给Looper。
二、Looper的工作原理
下面来介绍Looper的工作原理。Looper与线程相关,一条线程对应一个Looper,而一个Looper又对应一个MessageQueue。Looper充当了一个消息循环角色,它不断地进行循环,调用MessageQueue.next()来获取一个消息进行处理。
如果需要为子线程创建一个Handler,那么就要先调用Looper#prepare()
为该线程初始化一个Looper,否则会抛出异常。那么UI线程我们在业务层并没有显式地调用该方法,但是我们可以直接创建UI线程的Handler,这是为什么呢?这是因为在应用启动的时候,在ActivityThread#main()
方法里面,已经帮我们调用了Looper.prepareMainLooper()
方法,这样我们就可以在主线程直接使用Handler了。我们来看看Looper#prepare()
:
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed); //实例化一个MessageQueue,一个Looper对应一个MessageQueue
mThread = Thread.currentThread();
}
这里出现了ThreadLocal
,这个类笔者在之前的文章有专门解析过,这里就不再赘述了。简单地说,它是一个线程独立的变量,不同的线程所访问得到的值仅与该线程有关,与别的线程无关。这里把Looper设置为了线程独立的变量,也就是说调用了Looper#prepare()
方法的线程,会创建一个Looper实例与当前线程绑定起来。经过以上步骤,该线程的Looper就创建完毕了,接着如果要启动消息循环机制,就要接着调用Looper#loop()
方法,该方法真正地开启了消息循环,它会不断地从MessageQueue取出消息进行处理。
public static void loop() {
final Looper me = myLooper();
//在没有调用prepare()的线程而使用Handler会直接抛出异常
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//省略部分代码....
for (;;) {
Message msg = queue.next(); // 这里可能会阻塞,等待消息
if (msg == null) {
// 如果MessageQueue返回了null,表示已经没有消息要处理,退出消息循环
// 只有调用了quit()方法,MessageQueue才会返回null
return;
}
try {
msg.target.dispatchMessage(msg);
}
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);
}
msg.recycleUnchecked(); //回收该Message
}
}
loop()方法的逻辑不难理解,它的for循环内部,会不断地调用queue.next()
方法,从而获取一个Message,然后调用msg.target.dispatchMessage(msg)
方法来处理消息。实际上msg.target就是发送消息的Handler,也就是说,这消息从Handler发送到消息队列后,经过消息循环系统的循环后,最后又交给了Handler#dispatchMessage(msg)来处理,只是经过消息循环机制后,dispatchMessage(msg)方法会在创建Handler所在的线程执行,这样也就实现了代码逻辑切换到指定的线程去执行这一效果了。
接着,我们来看看主线程的消息循环是怎样的,在ActivityThread#main(args)
代码内:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
//省略部分代码...
Looper.prepareMainLooper();
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
我们可以看到,这里顺序执行了prepare()
和loop()
,开启了主线程的消息循环系统,这样子线程发送过来的消息,会经过主线程的消息循环然后切换到UI线程内去处理这些消息。
三、Handler的工作原理
上面讲述了MessageQueue和Looper的工作原理,它们是Handler得以运作的底层支撑,有了上述的知识基础,下面来理解Handler就容易很多了。接着我们来看Handler的工作原理。我们利用Handler发送消息有很多方法可以调用,一种常见的形式是:mHandler.sendEmptyMessageDelayed(msg,delayMillis)
,然后我们在Handler#handleMessage(msg)
来处理这个消息,此时线程已经被切换到了创建Handler的线程了。
1、首先,我们来看Handler的构造方法,看它的初始化过程做了哪些工作:
public Handler() {
this(null, false); //async默认是false,即采取同步消息的形式
}
public Handler(Callback callback, boolean async) {
// 下面检查是否存在内存泄漏的问题,为了防止内存泄漏,我们的Handler
// 需要写出静态内部类的形式
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 " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看出,在初始化的过程中,Handler会判断是否存在内存泄漏的问题,以及当前线程是否以及准备好了Looper(即Looper.prepare()是否已经被调用)。然后设置mCallback和mAsynchronous参数。从构造方法可以看出,默认的情况下我们使用无参构造方法所创建的Handler的mAsynchronous都是false,即该Handler发送的消息都是同步形式的,会被消息屏障过滤掉。当然,我们也可以利用带async参数的构造方法创建一个发送异步消息的Handler。
2、利用Handler发送消息
一般地,我们发送消息的时候有很多方法可以调用,比如post()和sendMessage()等,但实际上post()方法内部调用的还是sendMessage()方法。因此我们直接来看sendMessage的相关方法即可,比如说mHandler.sendEmptyMessageDelayed()
方法:
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain(); //获取一个Message实例,这里被设计成对象池的形式,便于复用
msg.what = what; //what参数用于标识某一个消息
return sendMessageDelayed(msg, delayMillis);
}
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 queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; //这里的this 指向了调用该方法的对象,即我们的Handler
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
代码的逻辑很容易理解,生成一个消息后,就会调用MessageQueue#enqueueMessage(msg,when)
方法,把该消息插入了消息队列。所以说,Handler的sendMessage()系列方法,只是把消息插入了消息队列,后续是交给Looper的消息循环来进行处理的。
3、利用Handler处理消息
前面提到,Looper的loop()方法会不断调用MessageQueue#next()方法来获得一个Message,然后调用msg.target.dispatchMessage(msg)
方法,最终把消息又交给了Handler处理,此时已经被切换到了目标线程。我们来看Handler#dispatchMessage(msg)
的源码:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg); //如果msg有callback,那么优先调用
} else {
if (mCallback != null) {
//如果Handler有callback,那么调用Handler的callback来处理
if (mCallback.handleMessage(msg)) {
return;
}
}
//否则,最后调用Handler的重写方法来处理这个消息
handleMessage(msg);
}
}
处理逻辑很清晰,首先会判断msg是否带有一个callback的成员变量,这里的msg.callback实际上就是一个Runnable,这是通过handler.post(runnable)来添加的。然后接着判断Handler的mCallback是否不为空,这里的mCallback实际上创建Handler的另一种方式,即我们可以不要派生Handler子类,而是利用Handler handler = new Handler(callback)
来创建一个Handler。在以上条件都不满足的情况下,最后交给Handler#handleMessage(msg)
来处理,这也是我们派生子类需要重写的方法。
总结
上面介绍了Handler、MessageQueue和Looper三者的工作原理,下面给出一个示意图来方便大家理解整个消息机制的工作原理。
首先是子线程的Handler实例发送一个消息,这个消息就会被插入到消息队列中等待被处理。在主线程的Looper遍历的过程中,会发现消息队列新增了消息,就会把这条消息取出来交给Handler处理,处理完毕后Looper继续遍历过程查找下一条消息。
Handler工作流程
从去年到现在,我根据市场技术栈的需求,整理了一套JAVA的最新教程,如果你现在也在学习Java,在入门学习Java的过程当中缺乏系统的学习教程,你可以加我的Java学习交流群:【94687,1227】,获取,群里还有学习手册,面试题,开发工具,PDF文档教程,可以自行下载。