Handler 在整个 Android 开发体系中占据着很重要的地位,对开发者来说起到的作用很明确,就是为了实现线程切换或者是执行延时任务,稍微更高级一点的用法可能是为了保证多个任务在执行时的有序性。
由于 Android 系统中的主线程有特殊地位,所以像 EventBus 和 Retrofit 这类并非 Android 独有的三方库,都是通过 Handler 来实现对 Android 系统的特殊平台支持。大部分开发者都已经对如何使用 Handler 很熟悉了,这里就再来了解下其内部具体是如何实现的。
一、动手实现 Handler
本文不会一上来就直接介绍源码,而是会先根据我们想要实现的效果来反推源码,一步步来自己动手实现一个简单的 Handler
1、Message
首先,我们需要有个载体来表示要执行的任务,就叫它 Message 吧,Message 应该有什么参数呢?
- 需要有一个唯一标识,因为要执行的任务可能有多个,我们要分得清哪个是哪个,用个 Int 类型变量就足够表示了
- 需要能够承载数据,需要发送的数据类型会有很多种可能,那就直接用一个 Object 类型变量来表示吧,由开发者自己在使用时再来强转类型
- 需要有一个 long 类型变量来表示任务的执行时间戳
所以,Message 类就应该至少包含以下几个字段:
/**
* @Author: leavesC
* @Date: 2020/12/1 13:31
* @Desc:
* GitHub:https://github.com/leavesC
*/
public final class Message {
//唯一标识
public int what;
//数据
public Object obj;
//时间戳
public long when;
}
2、MessageQueue
因为 Message 并不是发送了就能够马上被消费掉,所以就肯定要有个可以用来存放的地方,就叫它 MessageQueue 吧,即消息队列。Message 可能需要延迟处理,那么 MessageQueue 在保存 Message 的时候就应该按照时间戳的大小来顺序存放,时间戳小的 Message 放在队列的头部,在消费 Message 的时候就直接从队列头取值即可
那么用什么数据结构来存放 Message 比较好呢?
- 用数组?不太合适,数组虽然在遍历的时候会比较快,但需要预先就申请固定的内存空间,导致在插入数据和移除数据时可能需要移动大量数据。而 MessageQueue 可能随时会收到数量不定、时间戳大小不定的 Message,消费完 Message 后还需要将该其移出队列,所以使用数组并不合适
- 用链表?好像可以,链表在插入数据和移除数据时只需要改变指针的引用即可,不需要移动数据,内存空间也只需要按需申请即可。虽然链表在随机访问的时候性能不高,但是对于 MessageQueue 而言无所谓,因为在消费 Message 的时候也只需要取队列头的值,并不需要随机访问
好了,既然决定用链表结构,那么 Message 就需要增加一个字段用于指向下一条消息才行
/**
* @Author: leavesC
* @Date: 2020/12/1 13:31
* @Desc:
* GitHub:https://github.com/leavesC
*/
public final class Message {
//唯一标识
public int what;
//数据
public Object obj;
//时间戳
public long when;
//下一个节点
public Message next;
}
MessageQueue 需要提供一个 enqueueMessage
方法用来向链表插入 Message,由于存在多个线程同时向队列发送消息的可能,所以方法内部还需要做下线程同步才行
/**
* @Author: leavesC
* @Date: 2020/12/1 13:31
* @Desc:
* GitHub:https://github.com/leavesC
*/
public class MessageQueue {
//链表中的第一条消息
private Message mMessages;
void enqueueMessage(Message msg, long when) {
synchronized (this) {
Message p = mMessages;
//如果链表是空的,或者处于队头的消息的时间戳比 msg 要大,则将 msg 作为链表头部
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
} else {
Message prev;
//从链表头向链表尾遍历,寻找链表中第一条时间戳比 msg 大的消息,将 msg 插到该消息的前面
for (; ; ) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
}
msg.next = p;
prev.next = msg;
}
}
}
}
此外,MessageQueue 要有一个可以获取队头消息的方法才行,就叫做next()
吧。外部有可能会随时向 MessageQueue 发送 Message,next()
方法内部就直接来开启一个无限循环来反复取值吧。如果当前队头的消息可以直接处理的话(即消息的时间戳小于或等于当前时间),那么就直接返回队头消息。而如果队头消息的时间戳比当前时间还要大(即队头消息是一个延时消息),那么就计算当前时间和队头消息的时间戳的差值,计算 next()
方法需要阻塞等待的时间,调用 nativePollOnce()
方法来等待一段时间后再继续循环遍历
//用来标记 next() 方法是否正处于阻塞等待的状态
private boolean mBlocked = false;
Message next() {
int nextPollTimeoutMillis = 0;
for (; ; ) {
nativePollOnce(nextPollTimeoutMillis);
synchronized (this) {
//当前时间
final long now = SystemClock.uptimeMillis();
Message msg = mMessages;
if (msg != null) {
if (now < msg.when) {
//如果当前时间还未到达消息的的处理时间,那么就计算还需要等待的时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//可以处理队头的消息了,第二条消息成为队头
mMessages = msg.next;
msg.next = null;
mBlocked = false;
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
mBlocked = true;
}
}
}
//将 next 方法的调用线程休眠指定时间
private void nativePollOnce(long nextPollTimeoutMillis) {
}
此时就需要考虑到一种情形:当 next()
还处于阻塞状态的时候,外部向消息队列插入了一个可以立即处理或者是阻塞等待时间比较短的 Message。此时就需要唤醒休眠的线程,因此 enqueueMessage
还需要再改动下,增加判断是否需要唤醒next()
方法的逻辑
void enqueueMessage(Message msg, long when) {
synchronized (this) {
//用于标记是否需要唤醒 next 方法
boolean needWake = false;
Message p = mMessages;
//如果链表是空的,或者处于队头的消息的时间戳比 msg 要大,则将 msg 作为链表头部
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
//需要唤醒
needWake = mBlocked;
} else {
Message prev;
//从链表头向链表尾遍历,寻找链表中第一条时间戳比 msg 大的消息,将 msg 插到该消息的前面
for (; ; ) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
}
msg.next = p;
prev.next = msg;
}
if (needWake) {
//唤醒 next() 方法
nativeWake();
}
}
}
//唤醒 next() 方法
private void nativeWake() {
}
3、Handler
既然存放消息的地方已经确定就是 MessageQueue 了,那么自然就还需要有一个类可以用来向 MessageQueue 发送消息了,就叫它 Handler 吧。Handler 可以实现哪些功能呢?
- 希望除了可以发送 Message 类型的消息外还可以发送 Runnable 类型的消息。这个简单,Handler 内部将 Runnable 包装为 Message 即可
- 希望可以发送延时消息,以此来执行延时任务。这个也简单,用 Message 内部的 when 字段来标识希望任务执行时的时间戳即可
- 希望可以实现线程切换,即从子线程发送的 Message 可以在主线程被执行,反过来也一样。这个也不难,子线程可以向一个特定的 mainMessageQueue 发送消息,然后让主线程负责循环从该队列中取消息并执行即可,这样不就实现了线程切换了吗?
所以说,Message 的定义和发送是由 Handler 来完成的,但 Message 的分发则可以交由其他线程来完成
根据以上需求:Runnable 要能够包装为 Message 类型,Message 的处理逻辑要交由 Handler 来定义,所以 Message 就还需要增加两个字段才行
/**
* @Author: leavesC
* @Date: 2020/12/1 13:31
* @Desc:
* GitHub:https://github.com/leavesC
*/
public final class Message {
//唯一标识
public int what;
//数据
public Object obj;
//时间戳
public long when;
//下一个节点
public Message next;
//用于将 Runnable 包装为 Message
public Runnable callback;
//指向 Message 的发送者,同时也是 Message 的最终处理者
public Handler target;
}
Handler 至少需要包含几个方法:用于发送 Message 和 Runnable 的方法、用来处理消息的 handleMessage
方法、用于分发消息的 dispatchMessage
方法
/**
* @Author: leavesC
* @Date: 2020/12/1 13:31
* @Desc:
* GitHub:https://github.com/leavesC
*/
public class Handler {
private MessageQueue mQueue;
public Handler(MessageQueue mQueue) {
this.mQueue = mQueue;
}
public final void post(Runnable r) {
sendMessageDelayed(getPostMessage(r), 0);
}
public final void postDelayed(Runnable r, long delayMillis) {
sendMessageDelayed(getPostMessage(r), delayMillis);
}
public final void sendMessage(Message r) {
sendMessageDelayed(r, 0);
}
public final void sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public void sendMessageAtTime(Message msg, long uptimeMillis) {
msg.target = this;
mQueue.enqueueMessage(msg, uptimeMillis);
}
private static Message getPostMessage(Runnable r) {
Message m = new Message();
m.callback = r;
return m;
}
//由外部来重写该方法,以此来消费 Message
public void handleMessage(Message msg) {
}
//用于分发消息
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
msg.callback.run();
} else {
handleMessage(msg);
}
}
}
之后,子线程就可以像这样来使用 Handler 了:将子线程持有的 Handler 对象和主线程关联的 mainMessageQueue 绑定在一起,主线程负责循环从 mainMessageQueue 取出 Message 后再来调用 Handler 的 dispatchMessage
方法,以此实现线程切换的目的
Handler handler = new Handler(mainThreadMessageQueue) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1: {
String ob = (String) msg.obj;
break;
}
case 2: {
List<String> ob = (List<String>) msg.obj;
break;
}
}
}
};
Message messageA = new Message();
messageA.what = 1;
messageA.obj = "https://github.com/leavesC";
Message messageB = new Message();
messageB.what = 2;
messageB.obj = new ArrayList<String>();
handler.sendMessage(messageA);
handler.sendMessage(messageB);