【Android】从源码角度看Handler机制
在Android开发规范中,规定了主线程的任务的响应时间不能超过5s,否则会出现ANR,即程序无响应。为了避免这个问题的出现,常用的一个解决方案就是开辟新线程,在开辟出来的子线程中去处理耗时的业务,然后回到UI线程(主线程)来刷新UI,这个过程中“回到UI线程刷新UI”这一步的实现,常用到handler。因此在我的理解中,Handler、主线程、子线程之间的关系可以比喻成人操作无人机,主线程就是操作无人机的人,子线程就是无人机,而handler就是人和无人机之间的遥控器,人放出无人机去拍摄各种场景(处理耗时操作),这个过程中人(主线程)可以不受影响的做自己的事情,当无人机(子线程)处理完任务,会把数据通过遥控器(Handler)的画面传递给人(主线程),人收到画面后对画面数据进行读取和使用(更新主线程)。例子仅做辅助理解用,如果有理解偏差不到位,还望指出!感谢!
实际上网上有关多线程、并发、Handler的资料有很多,多数讲解的内容也十分深刻详细,笔者自知能力有限,本文就不再像各位大佬那样介绍对Handler的理解了,但是想从源码的角度,来分析一下Handler的工作原理,也作为自己学习的内化成果检测。
首先需要介绍几个概念:
- MessageQueue: 消息队列,其实这里说队列有点不合适,因为实际上其内部存储并非队列的形式,而是用了一个单链表的数据结构来存储一系列消息。
- Looper: 消息轮询器,它能够不断的访问我们的消息队列,从而提取其中的消息以供处理。实现的核心在于其loop()方法。
- Handler: 既能接收消息,又能处理消息,主线程和子线程的桥梁。
- Message: 消息,是数据的载体。
下面我们正向的去了解一下Handler的运行原理:
我们前面说过,Handler相当于一个主线程与子线程之间的桥梁,这个桥梁之所以能在两端之间建立联系,是因为looper的存在:
我们的 Android 应用在启动时,会执行到 ActivityThread 类的 main 方法,就和我们以前写的 java 控制台程序一样,其实 ActivityThread 的 main 方法就是一个应用启动的入口。在这个入口里,会做很多初始化的操作。其中就有 Looper 相关的设置,代码如下
public static void main(String[] args) {
//............. 无关代码...............
Looper.prepareMainLooper();
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
进入prepareMainLooper()的实现:
public static void prepareMainLooper() {
prepare(false);//注意这里
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
进来就先执行了一个prepare方法,进去看看:
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));
}
这里我们可以看到,有一个sThreadLocal变量,如果这个变量非空,则执行prepare会报出异常:Only one Looper may be created per thread,如果这个变量为空,我们会为这个变量赋值一个新的Looper对象。
这里的Looper(quiteAllowed)显然是调用了Looper的构造方法:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
至此我们可以发现,整个过程在app的启动之初,主线程开始调用Looper.prepareMainLooper()方法, 为sThreadLocal设置了一个唯一的Looper,并且这个Looper是持有当前线程的引用的,也就是说,这个Looper与我们调用prepareMainLooper的线程是一个绑定的关系,而这个线程,就是主线程。
因此,在app启动之初,就将主线程所对应的Looper对象放进了sThreadLocal中。
说到这里还需要说一下这个ThreadLocal,ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。它其实类似于一个HashMap,每个线程对应了一系列数据。我们通过set和get方法读取某个线程所对应的一些数据的值。这个后文会有例子。
回过头继续看刚刚的main入口:
public static void main(String[] args) {
//............. 无关代码...............
Looper.prepareMainLooper();
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
在为当前线程创建好looper并放进sThreadLocal后,就开始执行Looper.loop()方法。这个方法具体是干什么的呢?
public static void loop() {
//获得一个 Looper 对象
final Looper me = myLooper();
// 拿到 looper 对应的 mQueue 对象
final MessageQueue queue = me.mQueue;
//死循环监听(如果没有消息变化,他不会工作的) 不断轮训 queue 中的 Message
for (;;) {
// 通过 queue 的 next 方法拿到一个 Message
Message msg = queue.next(); // might block
//空判断
if (msg == null)return;
//消息分发
msg.target.dispatchMessage(msg);
//回收操作
msg.recycleUnchecked();
}
}
这里就是消息轮询的关键了,在上文中给出的Looper的构造方法中,我们注意到它创建了一个MessageQueue对象mQueue,未来所有的任务都会在这个mQueue队列中存放,主线程通过loop()去遍历这个队列,并依次执行其中的任务。
这里介绍完looper的初始化过程以及系统通过looper实现的消息轮询,下面说一说Handler与这些东西到底有什么关系,先看Handler的构造方法:
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
//别忘了myLooper的实现
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
这样一看,似乎可以明白了——我们每次创建一个Handler,就会调用myLooper去从sThreadLocal中拿到当前创建Handler的线程的Looper对象,并且根据这个looper对象得到对应的消息队列。也就是说,如果我们在主线程中创建一个handler,那么他就会拿到主线程的Looper,然后得到主线程不断轮询的那个消息队列。
需要注意的是:线程默认是不具有Looper的,如果需要使用Handler就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。
当我们使用Handler去发送消息的时候,最终都会走进一个函数:
public boolean sendMessageAtTime(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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;//注意这句 查看定义可以发现 Message的target是一个类型为Handler的成员变量
//handler遵循谁发送消息 谁处理消息的原则 所以这里把target设置成self
// 使用默认的 handler 构造方法时,mAsynchronous 为 false。
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);//这句以后,messageQueue就会多一条message
//looper在调用loop()不断轮训messageQueue的时候就会发现它,并且处理它
}
看到这里相信能够明白了,我们每次使用handler去发送消息后,都会在当前线程的消息队列中添加一条消息,当前线程轮询到这条消息并执行它。
执行它的过程,刚刚我们在loop()方法的源码中可以看到,有这样一个函数:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);//我们没有给上面两个callback赋值,所以走进这个分支
}
}
//看这个方法的实现:
public void handleMessage(Message msg) {
//这是一个空方法
}
handleMessage方法的方法体是空的,这也是为什么我们在使用的时候常常需要复写一个handleMessage的原因。
至此应该有许多读者已经看晕了,说实话笔者能力有限…这一块正向的去解释确实有点凌乱。因此在这里再总结一下:
在android中线程是默认没有Looper的,在程序启动之初,会为主线程创建一个Looper对象,并且根据Looper初始化了当前线程的消息队列,然后开始主动轮询这个队列中的消息。当我们在某个线程中创建Handler时,就会获取当前线程的Looper(注意,这个线程是创建Handler的线程,而不是handler发送消息的线程),根据looper拿到这个线程的消息队列,然后未来所有通过Handler发送的消息,都会被加入到这个线程的队列中。我们常在主线程中创建了Handler后,用一个新的线程去调用handler.sendmessage()方法,而handler又是遵循自己发送自己处理的机制,会自己接收到消息并走入handleMessage方法,我们再通过复写handleMessage来刷新UI。
以上是对Handler机制以及其运行过程正向的分析,功力有限可能难以做到深入浅出。
有理解的不对的还望指正!感谢阅读!
推荐阅读
-
从源码角度理解Android线程
-
从源码角度看 PHP 字符串类型转换
-
Handler异步消息传递机制(四)Handler发送消息流程,源码(Android 9.0)彻底解析
-
Android消息通信机制Handler详解,Handler,Looper,MessageQueue,源码解析,讲解这几个类怎么配合工作的
-
Android消息机制三剑客之Handler、Looper、Message源码分析(一)
-
Android消息机制原理,仿写Handler Looper源码解析跨线程通信原理--之仿写模拟Handler(四)
-
Android Handler机制(三)----Looper源码解析
-
Android多线程(二)消息处理机制---Handler、Message、Looper源码原理解析
-
Android Handler消息机制源码分析
-
Android 从源码分析Handler消息机制