Android的消息机制
提到消息机制大家应该会先想到Handler。对于Android开发者来说想必Handler都并不陌生。
Handler的定义是一个Handler允许你发送和处理跟线程对象关联的消息和Runnable对象。而每个Handler实例关联一个线程和该线程所属的消息队列。通过他可以轻松地将一个任务切换到Handler所在的线程中执行。
我们大多数人使用Handler,常用于在子线程上将更新到UI的操作切换到主线程执行。像如下例子。但本质上说,Handler并不是专门用于更新UI的,它只是常被开发者用来更新UI。那首先一个疑问是Handler是如何实现在子线程上将更新UI的操作切换到主线程执行呢。针对第一个疑问,我们再把问题扩大下,为什么我们的应用程序在打开的时候,没有遇到程序主线程执行完毕然后直接退出的情况,它是如何实现的。看完这篇文章或许你就会有答案。
public class MainActivity extends AppCompatActivity {
TextView tv;
class MHandler extends Handler {
@Override
public void handleMessage(@NonNull @NotNull Message msg) {
super.handleMessage(msg);
tv.setText(msg.obj.toString());
}
}
MHandler mHandler = new MHandler();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.tv);
Message message = new Message();
message.obj = new String("message");
new Thread(new Runnable() {
@Override
public void run() {
mHandler.sendMessage(message);
}
}).start();
}
}
Handler,MessageQueue,Looper,Message
说到Handler机制,那就必然会涉及到MessageQueue,Looper,Message这几个类。
首先看如下图,观察他们之间的依赖关系。
我们这里先大致概括下这几个类的作用。
-
Message 用于传递消息的消息类。他是一个单链表结构。
-
MessageQueue 消息队列 他是一个消息Message的管理类,它持有Message 对象,常用于插入消息和取消息。
-
Looper 用于在一个线程中死循环处理消息。
Message
首先我们来看Message类 我们首先看一个Message对象可以携带的参数,如下
public final class Message implements Parcelable {
// 用户定义的消息码,以便接收者能够识别消息是什么类型
public int what;
// arg1和arg2是消息可选int类型参数
public int arg1;
public int arg2;
// 存放需要传递的任意对象
public Object obj;
// 消息处理的时间
long when;
// 消息携带的bundle类型数据
Bundle data;
// 处理消息的handler
Handler target;
// 回调方法
Runnable callback;
// 指向消息的下一个消息。通过此变量实现链表。
Message next;
....
MessageQueue 消息队列的工作原理
MessageQueue持有Message对象,它是管理Message单链表结构对象。 我们先看下MessageQueue类的几个方法。
boolean enqueueMessage(Message msg, long when) {
// 从这里可以看出msg.target得不能为null
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) {
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;
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 {
// 这里的代码块完成消息的插入操作。
// 首先他将要找到合适插入位置。消息的链表是按when触发时间从小到大排序。也就是
// 咱们要从当前全局变量是mMessages中找到合适的位置。首先Message是个链表结构,
// 所以通过next方法可以获取下一个消息的值,prev代表前面的消息,p代表当前消息。
// 所以这里通过循环执行next方法来更新prev和p的值,然后通过判断语句判断要插入
// 的消息是否小于当前消息的触发时间来终止循环,这代表找到合适的插入位置。
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
然后我们再看下next方法,next方法是从消息队列从取出满足现在触发时间的消息。下面的代码很长,我们先挑重点看。注意 synchronized (this){...}这里的代码块的注释。
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
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();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
....
if (msg != null) {
// 如果当前时间还没到达当前消息触发的时间点when,则设置nextPollTimeoutMillis值,
// 该值代表下一次轮询需要执行获取消息超时的时间。也就是距离当前时间多久到达当前消息
// 触发的时间。然后会在下一次for循环的时候执行
// nativePollOnce(ptr, nextPollTimeoutMillis);(在synchronized (this)前面的位置)
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 如果当前时间到达当前消息的触发时间,则获取消息并返回。
mBlocked = false;
// 这里涉及到同步障碍的情况才导致prevMesg 可能并不为null。同步障碍不是本篇文章重点,
// 暂不讨论,之后有机会我会出篇文章单独讨论。
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;
}
....
}
}
}
从这两个方法,我们可以看出尽管MessageQueue叫消息队列,但是它的内部实现并不是用的队列,实际上它是通过一个单链表的数据结构来维护消息列表。
Looper的工作原理
然后我们再看看Looper类 Looper对象的作用就是它会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则一直阻塞在那里。我们首先先看下Looper类,android官方给出的典型的使用例子。
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
我们可以看到Looper使用跟prepare和loop方法有关。我们看下prepare代码,可以发现prepare是一个静态方法,它最终会新建Looper对象给成员变量sThreadLocal。而sThreadLocal是ThreadLocal类型的,它提供线程本地变量。对ThreadLocal不了解的建议先去搜索相关文章了解下。
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));
}
public final class Looper {
// sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
@UnsupportedAppUsage
private static Looper sMainLooper; // guarded by Looper.class
private static Observer sObserver;
@UnsupportedAppUsage
final MessageQueue mQueue;
final Thread mThread;
...
}
我们可以看到构造方法,会初始化Looper类的消息队列常量mQueue和赋值当前线程给mThread。Looper除了prepare方法外,还提供了prepareMainLooper方法,这个方法主要用于给主线程创建Looper使用的。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
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还有一个重要的方法及时loop方法。只有调用了loop后,消息循环系统才开始起作用。这里我省略了部分代码,保留了核心的代码。
public static void loop() {
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;
....
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// 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);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;
final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
...
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
msg.recycleUnchecked();
}
}
}
通过代码观察我们可以看到,loop方法是一个死循环,终止循环的唯一方式是MessageQueue的next方法返回了null。当Looper的quit(消息队列退出方法)方法被调用时,它的next就会返回null。
loop方法会不停地调用MessageQueue的next方法来获取消息新消息。而next方法是一个阻塞操作,当没有消息时,next方法会一直阻塞在那里。如果从MessageQueue的next方法获取了新消息,则处理这条消息:msg.target.dispatchMessage(msg);这里的msg.target是发送了这条消息的Handler对象那个。这样Handler发送的消息最终又交给它的dispatchMessage方法来处理了。注意这里不同的是Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的,这样就成功地将代码逻辑切换到指定中的线程中执行了。 现在如果看不懂这句话,没关系后面讲解到Handler的工作原理的时候,你就会明白了。这里先有个这个概念。
Handler的工作原理
Handler的主要工作时发送和接收消息。我们常用的发送消息方法就是sendMessage(msg)。对于接收消息我们常常时实现Handler类的handleMessage()方法。handleMessage在Handler类是一个空实现的方法。
# Handler
public Handler() {
this(null, false);
}
public Handler(@Nullable Callback callback) {
this(callback, false);
}
public Handler(@Nullable 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();
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;
}
# Looper
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
观察Handler的构造方法,我们可以看到首先会调用Looper类的静态方法myLooper方法获取当前线程的Looper对象并赋值给成员变量mLooper。如果mLooper则带代表当前线程并没有创建Looper对象,也就是当前线程并没有执行Looper.prepare()故抛出异常。如果有则mQueue = mLooper.mQueue;获取消息队列。这里的Callback如下,它也是用于接收处理消息对象的。
/**
* Callback interface you can use when instantiating a Handler to avoid
* having to implement your own subclass of Handler.
*/
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
boolean handleMessage(@NonNull Message msg);
}
目前我们可以得出Handler的创建依赖Looper对象。而Looper则对MessageQueue有依赖。如下图:
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull 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);
}
// 将消息插入操作由queue.enqueueMessage(msg, uptimeMillis)代理完成。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
//
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
我们可以看到平常我们使用的sendMessage方法,其实最终会调用MessageQueue的对象enqueueMessage(msg, uptimeMillis)方法往消息队列插入消息。然后该方法又交由queue.enqueueMessage(msg, uptimeMillis)代理完成。我们知道调用enqueueMessage传入的uptimeMillis的参数是设置消息的触发时间也就是msg.when。然后在我们调用sendMessage(msg)的时候,方法内部会调用 sendMessageDelayed(msg, 0);来帮我们计算Message对象的触发时间when。
接下来我们再看下接收消息并处理的方法dispatchMessage(Message msg)。
# Handler
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
我们可以看到我们平常继承实现Handler类实现的handleMessage,就是在这一步被回调。同时我们也可以看到实现接收处理消息,还可以对Message对象,指定callback(这里的callback是runnable类型)来实现,如下代码。或者在创建Handler对象的时候,使用Handler(@Nullable Callback callback)这个构造方法创建,传递Callback对象给它。
Message msg = Message.obtain(mHandler, new Runnable() {
@Override
public void run() {
System.out.println("run");
}
});
new Thread(new Runnable() {
@Override
public void run() {
mHandler.sendMessage(msg);
}
}).start();
现在我们再回到Handler的构造方法。
public Handler(@Nullable 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();
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;
}
主线程的消息循环
我们可以发现如果当前线程没有Looper的话,就会抛出 "Can't create handler inside thread that has not called Looper.prepare()"的异常,这也解释了在没有Looper的子线程创建Handler会引发程序异常的原因。但我们平常在主线程创建Handler的时候,并没有调用过Looper.preare()方法。但是又没有抛出异常。而且我们也没有执行loop方法,但又能正常运转。这又是为啥?
其实是因为的android的主线程就是ActivityThread帮我们把这个工作做了。主线程的入口方法为main()
# ActivityThread
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// Install selective syscall interception
AndroidOs.install();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
你看方法里执行了Looper.preapareMainLooper()方法来创建主线程的Looper。设置主线程的looper。然后在main最后执行looper的loop方法。
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
大致流程如图:
总结
首先回到开头的疑问是Handler是如何实现在子线程上将更新UI的操作切换到主线程执行呢。其实就是子线程往与主线程共享的消息队列(MessageQueue)插入消息。然后主线程创建Looper对象,然后执行loop方法,不停的循环执行next方法来获取其他线程往消息队列(MessageQueue)插入的消息,然后将消息分发给他对应的handler来处理。
然后第二个问题是为什么我们的应用程序在打开的时候,没有遇到程序主线程执行完毕然后直接退出的情况,它是如何实现的。这是因为主线程不停循环执行某个代码块。而那个代码块,就是我们在主线程创建Looper对象中执行的loop方法。也就是ActivityThread的main方法中...
public static void main(String[] args) {
....
Looper.prepareMainLooper();
...
Looper.loop();
...
}