Android消息机制之Handler
简介
作为一个Android开发者,Handler的大名你一定听过。做Android开发肯定离不开Handler,它通常被我们用来连通主线程和子线程。
可以说只要有异步的地方一定有Handler。
那么,你了解过为什么Handler能连通主线程和子线程吗,也就是说,你了解过Handler背后的原理吗?
就让本文带你了解。
Handler的基本用法
按照惯例,我们首先看下Handler的一般用法:1
2
3
4
5
6
7
8
9
10Handler mHandler = new Handler() {
public void handleMessage(final Message msg) {
// 在这里接受并处理消息
}
};
//发送消息
mHandler.sendMessage(message);
mHandler.post(runnable);
也就是创建一个Handler对象,并重写handlerMessage方法,然后在需要的时候调用sendMessage方法传入一个message对象,或者调用post方法传入一个runnable对象。
那么我们从他的构造方法开始看起吧。
从Handler构造方法入手
1 | public Handler() { |
分析1.1 Handler # 构造方法
1 | /** |
分析1.2 Looper # myLooper()
1 | /** |
ThreadLocal是一个数据存储类,他的特别之处在于他是线程间独立的,也就是说这个线程放入到ThreadLocal的数据,其它线程是获取不到的。sThreadLocal就是获取当前线程的Looper对象。详细可见我之前的博客。
分析1.3 Looper # prepare()
为什么说如果mLooper为空就抛异常呢,这是因为一个Handler必须和一个Looper绑定,并且要先初始化Looper才能去初始化Handler。初始化Looper就通过Looper的prepare方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14/**
* 分析1.3:Looper.prepare()
*/
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");
}
// ->> 分析1.4
sThreadLocal.set(new Looper(quitAllowed));
}
这个方法和myLooper方法类似,就是通过ThreadLocal设置Looper。
分析1.4 Looper # 构造方法
1 | /** |
Looper的构造方法是私有的,也就是说外界并不能直接创建Looper方法,必须通过Looper.prepare方法来创建Looper对象,这样也就方便了保证一个线程只能有一个Looper对象。
在构造方法中,我们创建了一个MessageQueue对象。这个对象就是用来存放消息的消息队列。关于MessageQueue我们后面再来讲。
消息是如何存放的
上面就是Handler和Looper的一个构造方式,构造完成后,我们就要关注Handler是如何把消息放入MessageQueue中的,以及是如何从MessageQueue中取出的。
上面我们在讲Handler的基本用法的时候,讲到过sendMessage方法和post方法。我们先来看下这两个方法:
Handler # sendMessage() & post()
其实这块先不上具体的方法实现,先说一下他们的方法调用流程:1
2
3
4
5
6
7
8
9sendMessage()
-> sendMessageDelayed()
-> sendMessageAtTime()
-> enqueueMessage()
post()
-> sendMessageDelayed()
-> sendMessageAtTime()
-> enqueueMessage()
可以看到,这两个方法都会调用立马调用sendMessageDelayed():1
2
3
4
5
6
7
8public final boolean sendMessageDelayed(long delayMillis) { Message msg,
// 默认都传入0
if (delayMillis < 0) {
delayMillis = 0;
}
// ->> 分析2.1
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
分析2.1 Handler # sendMessageAtTime()
1 | /** |
分析2.2 Handler # enqueueMessage()
1 | /** |
那么消息就成功的从Handler中被存入了MessageQueue,那么消息什么时候从MessageQueue中被调出来呢?
消息是如何取出的
Looper # loop()
Looper里面有一个loop方法,我们可以看下这个方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45public static void loop() {
// 获取当前线程的Looper对象
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 获取当前线程的MessageQueue
final MessageQueue queue = me.mQueue;
// ...省略中间代码
// 死循环
for (;;) {
// 从消息队列中取出一个消息
// ->> 分析3.1
Message msg = queue.next(); // might block
// 如果取出的消息为空,就退出。
// 但是我之前说这个循环是个死循环?
// 这是因为虽然会有这个if可能会导致循环退出,但是通过next方法取出消息的时候,如果没有消息了,next方法会阻塞线程,直到有新的消息进来
// 这也就意味着,一般正常的遍历MessageQueue情况下,是不会有msg==null的。
// 但是如果你调用了Looper的quit或者quitSafely方法,这个时候next会返回null,就会退出循环。
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// ...省略中间代码
try {
// 传给Handler去处理消息
// ->> 分析3.2
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
// ...省略中间代码
} finally {
// ...省略中间代码
}
// ...省略中间代码
}
}
分析3.1 MessageQueue # next()
1 | /** |
分析3.2.1
nextPollTimeoutMillis是一个变量,用于表示时间。
- 如果nextPollTimeoutMillis = -1,则一直会阻塞不会超时
- 如果nextPollTimeoutMillis = 0,不会阻塞,立即返回
- 如果nextPollTimeoutMillis > 0,最长阻塞nextPollTimeoutMillis毫秒,如果期间有程序唤醒会立即返回
分析3.2 Handler # dispatchMessage()
1 | /** |
从上面可以得到以下信息:
处理消息的方式有3个,分别为Message自己的Callback、Handler的Callback以及Handler自己。
- 优先级最高的是Message自己的callback,这是一个Runnable对象,我们用Handler post一个Runnable的时候,其实就是把这个Runnable赋值个一个Message对象的callback,然后把Message对象发送给MessageQueue。
- 优先级第二的是Handler自己的mCallback,在我们创建一个Handler对象的使用可以传一个Handler.Callback对象给它并实现Callback里的handleMessage(msg)方法,作为我们的消息处理方法。
- 优先级最低的是Handler自己的handleMessage(msg)方法,这也是我们最常用的消息处理方法。
流程图
(图片来自于此处)
延伸
1. 为什么说Handler可能会导致内存泄漏
只要你用Android Studio,并在Activity里面用过Handler,都会注意到一个地方,就是如果你直接创建Handler对象并重写handleMessage方法的话,AS一把都会报一个warning:1
2
3This Handler class should be static or leaks might occur.
该处理程序类应为静态,否则可能发生泄漏
就是说如果直接这样写就可能会导致内存泄漏,但是如果你在不是Activity的类里面这样写又不会报warning,这是为什么呢?
因为Handler允许我们发送延时消息,但是如果在延时期间,用户关闭了Activity。这时Message持有Handler,而又因为Java的特性,内部类会持有外部类,也就是说Handler会持有Activity,这样就导致Activity泄漏了。
解决办法就是把Handler定义为静态内部类,并在内部持有Activity的弱引用,并及时移除所有消息:1
2
3
4
5
6
7
8
9
10
11
12private static class SafeHandler extends Handler {
private WeakReference<MainActivity> ref;
public SafeHandler(MainActivity activity) {
this.ref = new WeakReference<>(activity);
}
public void handleMessage( { Message msg)
}
}
2. 为什么主线程不需要创建Looper,而且主线程的Looper不许退出
因为在主线程创建时会自动调用Looper的prepare方法,并调用loop方法。我们就可以直接在主线程使用Handler。
而为什么不能退出,是因为如果Looper退出了,那么主线程就会挂掉。Android也不许你手动退出主线程的Looper。