前言
Android提供了Handler来满足线程间的通信,开发中不管直接还是间接基本离不开Handler的使用,通常Handler被我们用来做子线程更新UI线程的工具,可以说只要有子线程与主线程通信的地方就会有Handler。
工欲善其事必先利其器,熟悉Handler机制可以帮助我们更好的处理工作中遇到的问题。
1. 使用示例
private Handler mHandler;
public void test() {
new Thread(){
@Override
public void run() {
Looper.prepare();
initHandler();
Looper.loop();
}
}.start();
}
private void initHandler() {
mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
// 处理消息
}
};
}
// 通过Handler发送消息
mHandler.sendMessage(message);
mHandler.post(runnable);
...
2. Java源码分析
主要涉及到以下几个类:
- Message (消息实体)
- Handler (处理Message)
- MessageQueue (维护Message的队列,插入和取出Message)
- looper (循环从MessageQueue中取Message)
Handler机制的核心是发送与处理Message,那么我们先过一遍Message类。
2.1 Message实体类
public final class Message implements Parcelable {
...
// 处理Message的时间点
public long when;
// 持有处理Message的Handler对象
/*package*/ Handler target;
// 用来记录Message链表的下一个节点
/*package*/ Message next;
// Message复用池(也是链表格式)
private static Message sPool;
// Message复用池中当前Message个数
private static int sPoolSize = 0;
// Message复用池最大Message个数
private static final int MAX_POOL_SIZE = 50;
...
// 从Message复用池中获取Message,如果没有缓存才创建新的Message
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // 清除in-use标志
sPoolSize--;
return m;
}
}
return new Message();
}
void recycleUnchecked() {
// 将消息标记为in-use,清空其他信息,插入Message复用池
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
// 是否异步消息
public boolean isAsynchronous() {
return (flags & FLAG_ASYNCHRONOUS) != 0;
}
// 设置为同步或者异步消息
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
...
}
Message是一个封装消息的实体类,主要对简单的消息做一层包装,方便后续处理,里面几个比较重要的字段和方法如上,其中when是待处理消息的时间点,target是Handler对象的引用,sPool是消息复用池,因为Android交互是基于消息机制,如果不进行复用,频繁的处理消息就会不断的创建销毁Message对象,导致内存抖动,所以创建Message时,Google推荐使用Message.obtain()来获取Message对象。recycleUnchecked()是将当前Message清空并插入复用队列。
2.2 发送Message流程
主要是通过Handler将Message按照时间When字段插入MessageQueue中。
2.2.1 Handler类
我们先来看sendMessage(message)和post(runnable)。
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 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);
}
可以看到,不管是sendMessage还是post都调用了sendMessageDelayed方法,而sendMessageDelayed里面调用了sendMessageAtTime并传入SystemClock.uptimeMillis() + delayMillis(当前时间+延迟时间)参数,也就是消息真正待处理的时间点,sendMessageAtTime又调用了enqueueMessage方法,通过方法名我们基本上就可以判断该方法是将消息插入队列,我们接着往下看。
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);
}
在Handler的enqueueMessage方法中,调用了MessageQueue的enqueueMessage方法;注意这里有一个重点,Message的target指向了当前Handler对象,也就是说每个Message消息都会持有Handler对象的引用,这也就是为什么非静态内部类Handler可能会出现内存泄漏的原因。
2.2.2 MessageQueue类
将Message插入MessageQueue。
boolean enqueueMessage(Message msg, long when) {
...
// 同步锁,多线程同步的核心
synchronized (this) {
...
// when = 当前时间 + 延迟时间
msg.when = when;
// 获取MessageQueue中保存的Message队列头
Message p = mMessages;
boolean needWake;
// 如果Message队列为空或者新Message的when小于队列头
// 将新Message插入到队列头部
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;
// 根据当前Message的when循环查找合适的位置插入,保证队列中
// 的Message根据时间顺序排列。
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
MessageQueue中持有一个链表形式的Message队列,enqueueMessage方法主要就是将待发送的Message根据时间顺序插入到消息队列中,并判断是否需要唤醒处理消息队列的线程,如果需要,通过调用native方法nativeWake()将该线程唤醒。
2.3 取出Message流程
主要是通过Looper循环从MessageQueue中拿出时间When字段与当前时间匹配的Message。
2.3.1 Looper类
Looper.prepare()主要是初始化Looper,并通过ThreadLocal能力保证此线程只有一个Looper,这里我就不细讲了。
我们看另外比较重要的Looper.loop()方法。
public static void loop() {
...
for (;;) {
Message msg = queue.next(); // 可能阻塞
if (msg == null) {
// 没有消息表明消息队列调用了退出
return;
}
...
msg.target.dispatchMessage(msg);
...
msg.recycleUnchecked();
}
}
loop方法的主要作用是循环从MessageQueue中获取Message,并通过Message.Target(也就是Handler)调用dispatchMessage。
我们看看MessageQueue的next方法。
2.3.2 MessageQueue类
Message next() {
...
int nextPollTimeoutMillis = 0;
for (;;) {
...
nativePollOnce(ptr, nextPollTimeoutMillis);
// 同步锁,保证多线程之间Message数据同步
synchronized (this) {
// 尝试检索下一条Message,找到就返回
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 同步屏障,查找下一条异步消息
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 {
// 当前Message执行时间已到,返回此Message
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 没有更多Message
nextPollTimeoutMillis = -1;
}
...
}
...
nextPollTimeoutMillis = 0;
}
}
外部循环去检索Message,内部do while循环增加同步屏障检索异步Message,如果有异步Message则先处理异步,拿到Message之后判断时间when是否晚于当前时间,晚于当前时间的话计算时间差并保存到nextPollTimeMills,待下次循环开始然后调用native方法nativePollOnce()来进行阻塞,nativePollOnce()方法在native层使用到了Linux的epoll机制,并且native层也有一套Handler机制,这里先不做细致讲解,免得增加复杂度,总之内部在没有到处理消息的时间时会阻塞线程,释放CPU,直到拿到当前时间到达目标时间When的Message。
2.3.3 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);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
可以看到,dispatchMessage中判断msg.callback不为空则调用callback的run方法,也就是我们通过Handler.post(runnable)等传入的Runnable的run方法,否则调用handleMessage。
3. 整体流程
4. 小结
- Handler类主要处理收发消息,通过调用MessageQueue的enqueueMessage()方法将Handler发送的Message插入MessageQueue,并且内部有同步锁,保证多线程同步。
- Looper主要通过loop()方法循从MessageQueue中取出Message,从MessageQueue中取出消息内部也有同步锁,保证多线程同步。
- Handler消息机制,在Java层的MessageQueue中,调用了native方法实现线程的休眠和唤醒。native方法nativePollOnce()实现了消息循环的休眠,而nativeWake()方法则实现了消息循环的唤醒。