前言
Android消息机制分析
Handler源码分析
正文
一. 概述
Handler
算是我们平时开发中比较常用的一个, Handler
所代表的是Android
中重要的一部分, 即消息机制; 本文将分析Handler
的运行机制
1.1 使用场景
-
线程切换: 这个是使用
Handler
最常见和最频繁的场景了; 我们都知道,UI
线程中不允许执行耗时操作, 那么当我们需要先去获取数据(耗时)再进行UI
展示或者交互的时候, 线程切换就必不可少了 -
任务延时: 即在未来某个时刻再执行任务, 这个主要是通过
Handler.postDelayed()
和Handler.sendMessageDelayed()
完成的(实际上postDelayed()
也是通过sendMessageDelayed()
完成的)
1.2 为什么会有Handler
我们都知道Handler
的作用主要是用于切换线程, 那么为什么会出现Handler
喃; 我们以UI
线程中执行耗时操作为例, 对于耗时操作, 特别是在需要实时交互的应用中, 肯定是需要设计为多线程的, 那么Android
设计之初为什么不使用多线程去访问UI
呢, 最致命的一点就是, 多线程存在并发访问的不确定性, 这就会造成UI
表现的不可预期性, 造成用户体验极差; 当然, 对于多线程并发的问题还是有很多方式去避免和解决的, 最常见的一个就是加锁, 那么为什么不采用多线程访问,
对UI
加锁的方式实现呢, 一个是加锁会使UI
逻辑变得复杂, 另一个是加锁会降低效率, 阻塞某些线程的执行
所以, 基于很多方面的考虑, 特别是对于Android
应用这样交互性很强的场景而言, 使得需要去实现自己的一套异步通信机制, 需要达到的目标是, 线程之间的独立性保持和数据交互的便利性, 这也是Handler
实现的
1.3 概述
在Android
消息机制中, 有非常重要的几个部分, 也是我们分析的切入点:
-
MessageQueue: 单链表, 实现消息存储; 是
Java
层和Native
层的连接纽带 -
Looper: 无限循环查询是否有新消息
-
Handler: 消息接受和分发处理的中转站
以下是我们使用Handler
最常见的一种写法, 我们通常会重写handleMessage()
来实现自己的逻辑; 当在其他线程中通过Handler.sendMessage()
来发送消息时, 我们就会在handleMessage()
中接收到该消息
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// do somthing with msg
super.handleMessage(msg);
}
};
new Thread() {
@Override
public void run() {
mHandler.sendEmptyMessage(0);
}
}.start();
Handler
进行线程切换和消息传递的一个大致流程是, 当我们通过sendMessage()
发送消息的时候, MessageQueue
负责存储该消息, Looper
会无限循环的去检查MessageQueue
中是否有消息, 如果有则调用Handler
的dispatchMessage()
进行消息分发, 最终会回到handleMessage
中进行处理
在开始讲解之前, 需要先明确几个结论(在稍后会验证): 每个线程都可以有自己的Looper
, 但是默认没有创建(除了UI
线程外); Looper
是创建Handler
所必须的(否则会抛异常); MessageQueue
和Looper
是一一对应的关系, 也就是说每个线程会有自己的MessageQueue
和Looper
; Handler
在哪个线程中创建就在哪个线程中处理消息, 其他线程发送的消息存储在对应Handler
所对应的MessageQueue
中
二. 构造函数
Handler
提供了七个构造函数, 如下表
Handler() | Handler(Callback callback) | Handler(Looper looper) |
---|---|---|
Handler(Looper looper, Callback callback) | Handler(boolean async) | Handler(Callback callback, boolean async) |
Handler(Looper looper, Callback callback, boolean async) |
这七个构造函数最常用的是默认构造函数(即不带参数那个), 其他几个, 一类是带Callback
的, 是提供的消息处理函数, 我们使用Handler
一般都会重写其handleMessage()
方法来处理消息, 但是也可以不重写handleMessage()
, 而是在构造Handler
的时候传入一个Callback
回调, Callback
是Handler
中的一个接口, 如下; 另一类是带标志位async
的, 是设置消息是否为异步处理, Handler
中的消息默认是同步处理的
public interface Callback {
public boolean handleMessage(Message msg);
}
这里我们以构造函数Handler(Callback callback, boolean async)
进行分析, 如下; 可以看出, 这里的mLooper
是通过Looper.myLooper()
获取的, 如果我们获取为null
也就是说当前线程没有Looper
的话, 会抛出异常, 这个异常也是我们大多数初学者会遇到的; 这里也验证了上面所说的, Looper
是Handler
构造所必须的, 如果当前线程没有Looper
的话, 会抛异常(至于为什么说是当前线程, 接下来马上分析~)
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
那么Looper.myLooper()
又是如何获取当前线程的Looper
的呢; 如下, 可以看出是通过sThreadLocal
获取的, sThreadLocal
是一个ThreadLocal
类型的实例
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
ThreadLocal
不是一个线程, 而是用于在每个线程中存储数据的, ThreadLocal
可以在不同的线程中互不干扰的存储并提供数据; 那么它是如何实现在不同线程中互不干扰的存取数据的呢, 我们先来看其set()
方法, 可以看出, 其实际上会先去检查当前线程是否持有ThreadLocalMap
, 如果有的话, 直接将value
存储进去即可, 否则, 初始化当前线程的ThreadLocalMap
然后再存放值, 这样就可以根据不同的线程来存取各自线程的值了
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
那么我们要构造Handler
就必须获取当前线程的Looper
, 那么我们又是什么时候构造和存储的Looper
喃; 前面我们说了, 每个线程都可以有自己的Looper
, 但是除了UI
线程以外都默认没有创建; 我们先来看UI
线程中是什么时候创建的Looper
, UI
线程就是ActivityThread
, 在其main()
函数中, 我们发现了如下与Looper
相关的语句, 这实际上是开启了主线程的消息处理(因为要处理各种事件, 所以一开始就开启了)
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();
}
我们继续看Looper.prepareMainLooper()
, 其中实际上是调用了prepare()
, 如下; 这里我们看到了熟悉的ThreadLocal.set()
, 而且存储的是一个新的Looper
, 那么这里就将线程和各自的Looper
联系起来了, 我们在Handler
构造函数中获取的也就是和各个线程相关的Looper
了
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));
}
到这里我们知道了, perpare
实际上是在准备Looper
, 那么Looper.loop()
又是在做啥呢, 其实Looper.loop()
是开启消息处理循环, 前面我们说了Looper
不断的去检测MessageQueue
中是否有消息, 实际上就是在loop()
中开启的
这里我们暂时不讲loop()
, 而是放到后面讲消息处理的时候讲; 我们平时使用Handler
的时候, 大多是在UI
线程中创建的, 因为上面我们已经看到了, UI
线程中默认是创建了Looper
的, 所以不需要我们再去手动创建, 但是我们如何在子线程中构造和使用Handler
呢; 其实在Looper
的文档中已经给出了示例, 如下; 其实, 和上面UI
线程中Looper
的处理过程差不多, 只是由于UI
线程的特殊性, 所以单独给它准备了一个方法~(实际上是做一些其他特殊判断和处理)
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
三. 消息发送
当我们需要通过Handler
发送消息时, 可以使用两个系列, 一个是postXXX
系列, 一个sendXXX
系列
postXXX: post
系列主要是发送一个自定义的Runnable
事件, 然后去执行(延迟执行)自定义事件
post(Runnable r) |
---|
postAtTime(Runnable r, long uptimeMillis) |
postAtTime(Runnable r, Object token, long uptimeMillis) |
postDelayed(Runnable r, long delayMillis) |
postDelayed(Runnable r, Object token, long delayMillis) |
postAtFrontOfQueue(Runnable r) |
sendXXX: send
系列主要是发送封装的一个msg
, 然后根据msg
带的tag
或者数据在handleMessage()
中进行处理
sendMessage(Message msg) |
---|
sendEmptyMessage(int what) |
sendEmptyMessageDelayed(int what, long delayMillis) |
sendEmptyMessageAtTime(int what, long uptimeMillis) |
sendMessageDelayed(Message msg, long delayMillis) |
sendMessageAtTime(Message msg, long uptimeMillis) |
sendMessageAtFrontOfQueue(Message msg) |
但是, 实际上post
系列最终还是将Runnable
事件封装为了一个Message
对象交给了send
系列处理, 而不管是postXXX
还是sendXXX
, 最终转到的都是enqueueMessage()
进行处理; 另外, 不管是post
的Runnable
还是send
的int what
等其他类型, 其实最终都会将每一条消息封装为一个Message
对象进行传递
四. 消息存储
前面我们说过, MessageQueue
会对发送的消息进行存储, 这一步就是在enqueueMessage()
中处理的; 如下; 需要注意的是这里传入的MessageQueue
是在创建Handler
时创建, 也就是说和Handler
相关, 因为涉及到多线程的处理, 所以需要清楚MessageQueue
的来源, 不然消息处理的时候就混了~
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
这里我们简单看一下MessageQueue
中Message
的组织方式, 如下, MessageQueue.enqueueMessage()
, 可以看出是很明显的单链表的存储方式, 之所以用单链表, 是因为单链表在增加和删除节点上有优势
boolean enqueueMessage(Message msg, long when) {
...
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;
...
}
五. 消息处理
还记得前面我们还有一个问题没有解决吗, 就是Looper.loop()
过程, Looper.loop()
过程实际上就是开启循环处理消息的过程~
该函数如下; 首先获取消息处理线程的Looper
, 前面我们已经看到了, Looper.loop()
是在消息处理线程中调用的, 所以这里获取到的, 也就是消息处理线程, 即创建Handler
的线程; 之后获取对应线程的消息队列, 即MessageQueue
, 需要注意这里的MessageQueue
和Looper
关联的, 而Looper
的获取是通过Thread.currentThread()
来判断和获取的, 那么这里就能获取到和对应Handler
相关联的MessageQueue
, 当我们在不同线程中调用Handler.sendMsg
的时候, 插入的也是和该Handler
关联的MessageQueue
, 这样, 一整条完整的线就串成了: 不同的线程往需要发送消息的线程的MessageQueue
中插入Message
, 而和线程关联的Looper
又从该MessageQueue
中不断获取Message
处理, 这样就完成了线程的切换 !!
接下来就是用一个死循环(for
)开始消息处理了; 逻辑也比较简单, 其实就是不断的从MessageQueue
中获取消息(MessageQueue.next()
), 然后回调Handler.dispatchMessage(msg)
分发, 需要注意的是, 这里的msg.target
实际上是存储的对应Handler
的引用
public static void loop() {
final Looper me = myLooper();
...
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next(); // might block
// 如果消息为null, 不处理咯
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
msg.target.dispatchMessage(msg);
}
}
我们看一下dispatchMessage
的处理逻辑, 如下; 逻辑也很简单, 就是消息处理的优先级: 如果Message
自身设置了Callback
, 就直接执行Message
的Callback
(这种情况对应前面说的post(Runnable)
的情况); 如果构造Handler
的时候设置了Callback
那么交给该Callback
处理, 否则回调handleMessage()
处理
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这里还要注意的一点是从MessageQueue
中获取消息时, 即MessageQueue.next()
, 这是一个阻塞方法, 也就是说当MessageQueue
中没有消息的时候, 会阻塞在这里, 直到MessageQueue
中再次存储消息
六. 总结
到这里Handler
的整个Java
层面的运行机制我们就讲解完啦~ , 到这里我们对整个Android
的消息传递和处理机制也有了比较详细的了解了; 为加深记忆, 可以对着源码自己再过一遍过程~
网友评论