概述
Handler是Android中的一套消息传递机制。
作用
Handler允许你发送和处理与线程关联的Message和Runnable对象。每个Handler对象都与一个线程以及线程的消息队列相关联。当你创建一个新的Handler,它会被绑到创建它的线程或线程的消息队列上,之后它就可以进行消息传递。
Handler主要以下两个作用:
- 延时处理
- 将工作线程中需要更新UI的操作信息传递给UI线程,从而实现工作线程对UI的更新处理
意义
我们都知道Android不允许在主线程之外的其他线程操作UI。但在实际开发过程中,为了不影响UI响应速度,耗时操作一般放在工作线程中去操作。因此便存在需要在工作线程中更新UI操作的需求,Handler机制的存在就是为了处理这种情况。
工作流程
我们先简单说下Handler机制的工作流程:
- 异步通信准备:
- 创建
Looper
对象Looper.prepare()
,MessageQueue
由Looper
创建 - 启动
Looper
:Looper.loop()
- 创建
Handler
对象:new Handler(looper)
- 创建
- 消息发送:工作线程通过
Handler
发送消息到消息队列 - 消息循环:
Looper
循环取出消息队列中的消息(消息队列为空时,线程阻塞),并调用Handler.dispatchMessage(Message)
方法将消息发送给Handler
- 消息处理:
Handler
在主线程处理消息
接下来我们从源码的角度来分析Handler机制。
Looper
首先说下Looper
,顾名思义,它是一个循环器,为线程运行消息循环。
prepare
线程默认是不带Looper
的,需要的时候有开发者自行创建,因此就需要调用Looper.prepare()
方法创建。
// 将Looper对象作为线程的局部变量
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
final MessageQueue mQueue; // 消息队列
final Thread mThread; // 宿主线程
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
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));
}
可以看到,一个Looper
维护了一个MessageQueue
,创建的时候,它通过ThreadaLocal
将自己绑定到宿主线程。这里也可以看出,一个线程只允许有一个Looper
,因为sThreadLocal
对象是static
类型的,它以自己为key
添加到当前线程的threadLocals
映射表中,如果当前线程已有Looper
对象,即sThreadLocal.get()
返回不为空,会抛出异常。
loop
准备好Looper之后,需要启动它,通过调用Looper.loop()
方法来实现。
public static void loop() {
// 通过sThreadLocal.get()获取当前Looper对象
final Looper me = myLooper();
// Looper对象为空,抛出异常
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();
if (msg == null) {
// 当消息队列退出的时候,返回的是msg为空,退出循环
return;
}
...
try {
// msg.target即发送消息的Handler对象
// 这里将消息分发给Handler处理
msg.target.dispatchMessage(msg);
} finally {
...
}
...
// 释放消息
msg.recycleUnchecked();
}
}
可以看到,Looper
采用一个死循环来不断地从消息队列中获取消息,并分发给Handler
。当消息队列退出时,获取到的消息为null
,此时就会退出循环。
我们都知道,当消息队列为空时,Looper
会挂起等待,而在Looper
的代码中并没有看到挂起相关的操作,这部分逻辑是在哪里操作的呢?我们接着往下看。
MessageQueue
我们先来看下Message
这个类
public class Message {
// 用户定义的消息码,用来标识一条消息
public int what;
// 消息内容
public int arg1;
public int arg2;
public Object obj;
...
// 消息处理的时间
long when;
...
// 处理消息的Handler
Handler target;
// 下一条消息
Message next;
...
}
看完Message
的代码,我们知道Message
是链式结构存储的。
接下来看下一条消息是怎么添加到消息队列中的。
com.os.Handler.java
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) {
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 将当前Handler对象赋值给Message的target参数
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到Handler
最终调用到的是MessageQueue
的enqueueMessage
方法添加消息,如果不是延迟消息,则消息的发送时间为当前消息。
android.os.MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
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) {
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 当前没有其他消息或者队首的消息触发时间比新消息晚
// 将新消息作为队首加入到队列中
// 如果队列阻塞了,就需要唤醒它
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
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()) {
neeWake = false;
}
}
msg.next = p;
prev.next = msg;
}
// 如果当前处于阻塞状态,调用`nativeWake()`方法唤醒
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
通过之前Message
的实现中可以看出,消息队列是通过链表结构来维护的。插入的时候,根据消息的触发时间排序,触发时间越早的消息就排在越前面。
接下来看下怎么获取消息。
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
...
mPtr
由nativeInit()
方法初始化,当MessageQueue
退出并调用dispose()
方法时,会将mPtr
置为0。
private long mPtr;
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
private void dispose() {
if (mPtr != 0) {
nativeDestroy(mPtr);
mPtr = 0;
}
}
当mPtr
为0时,即表示MessageQueue
已退出,直接返回null
。这里也与Looper.loop()
方法中调用mQueue.next()
获取方法时若获取到null
值直接退出循环相呼应。
MessageQueue
还在运行中,则开启一个for
循环,提取消息。
Message next() {
...
// IdleHandler的个数
int pendingIdleHandlerCount = -1;
// 阻塞时间,初始为0
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPoolTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 调用native方法阻塞nextPollTimeoutMillis毫秒
nativePoolOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
// 取出第一个消息
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 如果msg.target为null,取出下一个异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 消息还没准备好,将延迟时间赋值给nextPollTimeoutMillis
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;
msg.markInUse();
// 返回消息
return msg;
}
} else {
// 消息队列为空,无限期等待,直到新消息进入
nextPollTimeoutMillis = -1;
}
// 再次判断是否退出,若是,返回null
if (mQuitting) {
dispose();
return null;
}
...
}
}
}
- 首先调用
nativePollOnce(ptr, nextPollTimeoutMillis)
方法,这是一个native方法,通过native层的MessageQueue
阻塞nextPollTimeoutMillis
毫秒的时间,该方法有以下特点:-
nextPollTimeoutMillis
= 0:立即返回,不阻塞 -
nextPollTimeoutMillis
< 0:无限期等待直到被唤醒 -
nextPollTimeoutMillis
> 0:最长等待时间为nextPollTimeoutMillis
毫秒,如果期间被唤醒,将立即返回。
-
- 取出第一个消息,如果
msg.target
为null
,则取出下一个异步消息(后面分析)。 - 消息不为
null
时,分以下两种情况处理:- 延迟消息,即
msg.when
比当前时间大,说明需要阻塞,设置nextPollTimeoutMillis
的值。 - 即时消息,将消息从消息队列中移出,并返回。
- 延迟消息,即
挂起时,需要考虑当前有没有IdleHandler
:
- 有
IdelHandler
,执行IdelHandler
,执行完后,将nextPollTimeoutMillis
设为0并继续循环,防止在此期间有其他消息进入 - 没有
IdelHandler
,进入阻塞状态
Message next() {
...
for (;;) {
...
synchronized (this) {
...
if (pendingIdelHandlerCount < 0)
&& (mMessage == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 没有IdleHandler,继续循环,此时nextPoolTimeoutMills > 0, 将阻塞
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;
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的数量,防止重复执行
pendingIdleHandlerCount = 0;
// 执行IdleHandler时,可能已经有新的消息添加到队列中
// 因此应该立即返回并查看待处理消息
nextPollTimeoutMillis = 0;
}
}
以上就是MessageQueue
取出消息的流程,总结一下:
- 消息队列是个单向列表,添加消息时,根据响应时间排序,时间越靠后,消息就越靠后;
- 每次调用
next()
方法获取消息时,会取出队列中的第一个消息。- 即时消息:从队列中移除该消息,并返回;
- 延迟消息:调用native方法等待相应的延迟时间;
- 队列为空:进入无限期等待,直到有新消息入列,调用
nativeWake()
方法唤醒它;
- 如果有
IdleHandler
存在,则每次等待时会执行这些IdleHandler
。
IdleHandler
是MessageQueue
定义的一个接口,需要开发者自己实现并调用addIdleHandler()
方法添加到列表中。
上面我们有提到当获取的第一个消息的msg.target
为null
,会取出下一个异步消息,那什么情况下msg.target
会为空呢?看下postSyncBarrier()
方法即可知道。
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) {
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
这个方法直接在MessageQueue
中插入了一个消息,并且没有设置target
。它的作用是插入一个消息屏障,在这个屏障之后的所有同步消息都不会被执行,哪怕延迟时间到了也不会。插入屏障之后,可以通过removeSyncBarrier(int token)
来移除屏障,参数是postSyncBarrier()
的返回值。
异步消息则不受消息屏障影响,可以继续执行,通过Message
的setAsynchronous(true)
可以将消息设置成异步消息。