Handler 相信每一个做 Android 开发的小伙伴都非常熟悉了,最常用的场景就是在子线程中进行数据操作然后通过 Handler 消息机制通知到 UI 线程来更新 UI ,地球人都知道在子线程中更新 UI 一般情况下都会报错。是吧!咱用的贼溜,各种姿势发送 Handler 消息都熟练掌握,但是如果这时候出去面试被问到“ Handler 原理”,“消息是怎么从子线程发送到主线程的”等等 Handler 底层的实现,就懵逼了。
虽然网上关于分析 Handler
的博客文章非常多,已经有很多大佬分析的非常透彻清楚了,但我这里还是想在看过大佬们的文章后自己再写一篇,一方面是让自己加深理解,另一方面就是想分享所学知识(分享会让知识变的更多)。
看了很多大佬去大厂面试的面经,Handler几乎是必问的,所以我们更加必须知其所以然了。
一、Handler是啥
1.1 一句话描述 Handler
简单来说Handler是结合线程消息队列来发送和处理 Message
, Runnable
对象来实现线程间通信的工具。
1.2 典型实例
先来一起看一个关于 Handler 使用的典型实例(实例来源 Gityuan 博客)
1 | class LooperThread extends Thread { |
在线程中声明一个Handler必须按以下顺序三步走:
- 调用 Looper.prepare() 。
- new Handler() 。
- 调用 Looper.loop() 。
以上三步曲我称之为:Looper 肉夹馍
(手动狗头)
这里产生一个问题了:
Question 1: 创建 Handler
之前为啥一定要先调用 Looper.prepare()
呢?
我们带着这个问题继续往下看。
二、Handler 工作原理
说起 Handler 那就必须要了解 Android 的消息机制了, Android 中有大量的交互场景是通过消息机制来驱动的,如我们最熟悉不过的 Activity
的生命周期等等。而 Handler 就是 Android 消息机制的重要组成部分。
我们先来看一张图:
图中描述的是消息是怎样从子线程流到主线程从而完成线程间通信的大概流程。Android 整个消息机制主要由Handler
,Looper
,MessageQueue
,Message
四个部分组成。这里先简单介绍一下这四位具体职责:
Handler : 负责发送消息和接收消息并进行处理。
MessageQueue : 消息队列,负责消息存储与管理。
Looper : 负责关联
Handler
当前所处线程,进行消息分发,通过不断循环实现将消息从消息队列取出,然后分发给对应的消息处理者。Message : 消息载体。
2.1 原理解析
看完上图脑海中应该对 Android 的消息机制已经有了一个大概的概念了,下面我们开始从源码来看 Android 的消息机制具体实现。这里就以上图中的主线程和子线程通信为思路。
先来看上图对应的代码实例
1 | public class MainActivity extends AppCompatActivity { |
2.1.1 创建 Handler
主( UI )线程中实例化一个 Handle
对象。我们跟到源码中看一下 Handle()
做了什么事情。
Handle
默认构造方法1
2
3public Handler() {
this(null, false);
}默认构造方法最终会走到
Handler(Callback callback, boolean async)
1
2
3
4
5
6
7
8
9
10
11
12
13
14public Handler(Callback callback, boolean async) {
......
//获取当前Looper对象
mLooper = Looper.myLooper();
//检查当前线程Looper是否存在
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
......
}
源码解析:
在该构造方法中,每次实例化 Handler
都会检查当前线程是否已绑定 Looper
,如果没有则会抛出异常提示你没有调用 Looper.Prepare()
。看到这里【1.2】的 Question 1`是不是就有答案了。
Question 1 正解:
在创建
Handler
对象之前必须要先创建Looper
并且将Looper
对象绑定到当前线程,否则在实例化Handler
时会报异常。
2.1.2 创建 Looper
那么 Looper.Prepare()
方法里又是怎么创建 Looper
对象并绑定到当前线程的呢?跟进源码看一下。
1 | public static void prepare() { |
源码解析:
调用无参
prepare()
方法默认会调用prepare(true)
。prepare(boolean quitAllowed)
方法参数quitAllowed
表示是否当前Looper
是否可退出,传true
表示可退出,false
为不可退出。prepare(boolean quitAllowed)
方法中会先检查当前线程是否已经有Looper
,如果有则会抛出异常,也就意味着每个线程中Looper.Prepare()
方法只可以调用一次!这里我们就知道了,
Looper
和当前线程是一一对应的关系,一个Looper
只能关联一个线程。检查完当前线程是否已经有
Looper
后sThreadLocal.set(new Looper(quitAllowed))
就开始创建一个Looper
对象并保存到当前线程的TLS(关于 ThreadLocal 请自行查阅资料)区域,这样就完成了Looper
对象的创建和关联到当前线程。Looper.Prepare()
调用后Looper
创建好了,我们再回到Handler
构造方法中:Looper.myLooper()
方法1
2
3public static Looper myLooper() {
return sThreadLocal.get();
}
源码解析:
这个方法很简单就是去当前线程的中的 TLS 区域 Get Looper
对象,没错就是 Looper.Prepare()
方法中sThreadLocal.set(new Looper(quitAllowed))
Set 进去的。
2.1.3 创建 MessageQueue
最后 mQueue = mLooper.mQueue
会获取 Looper
对象的消息队列,如果你刚才从 prepare(boolean quitAllowed)
方法中继续跟进 new Looper(quitAllowed)
方法中你就会发现:
1 | private Looper(boolean quitAllowed) { |
创建 Looper
对象时会为当前 Looper
对象创建一个消息队列 new MessageQueue(quitAllowed)
。
2.1.4 强行Question 2
不知道细心的你发现了没,我们的图一的代码实例中是不是少了点什么。
没错,少了肉夹馍!
呐!Question 2来啦:
不是反复强调创建 Handler
之前要先调用 Looper.Prepare()
创建 Looper
吗?那图一的代码实例中主线程并没有调用啊,直接就创建了 Handler
,你这代码肯定会抛这个异常:1
2RuntimeException( "Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
悄咪咪告诉你,如果你在图一的代码实例中创建Handler
之前调用 Looper.Prepare()
又会抛出以下异常哦:1
RuntimeException("Only one Looper may be created per thread");
答案在 ActivityThread.main()
中。
ActivityThread
的main()
方法就是整个APP的入口,也就是我们通常所讲的主线程
,UI线程
。
但他实际上并不是一个线程,ActivityThread
并没有继承Thread
类,我们可以把他理解为主线程的管理者,负责管理主线程的执行,调度等操作,可以看下这个类源码的注释。
1 | /** |
好了,我们来看下 ActivityThread.main()
一探究竟(这里只贴关键代码了)。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
26public static void main(String[] args) {
.......
Looper.prepareMainLooper();
.......
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
.......
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
源码解析:
Looper.prepareMainLooper();
此方法就是为我们的主线程创建了Looper
,内部实现调用了prepare(false)
方法。传false
表示当前Looper
不可退出。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/**
* 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();
}
}
以上,就是Question 2的正解所在,Android 程序入口 ActivityThread.main()
中调用 Looper.prepareMainLooper();
为主线程创建了 Looper
,所以我们的主线程可以直接创建 Handler
。
Looper.loop();
和【1.2】中的实例一样,标准的Looper 肉夹馍
写法,这个方法干啥用的,后面再说。
2.1.5 小结
OK,到这里,Handler
,Looper
,MessageQueue
都已经准备就绪,图一的流程差不多可以跑起来了。我们总结一下重点:
- 初始化
Handler
会检测当前线程Looper
是否存在,没有则会抛出异常。 - 基于第一点,所以每次创建
Handler
之前必须先调用Looper.Prepare()
创建Looper
。 Looper
可以理解为当前线程的消息调度者,负责消息分发。和当前线程的关联绑定通过sThreadLocal.set(new Looper(quitAllowed));
实现。Looper
和当前线程是一对一的关系,Looper.Prepare()
方法中有进行校验,重复创建Looper
绑定会抛出异常。Looper
和MessagQueue
也是一对一的关系,构造Looper
对象时会为其创建一个对应的MessagQueue
对象。
2.2 消息发送 - Handler
Handler
消息发送其实是一个消息入队的过程,调用 Handler
相应的发送消息方法将当前消息添加到 MessageQueue
消息队列,最终由 Looper
负责进行消息分发。Handler
为我们提供了一系列的消息发送方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessage(int what){
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageAtTime(msg, uptimeMillis);
}
发送消息的方法还有很多就不一一列出来了,所有发送消息的方法最终都是调用到 enqueueMessage
方法中:
1 | private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { |
该方法实现最终调用的是queue.enqueueMessage(msg,uptimeMillis)
方法即 MessageQueue
添加消息到消息队列中。
2.3 消息管理与存储 - MessageQueue
前面提到 Handler
所有发送的消息最终都通过 enqueueMessage
加入到 MessageQueue
消息队列中,所以消息的存储与管理由 MessageQueue
来负责。
2.3.1 消息入队
消息入队由 enqueueMessage(msg,uptimeMillis)
方法负责: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
45
46
47
48
49
50
51
52
53boolean enqueueMessage(Message msg, long when) {
//验证Message 是否有target,这里的target就是对应的handler
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) {
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;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//将消息按时间顺序插入到MessageQueue。一般地,不需要唤醒事件队列,除非
//消息队头存在barrier,并且同时Message是队列中最早的异步消息。
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
消息入队顺序是按照 Message
触发时间 long when
入队的,有新消息加入时,会循环遍历当前消息队列,对比消息触发时间,直到找到当前消息合适的插入位置,以此来保证所有消息的触发时间顺序。
2.3.1 消息出队
MessageQueue
的 next
方法负责从消息队列中取出一条消息。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
45
46
47
48
49
50
51
52
53
54
55
56Message next() {
.......
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 尝试检索下一条消息。如果找到,则返回。
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
////当消息Handler为空时,查询MessageQueue中的下一条异步消息msg,则退出循环。
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; //成功地获取MessageQueue中的下一条即将要执行的消息
}
} else {
//没有消息
nextPollTimeoutMillis = -1;
}
//消息正在退出,返回null
if (mQuitting) {
dispose();
return null;
}
}
//IdleHandler相关内容
.......
}
}
2.4 消息分发 - Looper
有人( Handler
)在子线程把消息发到我(MessageQueue
)这里来了,我负责把消息存起来,并且告诉大家存储(enqueueMessage
)消息和提取(next
)消息的方法 ,那么谁来负责把我这里的消息分发出去并且告诉主线程的人(Handler
)呢?
干这个活的就是我们的 Looper
了!
还记得大明湖畔的的 Looper 肉夹馍
么!
现在我们就来看一下 Looper 肉夹馍
之 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/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
......
//开启死循环
for (;;) {
//循环遍历,不断从MessagQueue中获取Messag
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
......
try {
//分发消息到对应的msg.target
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
msg.recycleUnchecked();
}
}
源码解析:
调用 loop()
方法后,方法内部会开启一个死循环,通过调用 MessageQueue
消息出栈方法 next()
获取 Message
,然后调用 msg.target.dispatchMessage(msg);
将消息分发到 Message
对应的 target
。
msg.target
就是发送该消息的Handler
对象。
2.5 消息接收 - Handler
loop()
方法中调用 msg.target.dispatchMessage(msg);
将消息分发出去,我们来看一下分发出去的消息是怎么被接收的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//回调到 Handler 的 handleMessage 方法
handleMessage(msg);
}
}
源码解析:dispatchMessage(msg)
方法内部调用了 handleMessage(msg)
方法,这个方法用过 Handler
的应该都非常熟悉了,它是 Handler
的一个空实现方法,一般在创建 Handler
的线程重写此方法,就可以回调到子线程发出的消息了。1
2
3
4
5/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
三、 总结
Looper 肉夹馍
职责:- 为当前线程创建
Looper
对象,并关联当前线程。 - 为当前线程创建
Handler
对象。 - 开启循环,不断从
MessageQueue
消息队列中取消息,取到后分发给Message
对应的target
。
- 为当前线程创建
整个消息从子线程流转到主线程流程:
- 主线程使用
Looper肉夹馍
。 - 子线程调用
Handler
相应发送消息方法,最终通过enqueueMessage
将消息加入消息队列MessageQueue
。 Looper
循环从MessageQueue
获取到消息,调用当前消息对应的target
的dispatchMessage(msg)
方法。dispatchMessage(msg)
回调handleMessage(msg)
方法,消息即从子线程流转到重写handleMessage(msg)
方法所在线程。
- 主线程使用
Android 主线程默认会使用
Looper 肉夹馍
(详见ActivityThread.main()
),因此 Android 主线程中只需创建Handler
对象即可。主线程
Looper
不可以退出,因此ActivityThread.main()
方法中调用的Looper.prepareMainLooper();
方法中prepare(false);
传参是false
。
如果强行退出主线程Looper
会抛出以下异常:1
2
3Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
at android.os.MessageQueue.quit(MessageQueue.java:415)
at android.os.Looper.quit(Looper.java:240)
原因是:Android App 主进程依赖消息机制驱动,主线程消息循环退出了,那么 App 也会退出。
四、问题思考
Looper
中开启了一个死循环来从MessageQueue
中取消息,而且没有单独开线程,为什么不会造成 Android 主线程卡死 ANR?
问题答案可以看 Gityuan 大佬的回答