Android消息机制主要指的是Handler的运行机制,是一块很有意思,也很有研究意义的内容。本文计划在较短的篇幅内,通过一定的源码,分析Android消息机制,并在结尾说点”题外话“,帮助我们理解消息机制在安卓应用中的作用。
Android的消息机制伴随着安卓四大组件的生命周期,Activity启动,Service绑定与注册,Broadcast广播的发送,ContentProvider的启动等过程,先是依靠Binder将主线程切到Binder线程池的线程,系统完成组件对象的创建、绑定以及其他的准备工作后,再通过消息机制将线程切回至主线程,在主线程调用组件的生命周期方法。例如Activity、Service的onCreate(),ServiceConnection的onServiceConnected()等方法,就是系统通过发送消息给消息队列,由消息队列回调在使得这些方法运行在主线程。
另外由于Android开发规范的限制,我们并不能在子线程中访问UI控件,通过主线程(也就是UI线程)的Handler,可以将更新UI的操作切换到主线程中执行。
如果我们要用一句话来总结安卓消息机制的作用:极大地简化在子线程中访问主线程操作,更容易地在主线程中控制四大组件的生命周期,也使得UI线程对于用户的响应更加高效、流畅。
在引言我们说了,消息机制主要指的是Handler的运行机制,而Handler
运行需要底层的MessageQueue
和Looper
的支撑,那么这三者的关系如何呢?相互之间是怎么配合的?这里我们先给出结论:Handler发送消息到MessageQueue中存储,Looper从MessageQueue中取出消息并执行。 接下来我们逐步通过源码进行分析。
MessageQueue,也就是消息队列,主要包含两个操作:插入和读取。插入和读取对应的方法分别为enqueueMessage
和next
,其中读取操作同时还伴随着消息的移除操作。我们说消息队列,然而MessageQueue的内部并不是通过队列来存储消息的,为了提高消息插入和删除的效率,实际上MessageQueue是通过单链表的形式来维护消息列表的,我们来看看enqueueMeesage()的源码:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
// target就是此消息的接收方
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
// msg是否正在被使用
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; // 是否需要唤醒线程
// 如果消息队列为空(p = null),或者新消息是一个即时消息
if (p == null || when == 0 || when < p.when) {
// 把msg作为消息队列的新头部,第一个执行
msg.next = p;
mMessages = msg;
needWake = mBlocked; // 如果mBlocked = true,线程阻塞了,就需要唤醒线程
} else {
// 向单链表插入一条延时消息:根据消息的延时时间when,将消息插入链表一个合适的位置
// 原理是when越小,消息所处的位置越靠前
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
// 找到链表中第一个小于when的位置
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 插入new msg
msg.next = p;
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr); // 唤醒线程
}
}
return true;
}
通过源码中的注释我们已经能够理解enqueueMessage的插入过程,接下来我们看next():
Message next() {
// 当调用Looper的quit()方法后,即Looper停止后,mPtr为0
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();
}
// 阻塞方法,nextPollTimeoutMillis为最长阻塞时间
// nextPollTimeoutMillis = 0, 不阻塞,立即往下执行
// nextPollTimeoutMillis = -1, 一直阻塞,不会超时
// nextPollTimeoutMillis > 0, 最长阻塞nextPollTimeoutMillis(ms),如果期间线程被唤醒,立即往下执行
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 手机开机到现在的时间
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// msg.target = null 说明此消息是通过postSyncBarrier()发送的消息,是个同步屏障
// looper遇到同步屏障后会忽略同步消息,只处理异步消息
// 同步屏障实际上是让消息机制优先处理异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 当前这个消息还没到指定的处理时间时,继续延迟,剩余延迟时间 msg.when - now
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
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 {
// 单链表内没有消息
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// 如果执行到这里,说明单链表中没有消息,接下来处理一些不紧急的任务(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);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
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()是一个无线循环的方法,如果队列中没有消息,那么next()会一直阻塞在这里。当有新消息到来时,next()方法会返回这条消息并将其从单链表中移除。
我们分析了MessageQueue主要是对消息进行插入和移除,分别使用enqueueMessage()和next()方法,那谁来调用消息队列的这些方法呢,其实就是Looper,Looper在消息机制中扮演着消息循环的角色,具体来说就是它会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里。
Looper和MessageQueue关系很紧密,在Looper的构造方法中,它会创建一个MessageQueue对象,并将该对象保存起来:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
可以看到,作为Looper中唯一的一个构造方法,这个方法居然是private
的,仅允许类内部创建它的实例。我们如果要创建一个Looper,需要调用它的prepare()方法,Looper除了prepare()以外,还提供了prepareMainLooper(),这个方法主要是给主线程(ActivityThread)创建Looper使用的,但本质上还是调用的prepare(),接下来我们看看prepare()方法的源码吧:
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));
}
这里涉及到一个ThreadLocal对象,它并不是线程,它的作用是可以在每个线程中存储数据,不同线程的数据互不干扰。在消息机制中,ThreadLocal就存储了在不同线程中创建的Looper对象,这些对象是互不干扰的,我们通过ThreadLocal.get()方法就可以得到当前线程中的Looper对象。关于当前属于哪个线程的判断,由get()方法内部处理,这里我们不对ThreadLocal做过多的分析。
同时,我们可以看出,在一个线程中只有一个Looper对象,重复创建会报运行时异常。Looper对象只会创建一次,因此在Looper构造方法内部中的MessageQueue对象也只有一个,而Handler是可以多次创建的。
接下来我们看看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.");
}
// mQueue对象
final MessageQueue queue = me.mQueue;
// ...
for (;;) {
// 调用mQueue.next()读取消息队列中的消息,没有新消息的话就会堵塞在这
Message msg = queue.next(); // might block
// loop()方法唯一出口
if (msg == null) {
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);
}
// ....
try {
// 在Looper中调用Handler的dispatchMessage()方法,使得将代码逻辑切换到Looper所在线程中执行
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);
}
}
// ....
}
}
Looper的loop方法也很好理解,我们省去一些不必要的代码。可以看出,loop()实际上也是一个死循环,唯一跳出循环的方式就是MessageQueue的next()方法返回null。当Looper的退出方法被调用时,Looper就会调用MessageQueue的quit(boolean)方法,将队列的mQuitting变量置为true,这样当MessageQueue执行下一次循环的时候会发现mQuiting标志为true,进而使得next()返回null,Looper的loop()也随之跳出循环结束。读者可以结合MessageQueue的next()源码,更容易理解loop的退出过程。
Looper提供了两个退出方法:quit()和quitSafely()。二者的区别是,quit()会直接退出Looper,而quitSafely只是设定一个退出标记,然后把消息队列中已有的消息处理完毕后才安全的退出。Looper退出后,通过Handler发送的消息会失败,这个时候Handler的send()方法会返回false。
在子线程中,如果我们手动为其创建了Looper,那么在所有的事情完成以后应该调用quit()方法来终止消息循环,否则这个子线程会一直处于等待状态,而如果退出Looper后,这个线程就会立刻终止,所以建立在不需要Looper的时候终止它,减少不必要的线程开支。
再回头看看我们loop()的源码,里面还有一句msg.target.dispatchMessage(msg)
,msg.target 就是我们的Handler对象,我们在Looper中调用Handler.dispatchMessage()方法,那么Looper在哪个线程中创建,dispatchMessage()就会运行在哪个线程中,这样,一个消息取出后,就被切换到指定的线程中处理了。
在分析Handler工作前,我们先来看看Handler有哪些构造方法:
public Handler() {
// 无参构造方法,默认是同步Handler
this(null, false);
}
public Handler(@Nullable Callback callback) {
// 指定一个callback
this(callback, false);
}
public Handler(@NonNull Looper looper) {
// 指定一个looper,一般在子线程中调用
this(looper, null, false);
}
public Handler(@NonNull Looper looper, @Nullable Callback callback) {
// 指定looper, callback
this(looper, callback, false);
}
public Handler(boolean async) {
// async = true,创建一个处理异步消息的Handler
this(null, async);
}
public Handler(@Nullable Callback callback, boolean async) {
// 用当前线程的Looper来创建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());
}
}
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;
}
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
从上面这些构造方法,我们可以自己指定Looper对象来创建Handler,可以创建一个专门处理发送异步消息的Handler,同时我们也可以给Handler设置Callback回调来创建Handler,如果调用的是无参构造方法,那么系统就使用当前线程的Looper对象来创建一个Handler对象,一个线程可以创建多个Handler对象。
Handler的工作主要是消息的发送和接受。消息的发送可以通过post的一系列方法以及send的一系列方法来实现,大部分的发送方法,它们最终都会来到sendMessageAtTime()
,我们来看看它的源码:
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);
}
其内部调用了enqueueMessage(),我们跟进一下看看:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this; // 同步消息,target指向自己
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
在enqueueMessage()内部,我们给msg设置了target,就是当前的Handler对象,同时调用mQueue的enqueueMessage()方法,将消息插入到消息队列中。
Looper收到消息后最终会调用Handler的dispatchMessage()进行处理,这时Handler就进入了处理消息的阶段,我们来看看dispatchMessage的具体实现:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
首先,检查Message的callback是否为null,不为null就通过handleCallback来处理消息,Message的callback是一个Runnable对象,实际上就是Handler的post()方法所传递Runnable参数。
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
handleCallback(msg)
的内部实现也很简单,就是调用Runnable的run()方法,这里就不贴代码了。
接下来检查mCallback是否为空,这个mCallback就是我们创建Handler时可选的Callback对象,Callback是个接口,里面只有handleMessage()一个待实现的方法:
public interface Callback {
boolean handleMessage(@NonNull Message msg);
}
值得注意的是,如果mCallback.handleMessage(msg)返回的是false,那么还是会执行到Handler的handleMessage()方法。具体怎么利用方法的返回值,就要看具体的使用场景了,一般很少有这两个方法同时使用的场景。
实际上,通过Callback,我们可以采用如下方式来创建Handler对象:Handler handler = new Handler(callback)。那么这样做的意义是什么呢?在日常开发中,创建Handler最常见的方式就是派生一个Handler的子类,并重写其handlerMessage()方法来处理具体的消息,而Callback给我们提供了另一种使用Handler的方式,当我们不想派生子类时,就可以通过Callback来实现。
我们将Handler处理消息个过程可以总结为一个流程图,如下所示:
Android应用本质上是在手机上的一个事件驱动型进程,应用启动的入口是ActivityThread.main()方法。当我们用手点击启动一个App,实际上就是让系统顺序执行这个App的main()方法,在方法中,系统会为这个App创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环,Looper.loop()不断读取MessageQueue中的消息并处理,如果消息队列中没有消息了,主线程就会阻塞,直到新消息的到来。因此一个应用进程的启动后会一直停留在Looper.loop()这一行,直到消息队列收到进程退出的消息,由消息队列调用进程的退出,或者主进程被系统杀死,应用才算真正的退出。一个应用启动了便不能自己退出,必须借助“外力”。
说起来还是有点抽象,下面请看main()的完整代码,它并没有我们想象中的那么长:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// Install selective syscall interception
AndroidOs.install();
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("" );
// 准备主线程的Looper
Looper.prepareMainLooper();
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) {
// 创建主线程的Handler
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
// 最后一行代码,开启消息循环
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
看到这里,题外话就说完了,不过,看完main()方法的我,突然想到一个有意思的问题:Looper.loop()既然是一个死循环,消息队列中没有消息时便会阻塞,那为什么它没有在主线程中导致ANR错误呢!?
多么有意思的问题!~ 这个问题实际上在网上已经有答案了,我在这就给大家卖个关子,把问题当做本文的结尾了,大家自行去网上冲浪吧
拜托拜托,谢谢各位同学!