安卓消息机制
以下源码皆基于安卓SDK版本:android-28
本文纯属本人基于安卓源码对安卓消息机制的总结。
Message篇
对于Message类,我们只需要关注以下部分即可:
public final class Message implements Parcelable{
long when;
Message next;
// 以下省略其他的成员变量和方法
}
Handler篇
对于Handler,我们主要讲解两个方面:
- Handler是如何处理消息的
- Handler发送消息的常用方式以及内部原理
Handler是如何处理消息的
首先,针对消息的处理,Handler内部提供了三种处理方式,具体可以参照Handler内部dispatchMessage
的源码,如下:
1 public void dispatchMessage(Message msg) {
2 if (msg.callback != null) {
3 handleCallback(msg);
4 } else {
5 if (mCallback != null) {
6 if (mCallback.handleMessage(msg)) {
7 return;
8 }
9 }
10 handleMessage(msg);
11 }
12 }
首先,我们可以看到针对消息msg来说,如果它自己被指定默认的消息处理函数,则调用该函数进行处理,具体体现在上述代码第三行,那么该函数具体做了什么事,那么我们看一下安卓Handler类对于该方法的实现,如下:
private static void handleCallback(Message message) {
message.callback.run();
}
先不着急,我们在看一下Message类中的callback是个什么,继续查找源码,如下:
public final class Message implements Parcelable {
// 以上省略其他的成员变量和成员函数
/*package*/ Runnable callback;
// 以下省略其他的成员变量和成员函数
}
众所周知,Runnable是一个内部只定义了一个run方法的接口。那么结合dispatchMessage
、handleCallback
、Message类来看,我们可以知道Handler处理消息的整个逻辑:
- 对于Handler对象,如果其处理消息时,消息自己被设置了某一消息处理函数,那么就会优先调用该函数对消息进行处理;
- 如果没有为要处理的消息对象指定消息处理函数,且Handler对象自己被指定了消息处理函数,即
mCallback
的值不为空,则优先调用mCallback
的消息处理函数,即mCallback.handleMessage
函数; - 若Handler对象自己没有被指定消息处理函数,则调用Handler对象自己内部的成员函数来处理消息,即
handleMessage
函数
针对以上第2点、第3点结论的推出,我们可以结合dispatchMessage
函数的处理逻辑和以下代码块分析推理得出
mCallback
是Handler类内部的一个成员变量,其相关的声明如下:
final Callback mCallback;
// Callback 是Handler类内部定义的接口
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public boolean handleMessage(Message msg);
}
// 这个是Handler内部自己的成员函数,我们可以很清楚的知道,
// 如果调用dispatchMessage的对象是Handler声明出来的,那么handleMessage实际上什么也没有做
// 如果调用dispatchMessage的对象是Handler的子类声明出来的,并且这个子类重新实现了handleMessage
// 函数,那么就会使用子类实现的处理逻辑对消息进行处理,当然这个前提是前面提到过的第1点、第2点都不会执行 // 的前提下
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
那么问题就来了,mCallback
是什么时候指定的?熟悉Spring的同学肯定都知道,里面有一个“依赖注入”的概念,那么针对mCallback
成员变量的设置,我们猜想要么可以通过“构造器注入”或者“set注入”的方式来为mCallback
变量进行赋值,对于我们的Handler来说,采取的是“构造器注入”的方式,具体可以查看Handler的构造函数。下面简单的贴一下与mCallback
相关的Handler构造函数的实现,其他的Handler的构造函数的实现不予展示,感兴趣的同学可以自行查阅Handler的源码。
1 public Handler(Callback callback, boolean async) {
2 if (FIND_POTENTIAL_LEAKS) {
3 final Class<? extends Handler> klass = getClass();
4 if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
5 (klass.getModifiers() & Modifier.STATIC) == 0) {
6 Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
7 klass.getCanonicalName());
8 }
9 }
10
11 mLooper = Looper.myLooper();
12 if (mLooper == null) {
13 throw new RuntimeException(
14 "Can't create handler inside thread " + Thread.currentThread()
15 + " that has not called Looper.prepare()");
16 }
17 mQueue = mLooper.mQueue;
18 mCallback = callback;
19 mAsynchronous = async;
20 }
21
22 public Handler(Looper looper, Callback callback, boolean async) {
23 mLooper = looper;
24 mQueue = looper.mQueue;
25 mCallback = callback;
26 mAsynchronous = async;
27 }
如上源码所示,第18行和第25行为Handler的mCallback
指定了值。
Handler发送消息的常用方式以及内部原理
Handler对于消息的发送方式有以下几种函数可供调用:
public final boolean post(Runnable r);
public final boolean postAtTime(Runnable r, long uptimeMillis);
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis);
public final boolean postDelayed(Runnable r, long delayMillis);
public final boolean postDelayed(Runnable r, Object token, long delayMillis);
public final boolean postAtFrontOfQueue(Runnable r);
public final boolean sendMessage(Message msg);
public final boolean sendEmptyMessage(int what);
public final boolean sendEmptyMessageDelayed(int what, long delayMillis);
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis);
public final boolean sendMessageDelayed(Message msg, long delayMillis);
public boolean sendMessageAtTime(Message msg, long uptimeMillis);
public final boolean sendMessageAtFrontOfQueue(Message msg);
public final boolean executeOrSendMessage(Message msg);
查看以上函数的源码,其最终都是调用到Handler类中的下面这个函数,注意是私有方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis){
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
**其中,这个queue对象是Handler类中的mQueue
成员变量。**那么问题来了,这个mQueue
变量是哪里设置的,是“构造器注入”还是“set注入”?通过查看Handler类的源码,我们发现只有两个地方对这个mQueue
变量进行了赋值。代码如下:
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue; // 在这里被赋值
mCallback = callback;
mAsynchronous = async;
}
public Handler(Callback callback, boolean async) {
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());
}
}
mLooper = Looper.myLooper(); // 取到mLooper对象
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对象被构造的时候就可以为成员变量mQueue
指定相应的值。第一段代码指定是外来传进来的looper对象中的mQueue
,第二段代码取的则是Looper.myLooper().mQueue
。这里,我们重点关于第二段代码。这时候,我们的第一个疑问就是:Looper.myLooper()
是个什么玩意儿?它有什么用?是干什么的? 强行三连,最为致命!我们先把这个疑惑放在一边,只需要知道,这个mQueue
是从某个Looper对象中来的,不管它是外部的Looper对象设进来还是Looper.myLooper()
设进来的。我们先看看那个return queue.enqueueMessage(msg, uptimeMillis);
这行代码是做什么的。那么就需要到MessageQueue
这个类中去查看了,那么接下来进入我们的MessageQueue
篇。
MessageQueue
篇
在Handler篇,章节"Handler发送消息的常用方式以及内部原理"的末尾部分,我们提到了enqueueMessage
这个函数,那么我们就详细看看它的源码实现。
1 boolean enqueueMessage(Message msg, long when) {
2 if (msg.target == null) {
3 throw new IllegalArgumentException("Message must have a target.");
4 }
5 if (msg.isInUse()) {
6 throw new IllegalStateException(msg + " This message is already in use.");
7 }
8
9 synchronized (this) {
10 if (mQuitting) {
11 IllegalStateException e = new IllegalStateException(
12 msg.target + " sending message to a Handler on a dead thread");
13 Log.w(TAG, e.getMessage(), e);
14 msg.recycle();
15 return false;
16 }
17
18 msg.markInUse();
19 msg.when = when;
20 Message p = mMessages;
21 boolean needWake;
22 if (p == null || when == 0 || when < p.when) {
23 // New head, wake up the event queue if blocked.
24 msg.next = p;
25 mMessages = msg;
26 needWake = mBlocked;
27 } else {
28 // Inserted within the middle of the queue. Usually we don't have to wake
29 // up the event queue unless there is a barrier at the head of the queue
30 // and the message is the earliest asynchronous message in the queue.
31 needWake = mBlocked && p.target == null && msg.isAsynchronous();
32 Message prev;
33 for (;;) {
34 prev = p;
35 p = p.next;
36 if (p == null || when < p.when) {
37 break;
38 }
39 if (needWake && p.isAsynchronous()) {
40 needWake = false;
41 }
42 }
43 msg.next = p; // invariant: p == prev.next
44 prev.next = msg;
45 }
46
47 // We can assume mPtr != 0 because mQuitting is false.
48 if (needWake) {
49 nativeWake(mPtr);
50 }
51 }
52 return true;
53}
逐块来看代码功能:
- line2到line7, 做一些检查:检查消息msg是否有对应的Handler对象和msg当前是否在被使用。
- line9告诉我们:以下的过程是只允许一个线程进行处理,是需要做线程安全处理的。
- line10到line16:告诉我们如果
mQuitting
为真,那么对消息msg进行回收处理。 - line18:将消息标记为正在使用
- line19: 设置消息的处理时间(主要用来排消息在消息队列中的位置),有以下几种方式:
- 系统启动后,当前调用postXXX或者sendXXX这类方法的时间作为时间标记,这个时间标记仅用来安排消息在消息队列中的位置,而不是消息的执行时间
- 在上面的时间标记的基础上,再往后延一定的时间
- 人为指定一个时间标记,很灵活
- line20:
mMessages
其实是由各个Message链接起来的链表结构,关于链表可自行查阅《数据结构与算法》中的链表章节。mMessages
可能为空,表示一个消息也没有;也可能不为空,表示至少已经有一个Message在这个链上。 - line22到line27:链表为空或者新进来的消息的when值比链表头部节点的when值小,则将新进来的消息加入链表的头部
- line32到line44: 链表不为空且新加入的消息的when值比链表头部节点的when值大,则与链表中的其他节点的when值进行比较,一直找到比链表中某个节点的when值小为止,然后将新的消息插入找到的那个节点的前面。(其现实意义就是:链表中的消息是按照进入队列中那个时间点的值进行排列的,早进入队列的消息的when值小,所以需要排在靠近链表头部的位置,晚进入队列的消息的when值大,所以需要排在靠近链表尾部的位置,这样就以链表的形式完成了一个消息的队列结构)。
Looper篇
先看看Looper内部几个重要的成员变量
public final class Looper {
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
}
再看看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));
}
代码比较简单,但是sThreadLocal.get()
干的事情可不少,以下贴一下源码:
// 类ThreadLocal的成员函数
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
以下贴一下getMap
的代码:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
以下,贴一下threadLocals
是个什么东西:
public class Thread implements Runnable{
ThreadLocal.ThreadLocalMap threadLocals = null;
// 省略其他的成员变量和成员函数
}
经过在Thread的源码中查找,Thread并没有提供为threadLocals设置值的方法,所以不需要考虑使用Thread的成员函数为其设置值这条路径(这点很重要)。还有另外重要的一点,threadLocals只在声明的时候指定了默认的初始值null和执行exit函数时设置为null。
然后我们接着返回去看我们上面贴出来的get方法:
- 对于一开始,当前线程(此时记做为Ti)调用get方法是获取的map肯定是null,所以会调用:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
由于map是空,所以调到createMap
为当前的线程对象t的threadLocals进行创建操作,具体以假代码的方式描述为: **t.threadLocals的table数组中存储了【Looper类的sThreadLocal,null】这样形式的节点元素。**table是ThreadLocalMap的成员变量,存储Entry元素。其中null元素是由于调用initialValue()
函数而来的。那么这样我们再回到开头看prepare函数,其实if语句内的判断是返回null,所以执行sThreadLocal.set(new Looper(quitAllowed));
将set函数的代码贴出来,如下:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
由于我们在get的时候已经创建过关于当前调用prepare函数的线程对象的map了,所以这里只是将那个新new出来的Looper对象加到这个线程对象Ti的threadLocals中去。
综上所述:
- 当前哪个线程调用
Looper.prepare()
,其本质上是将Entry元素,即【Looper类的静态成员变量sThreadLocal,new出来的Looper对象】增加到当前线程的threadLocals中去,threadLocals本质上是个map,其key=Entry中的key,value=Entry的value。 - 当前线程不能调用
Looper.prepare()
两次及两次以上,通过prepare的函数内容我们知道,调用多次会抛出RuntimeException异常对象。
然后我们看看如何使用Looper进行消息的处理
其本质上是当前线程显示调用Looper.loop()
函数来进行消息的处理,那么我们则看看loop函数内部的实现,如下:为了方便,我们将分析过程写在代码内部。
public static void loop() {
// myLooper其实是sThreadLocal.get(),那么我们当前线程调用Looper.loop()时,获取的就是我们当前线程之前在调用时
// new出来的Looper对象。
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 这个new出来的Looper对象的mQueue的内容我们暂时是不知道,所以先着重作为一个重点,后续需要去看这个Looper对象me中的
// mQueue的值是怎么来的 !!!!!!!!!!!
0 final MessageQueue queue = me.mQueue;
// !!!!!!! 以下关于Binder、slowDeliveryDetected、traceTag、slowDispatchThresholdMs、
// slowDeliveryThresholdMs、和与之相关的代码都先不关注
// 只关注我下面标注数字的代码行即可
// 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();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
boolean slowDeliveryDetected = false;
1 for (;;) {
// 在queue中沒有消息的時候,這個loop()函數會阻塞在下面這行代碼處
// 因為Android的底層內核是Linux系統,Linux內核會監聽文件描述符,
// 這裡的文件描述符對象從抽象上看就是Android的Message,當沒有Message
// 加到當前線程的MessageQueue中去的時候,通過Looper的loop()函數去調用next
// 的時候會發生阻塞
2 Message msg = queue.next(); // might block
3 if (msg == null) {
4 // No message indicates that the message queue is quitting.
5 return;
6 }
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
7 try {
8 msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
9 } finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
10 }
if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", 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);
}
11 msg.recycleUnchecked();
}
}
根据上面标注的数字,可以看出来,其实代码逻辑很简单,就是从me这个Looper对象mQueue中取消息出来,然后执行标注7那一行的代码即可,其中取消息的过程可能发生阻塞(即标注2那一行的代码)。其中标注7的消息具体处理逻辑请详看“Handler篇—Handler是如何处理消息的”中的“Handler处理消息的整个逻辑”即可。
接下来那么关于loop()函数,我们就只剩下一个疑问了,我们已经知道了me是怎么来的了,那么代码中我们标注0的地方me.mQueue这个是怎么来的。通过在整个Looper类中查找mQueue,我们只在两个地方发现可能会设置mQueue的地方:
- Looper类的构造函数:所以我们结合prepare函数可以知道,mQueue的创建是这个时候创建出来的
public static @NonNull MessageQueue myQueue() {
return myLooper().mQueue;
}
- 同一个包(package)下的其他类可能为mQueue进行增删(因为Looper类中的变量mQueue是包访问权限)
所以我们的结论是:后两种方式都极有可能改变Looper中的mQueue,但必须都是同一个包下的类才有权修改其内容。
重看Handler类
我们在“Handler篇—Handler发送消息的常用方式以及内部原理”章节中提到过Handler对象不管是postXXX还是sendXXX方式的发送消息方式,最终都是调用enqueueMessage函数,其作用就是将postXXX或者sendXXX中的参数Message对象加到其mQueue成员变量中。通过查阅Handler类的源码知道,这个mQueue就是在Handler对象初始化(即调用构造函数)的时候设进来的,其来源只有以下两种:
- 将Handler的构造函数中通过参数传进来的Looper对象的mQueue设置给他
- 取当前线程的Looper对象的mQueue
💡 结论: 诶! 这不是将Handler和Looper对象联系起来了吗?这样我们就知道了,Looper对象在执行loop()函数时,其内部的队列mQueue中的消息正是Handler对象通过postXXX或者sendXXX方法添加进去的呀!❤️
再回过去看Looper类的loop()函数
这次我们看数字标注的2-6,看代码我们会产生以下的疑问:如果队列中没有消息不是在标注5的那一行return了吗,loop函数不是直接结束了吗,如果在loop()函数调用前,Handler对象根本就没有调过postXXX或者sendXXX这类的方法,那不是loop函数的调用就是个摆设,没起一点作用。
我们在源码中注意到有这么一行英文注释(在我们标注的数字4那一行): “// No message indicates that the message queue is quitting.” 那我们就想,消息队列退出是什么意思,所以我们去MessageQueue中查找有没有与之对应的标记,正好找到这么一个成员变量 “mQuitting”,这个值的赋值只有在调用MessageQueue类的void quit(boolean safe)
时才发生,既然MessageQueue和Handler和Looper类相关性如此密切,我们则去这两个类中去查找是否调用了这个quit函数,然后只在Looper类中找到了quit的调用,在Handler中没有找到,代码如下:
public void quit() {
mQueue.quit(false);
}
💡 原来这句英文注释是这个意思,并不是队列中没有消息了就直接结束loop()函数,而是这个Looper对象自己调用了自己的quit()函数才会终结这个loop()函数内部的循环,最终终结这个消息的处理过程。
最终总结
至此,Android对于消息的处理的整个逻辑就比较清晰了,我们综合以上对于“Message篇”、“Handler篇”、“MessageQueue篇”、“Looper篇”的分析,得出简要的结论如下:
- 首先,为了处理当前线程中的消息,Andorid为当前线程创建一个Looper对象来负责消息的处理,这个创建过程就是Looper类的prepare函数,该函数内部的if判断+异常抛出保证了,每一个线程只能创建一个Looper对象
- 为了进行消息的处理,Android的Looper类提供了loop()函数来无限地对消息进行处理,除非显式地调用了quit函数终结消息处理
- 消息是Handler对象通过postXXX或者sendXXX这类方法将其加到调用这些方法的当前线程的Looper对象中的(即第1点提到的Looper对象)
- 与Handler对象绑定的Looper对象要么是当前线程的Looper对象,要么是其他传进来的Looper对象(构造函数传Looper对象:这个就有可能是其他线程的Looper对象)