Android Handler消息机制
Android 消息机制作为系统运行的机制之一,在大一点的厂子面试被问到的概率很大,可见它的重要性。在消息机制中,有下面几个角色:
- a. Message: 消息实体
- b. MessageQueue: 消息队列,存放Message,以链表的方式实现
- c. Looper: 对MessageQueue进行循环,获取Message给Handler处理
- d. Handler: 对Message进行处理
下面从源码的角度分析它们之间是怎么协作的
Looper:
public final class Looper {
...
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
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));
}
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
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提供了两个方法来创建Looper对象,并将创建的对象保存在sThreadLocal。
从prepare方法可以看到,每个线程只允许创建一个Looper对象,否则会抛异常。
而我们在主线程创建Handler时,则不用自己创建Looper,那主线程的Looper是在哪里被创建的呢?我们看下prepareMainLooper()方法的注释
在 Looper 的 prepareMainLooper() 方法注释中可以看到这样一句话:
Initialize the current thread as a looper, marking it as an application's main looper.
The main looper for your application is created by the Android environment, so
you should never need to call this function yourself. See also: {@link #prepare()}
意思是说:将当前线程初始化为looper,将其标记为应用程序的主循环。您的应用程序的主要循环器是由Android环境创建的,永远不应该自己调用这个函数。
由此可知,是系统运行时就帮我们创建了Looper了,可以看下ActivityThread的main方法:
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();
}
印证了上述的注释。
接下来看下里面loop()方法:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
...
for (;;) {
// 从消息队列中取得消息,具体的在下面MessageQueue进行解析
Message msg = queue.next();
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
try {
// target是Handler,由Handler进行处理,具体看下面Handler的解析
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
...
}
}
Handler:
首先是send系列方法,拿一个出来看:
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
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);
}
可见最后是调用了sendMessageAtTime方法,其它的方法包括post系列的方法也一样,最终都是调用了sendMessageAtTime,不同的是最后传入的uptimeMillis。
然后再看下Looper调用Handler的dispatchMessage方法
/**
* Handle system messages here.
* 处理消息
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
MessageQueue:
接下来看下sendMessageAtTime里的enqueueMessage方法,此方法为将一个消息入队。进入MessageQueue类
// msg-上面说的消息实体, when-需要延时执行的时长
boolean enqueueMessage(Message msg, long when) {
// 参数检查省略
synchronized (this) {
// 检查Looper及相应的队列是否已经终止,是的话对msg进行回收并退出
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; // mMessages指向队列头部
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
// 假设队列为空 或 msg不需要延时 或
// msg需要延时的时长小于队头消息的延时时长,
// 则将msg插入对头
msg.next = p;
mMessages = msg;
// 标记是否需要唤醒,mBlocked表示现在消息队列是否处于阻塞状态
// 阻塞的原因在下面next()方法获取消息时再进行说明
needWake = mBlocked;
} else {
// 将msg插入在队列的中部,插入的位置
// 根据when得到,when小的msg会在前面
// 这样方便保证之后先出队msg都是需要
// 先执行的,从而保证在delay的时候过后执行msg
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;
}
// 当在next()获取msg时,假设获取到的when大于0,
// 说明此时需要延时when后才能
// 执行这个msg,因此进行了阻塞,mBlocked=true。
// 但是这个时候有新的消息入队并处于队头位置,
// 因此先于上一个队头消息执行,所醒
// 以此时需要唤醒队列,才能保证后来者需要先执行
// 的不会因为阻塞而中断
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
接下来看看上面提到的出队的方法next():
Message next() {
// 条件检查,省略
// 第一次循环的时候为-1,其它情况不会时-1
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0; // 需要进行阻塞的时间
for (;;) { // 死循环,不断获取msg
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 && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
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 {
// 如果没有消息,则设置阻塞时长为无限,直到被唤醒
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// 第一次循环 且 (消息队列为空 或
// 消息队列的第一个消息的触发时间还没有到)时,
// 表示处于空闲状态
// 获取到 IdleHandler 数量
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 没有 IdleHandler 需要运行,循环并等待
mBlocked = true; // 设置阻塞状态为 true
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 运行 IdleHandler,只有第一次循环时才会运行
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);
}
}
}
// 重置 IdleHandler 的数量为 0,确保不会重复运行
// pendingIdleHandlerCount 置为 0 后,上面可以通
// 过 pendingIdleHandlerCount < 0 判断是否是第一次循环,
// 不是第一次循环则 pendingIdleHandlerCount 的值不会变,始终为 0。
pendingIdleHandlerCount = 0;
// 在执行 IdleHandler 后,可能有新的消息插
// 入或消息队列中的消息到了触发时间,
// 所以将 nextPollTimeoutMillis 置为 0,表示不
// 需要阻塞,重新检查消息队列。
nextPollTimeoutMillis = 0;
}
}
经过上面两个方法,这里总结一下:
消息在入队(即有新消息加入)的时候,会根据delay的时间,在队列找到合适的位置入队,从而保证整个队列的顺序是以延迟时间从小到大排序。
- a. 当入队的消息的delay时间比原先队头消息短的时候,或者队列为空的时候,则消息会入队在队头,并且当此时列表处于阻塞状态时,则唤醒队列;
- b. 否则入队的位置为非队头。
这个特性方便后续获取消息即出队的时候,直接出队头消息,即是最优先需要执行的消息。出队时
- a. 若队列为空,则无限长时间进行阻塞;
- b. 出队的消息要是到达执行时间了,则出队;
- c. 出队的消息还没到执行时间,则进行对应时间的阻塞。