Handler 运行机制
Android 的消息机制概述
Handler 是 Android 消息机制的上层接口,这使得在开发过程中只需要和 Handler 交互即可。从本质上来说,Handler 并不是专门用于更新 UI 的,它只是常被开发者用来更新 UI。因为主线程不能做耗时操作,而子线程不能访问 UI(因为 Android 的 UI 控件不是线程安全的)。
Android 的消息机制主要是指 Handler 的运行机制,Handler 的运行需要底层的 MessageQueue 和 Looper 的支撑。异步消息处理主要由 4 个部分组成,日常开发中,经常都只会用到 Handler 和 Message 两个类:
- Message:负责消息的搭载。是线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间传递数据。里面有个target用于标记消息,obj用于存放内容。
- Handler:顾名思义就是处理者的意思。它主要是用于发送和处理消息的。发送消息一般是使用 Handler 的 sendMessage()、post() 等,而发出的消息经过一系列地辗转处理后,最终会传递到 Handler 的 handleMessage() 中。
- MessageQueue:消息队列。它主要用于存放所有通过 Handler 发送的消息,这部分消息会一直存在于消息队列中,等待被处理。内部采用单链表(在插入和删除上比较有优势)的数据结构来存储消息列表,以队列的形式对外提供插入和删除的工作。采用先进先出的方式管理Message。每个线程中只会有一个 MessageQueue 对象 。
- Looper:消息循环。消息泵,是 MessageQueue 的管理者(MessageQueue 只是一个消息存储单元),调用 Looper 的 loop() 后,会以无限循环的形式从 MessageQueue 中查找是否有新消息,有则将消息分给对应的 Handler 处理,传递到 Handler 的 handleMessage() 中,若消息队列为空,线程则会阻塞等待。每个线程中只会有一个 Looper 对象。
一个 Thread 对应多个 Handler,但只对应一个 Looper 和 MessageQueue,Handler 与 Thread 共享 Looper 和 MessageQueue,Message 只是消息的载体,将会被发送到与线程绑定且唯一的 MessageQueue 中,并被与线程绑定且唯一的 Looper 分发,被与其自身绑定的 Handler 消费。
Android 的消息机制分析
Handler 整体工作流程浅析分为以下四个步骤:异步通信准备 => 消息入队 => 消息循环 => 消息处理
- 异步通信准备
- 假定是在主线程创建 Handler,则会直接在主线程中创建处理器对象Looper、消息队列对象 MessageQueue 和 Handler 对象。需要注意的是,Looper和MessageQueue均是属于其创建线程的,是线程绑定得,并且都唯一。
- Looper对象的创建一般通过Looper.prepareMainLooper()和Looper.prepare()两个方法,而创建Looper对象的同时,将会自动创建MessageQueue。
- 创建好MessageQueue后,Looper将自动进入消息循环。此时,Handler自动绑定了主线程的Looper和MessageQueue。
- 消息入队
工作线程通过Handler发送消息Message到消息队列MessageQueue中,消息内容一般是 UI 操作。发送消息一般都是通过Handler.sendMessage(Message msg)和Handler.post(Runnabe r)两个方法来进行的。而入队一般是通过MessageQueue.enqueueeMessage(Message msg,long when)来处理。 - 消息循环
主要分为「消息出队」和「消息分发」两个步骤,Looper会通过循环取出消息队列MessageQueue里面的消息Message,并分发到创建该消息的处理者Handler。如果消息循环过程中,消息队列MessageQueue为空队列的话,则线程阻塞。 - 消息处理
Handler接收到Looper发来的消息,开始进行处理。
异步消息处理流程:
首先需要在主线程中创建一个 Handler 对象,并重写 handleMessage()。
然后当子线程中需要进行 UI 操作时,就创建一个 Message 对象,并通过 Handler 将这条消息发送出去。
实际上这个 Message 是发送到了与当前线程绑定的一个MessageQueue 的队列中等待被处理。
然后与当前线程绑定的 Looper 会在构造函数中通过 queue.next()(MessageQueue的方法)方法不断地从MessageQueue 中取出新的 Message,
next() 操作是一个阻塞操作,进而导致 loop 一直阻塞,无消息时阻塞等待,当有消息时,Looper 收到消息之后开始处理消息,同样在构造函数中调用 msg.target.dispatchMessage(msg) 方法将消息分发到与 Message 绑定的 Handler 中的 handler.handleMessage() 回调方法中。这里的msg.target就是 Handler。
handler.handleMessage(msg) 在 dispatchMessage(Message msg)中回调的。
Handler 处理消息过程;会检查 Message 的 callback 和 mCallback 是否为 null,当发送一条消息时,最终都会调用 enqueneMessage(),而 msg.target 得赋值就在这个方法中,也是在这里将 message 信息添加到MessageQueue 中。
由于 Handler 是在主线程中创建的,所以此时 handleMessage() 方法中的代码也会在主线程中运行,于是就可以在这里进行 UI 操作了。
ThreadLocal 的工作原理
Handler 创建时会采用当前线程的 Looper(通过 ThreadLocal 获取每个线程的 Looper,它可在不同线程中互不干扰地存储并提供数据)来构造消息循环系统。
一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,或是复杂逻辑下的对象传递,可考虑采用 ThreadLocal。
它是一个线程内部的数据存储类,在指定线程中存储数据,并在指定线程中获取数据。可在不同线程中维护一套数据的副本并且彼此互不干扰。(因为不同线程访问同一个 ThreadLocal 的 get(),ThreadLocal 内部会从各自的线程中取出一个数组,然后根据当前 ThreadLocal 的索引去查找出对应的 value 值)
ThreadLocal 是一个泛型类,它的定义为 public class ThreadLocal
- **set()**:会通过 values() 获取当前线程中的 ThreadLocal 数据(通过 ThreadLocal.Values localValues 的 table 数组)
- **get()**:同样取出当前线程的 localValues 对象,为 null 就返回初始值(由 ThreadLocal 的 initialValue() 描述),默认为 null。如果不为 null,就取出它的 table 数组并找出 ThreadLocal 的 reference 对象在数组中的位置,然后 table 数组中的下一个位置所存储的数据就是 ThreadLocal 的值。
线程本地存储的功能
- ThreadLocal类可实现线程本地存储的功能,把共享数据的可见范围限制在同一个线程之内,无须同步就能保证线程之间不出现数据争用的问题,这里可理解为ThreadLocal帮助Handler找到本线程的Looper。
底层数据结构
- 每个线程的Thread对象中都有一个ThreadLocalMap对象,它存储了一组以ThreadLocal.threadLocalHashCode为key、以本地线程变量为value的键值对,而ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口,也就包含了一个独一无二的threadLocalHashCode值,通过这个值就可以在线程键值值对中找回对应的本地线程变量。
消息队列的工作原理
MessageQueue 主要包含两个操作:
- 插入:enqueueMessage(),往消息队列中插入一条消息。
- 读取:next(),从消息队列中取出一条消息并将其移除。这是一个无限循环的方法(for(;;)),如果消息队列中没有消息,那么 next() 会一直阻塞在这里。当有新消息到来时,next() 会返回这条消息并将其从单链表中移除。
Looper 的工作原理
在 Android 中,所有 Android 框架的事件(比如 Activity 的生命周期方法调用和按钮点击等)都是放入到消息中,然后加入到 Looper 要处理的消息队列中,由 Looper 负责一条一条地进行处理。
每个线程只有一个 Looper。当一个 Android 应用启动时,会自动创建一个供应用主线程使用的 Looper 实例。Looper 的主要工作就是一个一个处理消息队列中的消息对象。它会不停地从消息队列中查看是否有消息,如果有新消息就立刻处理,否则就一直阻塞在那里。
在它的构造方法中会创建一个 MessageQueue 即消息队列,然后将当前线程的对象保存起来。
Looper的构造函数:
1 | // 做了两件事 |
Looper 提供的一些方法:
- 可通过 Looper.prepare() 为当前线程创建一个 Looper,接着通过 Looper.loop() 来开启消息循环。
- Looper.myLooper() 可以获得当前线程的Looper对象。
- prepareMainLooper() 给 ActivityThread 创建 Looper 使用,其本质也是通过 prepare() 实现的。
- getMainLooper() 可在任何地方获取到主线程的 Looper。
- 提供了 quit()(直接退出) 和 quitSafely()(设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全退出) 来退出一个 Looper。退出后,send() 返回 false。
在子线程中,如果手动为其创建了 Looper,那么做完事后应调用 quit() 来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出 Looper 后,这个线程就会立刻终止,因此建议不需要的时候终止 Looper。
Looper 的 loop()
Looper.loop()方法无限循环
看看 Looper.loop() 方法无限循环部分的代码
1
2
3
4
5
6
7
8while (true) {
//取出消息队列的消息,可能会阻塞
Message msg = queue.next(); // might block
...
//解析消息,分发消息
msg.target.dispatchMessage(msg);
...
}
最重要的一个方法,只有调用了它,消息循环系统才会真正地起作用,其实现:
- 唯一跳出 loop() 死循环的方式是 MessageQueue 的 next() 返回了 null。
- 当 Looper 的 quit() 被调用时,它会调用 MessageQueue 的 quit() 或 quitSafely() 来通知消息队列退出,当消息队列被标记为退出状态时,它的 next() 就会返回 null。
- loop() 会调用 MessageQueue 的 next() 来获取新消息,而 next() 是一个阻塞操作,当没有消息时,next() 会一直阻塞在那里,从而导致 loop() 一直阻塞在那里。
- 如果 MessageQueue 的 next() 返回了新消息,Looper 就会处理这条消息:
- msg.target.dispatchMessage(msg),这里的 msg.target 是发送这条消息的 Handler 对象,这样 Handler 发送的消息最终又交给它的 dispatchMessage() 来处理了。
- 但是这里不同的是,Handler 的 dispatchMessage() 是在创建 Handler 时所使用的 Looper 中执行的,这样就成功地将代码逻辑切换到指定的线程中去执行了。(Looper 如绑定是UI线程,那Handler的方法就在UI线程中执行,线程间就是这样交互的。)
loop的循环消耗性能吗?
- 主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。
- 简单的来说:ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么程序也就可以退出了。
Handler 的工作原理
Handler 的主要作用是将一个任务切换到某个指定的线程中去执行,因为子线程不能访问 UI,ViewRootImpl 对 UI 操作做了验证,由其 checkThread() 完成。
Handler 创建的时候会采用当前线程的 Looper 来构造消息循环系统,需要注意的是,线程默认是没有 Looper的,如果需要使用 Handler 就必须为线程创建 Looper,因为 Android 是单线程模型的,默认的 UI 主线程,也就是 ActivityThread,ActivityThread 被创建的时候就会初始化 Looper,这也是在主线程中默认可以使用 Handler 的原因。
Handler 的工作主要包含消息的发送(post 的一系列方法最终是通过 send 的一系列方法来实现的)和接收过程。它把消息发送给 Looper 管理的 MessageQueue,并负责处理 Looper 分发给他的消息。当 Handler 的 send() 被调用时,发送消息的过程仅仅是向消息队列中插入了一条消息,它会调用 MessageQueue 的 enqueueMessage() 将这个消息放入消息队列中,MessageQueue 的 next() 就会返回这条消息给 Looper,然后 Looper 发现有新消息到来时,就会处理这个消息,最终消息中的 Runnable 或者 Handler 的 handleMessage() 就会被调用,这时 Handler 就进入了处理消息的阶段。
Handler 处理消息的过程如下:
- 首先,检查 Message 的 callback(Runnable 对象,实际上就是 Handler 的 post() 所传递的 Runnable 参数)是否为 null,不为 null 就通过 handleCallback 来处理消息。
- 其次,检查 mCallback 是否为 null,不为 null 就调用其 handleMessage() 处理消息。Callback 是个接口,可用来创建 Handler 对象,其不需要派生 Handler 的子类。
- 最后,调用 Handler 的 handleMessage() 来处理消息。
主线程的消息循环
ActivityThread 中 main 方法
ActivityThread 类的注释上可以知道这个类管理着我们平常所说的主线程(UI线程)
- 首先 ActivityThread 并不是一个 Thread,就只是一个 final 类而已。我们常说的主线程就是从这个类的 main 方法开始,main 方法很简短
1
2
3
4
5
6
7
8
9public static final void main(String[] args) {
...
// 创建 Looper 和 MessageQueue。
Looper.prepareMainLooper();
...
// 轮询器开始轮询。
Looper.loop();
...
}
ActivityThread 的入口方法为 main,其中系统会通过 Looper.prepareMainLooper() 来创建主线程的 Looper 以及 MessageQueue,并通过 Looper.loop() 来开启主线程的消息循环,然后 ActivityThread 还需要一个 Handler 来和消息队列进行交互,这个 Handler 就是 ActivityThread.H,它内部定义了一组消息类型,主要包含了四大组件的启动和停止过程。
ActivityThread 通过 ApplicationThread 和 AMS 进行进程间通信,AMS 以进程间通信的方式完成 ActivityThread 的请求后回调 ApplicationThread 中的 Binder 方法,然后 ApplicationThread 会向 H 发送消息,H 收到消息后会将 ApplicationThread 中的逻辑切换到 ActivityThread 中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。
Handler 的基本使用
示例(Java):
实现的方式有三种:
- 调用 sendMessage 方法
- 通过 post 方法
- obtainMessage
1 | public class MainActivity extends AppCompatActivity { |
示例(Kotlin):
1 | /** |
Handler 的内存泄漏
使用 Handler 过程中可能存在的内存泄漏:
每当创建一个Handler的时候,它都会自动与当前线程的Looper绑定,当发送一个 target 为这个 Handler 的消息到 Looper 处理的消息队列时,实际上发送的消息已经包含了一个 Handler 实例的引用,只有这样 Looper 在处理到这条消息时才可以调用 Handler#handleMessage(Message) 完成消息的正确处理。这时,如果有消息未处理,因为Message持有Handler的引用,而在 Java 中,非静态的Handler(包括匿名类)又会隐式的持有其外部类的引用,会导致类无法被回收。如果这个 Handler 是在主线程进行了初始化,会默认关联这个主线程的 Looper,因为主线程中的 Looper 生命周期和当前应用一样长,当长生命周期的对象持有短生命周期对象的引用,同样会导致类无法被回收。
要解决这种问题,思路就是不使用非静态内部类,
继承 Handler 时,要么是放在单独的类文件中,要么就是使用静态内部类。
因为静态的内部类不会持有外部类的引用,所以不会导致外部类实例的内存泄露。
当需要在静态内部类中调用外部的 Activity 时,可以使用弱引用来处理。
另外同样也需要将 Runnable 设置为静态的成员属性。
或者在onDestroy()方法中调用Handler.removeCallbacksAndMessages(null)即可避免。
1 | public class MainActivity extends AppCompatActivity { |
一些问题
1、为什么一个线程只有一个Looper,只有一个MessageQueue?
查看源码,调用 Looper.prepare() 为当前线程创建 Looper 时做了判断。
1 | public static void prepare() { |
2、如何获取当前线程的Looper?是怎么实现的?(理解ThreadLocal)
3、是不是任何线程都可以实例化Handler?有没有什么约束条件?
子线程默认是没有 Looper 的。
4、Looper.loop是一个死循环,拿不到需要处理的Message就会阻塞,那在UI线程中为什么不会导致ANR?
因为Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。
5、为什么系统不建议在子线程访问UI,不对UI控件的访问加上锁机制的原因?
- 为什么系统不建议在子线程访问UI
- 系统不建议在子线程访问UI的原因是,UI控件非线程安全,在多线程中并发访问可能会导致UI控件处于不可预期的状态。
- 不对UI控件的访问加上锁机制的原因
- 上锁会让UI控件变得复杂和低效
- 上锁后会阻塞某些进程的执行
6、使用Hanlder的postDealy()后消息队列会发生什么变化?
内部调用了 sendMessageDelayed(),post delay的Message并不是先等待一定时间再放入到MessageQueue中,而是直接进入并阻塞当前线程,然后将其delay的时间和队头的进行比较,按照触发时间进行排序,如果触发时间更近则放入队头,保证队头的时间最小、队尾的时间最大。此时,如果队头的Message正是被delay的,则将当前线程堵塞一段时间,直到等待足够时间再唤醒执行该Message,否则唤醒后直接执行。
7、Message可以如何创建?哪种效果更好,为什么?runOnUiThread如何实现子线程更新UI?
创建Message对象的几种方式:
- Message msg = new Message();
- Message msg = Message.obtain();
- Message msg = handler1.obtainMessage();
后两种方法都是从整个Messge池中返回一个新的Message实例,能有效避免重复Message创建对象,因此更鼓励这种方式创建Message
runOnUiThread如何实现子线程更新UI
- 如果msg.callback为空的话,会直接调用我们的mCallback.handleMessage(msg),即handler的handlerMessage方法。由于Handler对象是在主线程中创建的,所以handler的handlerMessage方法的执行也会在主线程中。
- 在runOnUiThread程序首先会判断当前线程是否是UI线程,如果是就直接运行,如果不是则post,这时其实质还是使用的Handler机制来处理线程与UI通讯。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
备注
参考资料: