【艺术探索笔记】第 10 章 Android 的消息机制
第 10 章 Android 的消息机制 Android 的消息机制主要是指 Handler 的运行机制。
Handler 的运行需要底层的 MessageQueue 和 Looper 的支撑。
MessageQueue 是消息队列,内部存储了一组消息,以队列的形式对外提供添加删除;存储消息使用的是单链表的结构。
Lopper 是消息循环。MessageQueue 只是消息存储,不能处理消息,Looper 就填充了这个功能。它以无限循环方式去查是否有新消息,有就处理,没有就阻塞。
ThreadLocal 可以在不同的线程中互不干扰的存储数据,通过 ThreadLocal 可以轻松获取每个线程的 Looper
【【艺术探索笔记】第 10 章 Android 的消息机制】线程默认是没有 Looper 的,如果要使用 Handler 必须先创建 Looper。主线程(ActivityThread)被创建时就会默认初始化 Looper,这也是在主线程中默认可以使用 Handler 的原因。
10.1 Android 的消息机制概述 Handler 的主要作用是将一个任务切换到某个指定的线程中去执行。
Android 中为什么需要这个功能?因为 Android 规定访问 UI 必须在主线程中进行,如果在子线程中访问 UI 会抛出异常,因为 ViewRootImpl 对 UI 操作进行了验证:
文章图片
系统提供 Handler 主要是为了解决子线程中我发访问 ui 的矛盾。
- 为什么不允许在子线程访问 ui?
Android 的 ui 控件不是线程安全的,在多线程中并发的访问可能导致 ui 控件处于不可预期状态。
- 为什么系统不对 ui 控件的访问加上锁机制?
- 会使 ui 访问逻辑变得复杂
- 锁机制降低 ui 访问效率,因为锁机制会阻塞某些线程的执行
- 会使 ui 访问逻辑变得复杂
Handler 创建完成后,内部的 Looper 和 MessageQueue 就可以和 Handler 一起协同工作了,Handler 通过 post 方法将一个 Runnable 投递到 Handler 内部的 Looper 中去处理,也可以通过 Handler 的 send 方法发送一个消息,消息同样会在 Looper 中去处理。post 方法最终也是通过 send 方法来完成的
send 方法的工作过程:
当 Handler 的 send 方法被调用时,它会调用 MessageQueue 的 enqueueMessage 方法将这个消息放入消息队列,然后 Looper 发现有新消息到来时,就会处理这个消息,最终消息中的 Runnable 或者 Handler 中的 handleMessage 方法就会被调用。注意 Looper 是运行在创建 Handler 所在的线程中的,这样一来 Handler 中的业务逻辑就被切换到创建 Handler 所在的线程中去执行了。
10.2 Android 的消息机制分析 10.2.1 ThreadLocal 的工作原理
ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在存储数据的线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
Looper、ActivityThread、AMS 中都用到了 ThreadLocal。
以 Handler 为例,它需要当前线程的 Looper,很显然 Looper 的作用域是线程并且不同的线程有不同的 Looper,这时候通过 ThreadLocal 就可以轻松实现 Looper 在线程中的存取。
如果不采用 ThreadLocal,那系统就必须提供一个全局的哈希表供 Handler 查找指定线程的 Looper,这样一来就必须提供一个类似 LooperManager 的类,系统并没有这么做而是选择了 ThreadLocal。
ThreadLocal 的另一个使用场景是
复杂逻辑下的对象传递
,比如监听器的传递
,当一个线程中的任务比较复杂时,比如入口多样、调用栈比较深。此时监听器要贯穿整个线程的执行过程,用 ThreadLocal 可以让监听器作为线程内的全局对象而存在,线程内部只需要通过 get 方法就可以获取到监听器。ThreadLocal 之所以有这种效果,是因为不同线程访问它的 get 方法时,ThreadLocal 内部会从各自的线程中取出一个数组,然后再从数组中根据当前 ThreadLocal 的索引去查找对应的 value 值。
ThreadLocal 的内部实现:
它是一个泛型类,定义为
public class ThreadLocal
,只要弄清楚它的 get 和 set 方法就可以知道它的工作原理。ThreadLocal#set()
api-27
文章图片
首先通过 getMap 方法获取当前线程中的 ThreadLocal 数据 ThreadLocalMap。Thread 类的内部有一个成员变量专门存储线程的 ThreadLocal 数据:ThreadLocal.ThreadLocalMap
,因此获取当前线程的 ThreadLocal 数据就异常简单了。
如果 map 的值为 null,就需要调用 createMap 方法用当前需要设置的值去为当前线程创建 ThreadLocalMap 对象;如果不为 null,就直接将当前值进行存储。
ThreadLocal.ThreadLocalMap#set()
api-27
文章图片
ThreadLocal#get()
api-27
文章图片
首先获取当前线程的 ThreadLocalMap 对象,如果不为 null 并且 ThreadLocal 存储的数据也不为 null,就返回存储的对象;否则调用 setInitialValue 初始化当前线程的 ThreadLocalMap 对象并初始化 ThreadLocalMap 对象里边的 value,返回初始的 value 值(null)。
10.2.2 消息队列的工作原理
MessageQueue 主要包含两个操作:插入和读取。
读取操作本身会伴随着删除操作,插入和读取对应的方法分别为
enqueueMessage
和 next
,enqueueMessage 的作用是往消息队列中插入一条消息,next 的作用是从消息队列中取出一条消息并将消息从消息队列移除。内部实现是用单链表的数据结构来维护消息列表,单链表在插入删除上比较有优势。
MessageQueue#enqueueMessage()
api-27
文章图片
MessageQueue#next()
api-27
文章图片
next 方法是一个无限循环方法,如果消息队列没有消息,它会阻塞在这里,有消息时,next 方法返回这条消息并将其从单链表移除
Looper 扮演者消息循环的角色。它会不停的从 MessageQueue 中查看是否有新消息,有的话就立刻处理,否则就阻塞在那里。
Looper 构造方法
api-27
文章图片
创建一个 MEssageQueue;把当前线程的对象保存起来。
Looper 除了 prepare 方法外,还提供了 prepareMainLooper 方法,这个方法主要给主线程 ActivityThread 创建 Looper 使用,本质也是调用了 prepare 方法来实现的。主线程比较特殊,Looper 还提供 getMainLooper 方法,可以在任何地方获取到主线程 Looper。
Looper 也是可以退出的,通过
quit
和 quitSafely
来推出一个 Looper。二者区别:quit 会直接退出;quitSafely 只是设定一个退出标记,然后把消息队列里边的消息处理完毕后才安全的退出。Looper 退出后,通过 Handler 发送消息会失败,send 方法会返回 false。
在子线程中如果手动创建了 Looper,在所有事情处理完后也应该主动调用 quit 来种植消息循环,否则这个线程会一直处于等待状态,退出 Looper 后,这个线程会立刻中止,因此建议不需要的时候终止 Looper。
Looper#loop()
api-27
文章图片
loop 方法是一个死循环,唯一跳出循环的方式是 MessageQueue 的 next 方法返回了 null。当Looper#quit()
调用时, Looper 就会调用 MessageQeue 的 quit 或 quitSafely 方法通知消息队列退出,当消息队列退出状态时,它的 next 方法就会返回 null。也就是说 Looper 必须退出,否则它会无限循环下去。
loop 内部调用 MessageQueue 的 next 方法获取新消息,next 方法是一个阻塞操作,当没有消息时,也会导致 Looper 一直阻塞;当 MessageQueue 的 next 返回了新消息,Looper 就会处理这条消息:msg.target.dispatchMessage(msg)
,这里的msg.target
就是发送这条消息的 Handler 对象,这样 Handler 发送的消息最终由交给了它的dispatchMessage
方法来处理了。
Handler 的 dispatchMessage 方法是在创建 Handler 时所使用的 Looper 中执行的,这样就成功将代码逻辑切换到指定线程中去执行了。
Handler 的工作主要包含消息的发送和接收过程。
消息的发送通过一系列的 send 和 post 方法来实现,post 的一系列方法最终都是通过 send 方法来实现的
消息发送
api-27
文章图片
发送过程只是向消息队列中插入了一条消息,MessageQueue 的 next 方法就会返回这条消息给 Looper,Looper 收到消息后就开始处理了,最终消息由 Looper 交给 Handler 处理,Handler 的 dispatchMessage() 方法会被调用。
Handler#dispatchMessage()
api-27
文章图片
- Handler 消息处理流程图
文章图片
文章图片
10.3 主线程的消息循环 Android 的主线程就是 ActivityThread,主线程的入口方法为 main,main 方法中会通过 Looper 的 prepareMainLooper() 来创建主线程的 Looper 以及 MessageQueue,并通过 Looper.loop() 方法来开启主线程的消息循环。
ActivityThread#main()
api-27
文章图片
private class H extends Handler {
public static final int LAUNCH_ACTIVITY= 100;
public static final int PAUSE_ACTIVITY= 101;
public static final int PAUSE_ACTIVITY_FINISHING= 102;
public static final int STOP_ACTIVITY_SHOW= 103;
public static final int STOP_ACTIVITY_HIDE= 104;
public static final int SHOW_WINDOW= 105;
public static final int HIDE_WINDOW= 106;
public static final int RESUME_ACTIVITY= 107;
public static final int SEND_RESULT= 108;
public static final int DESTROY_ACTIVITY= 109;
public static final int BIND_APPLICATION= 110;
public static final int EXIT_APPLICATION= 111;
public static final int NEW_INTENT= 112;
public static final int RECEIVER= 113;
public static final int CREATE_SERVICE= 114;
public static final int SERVICE_ARGS= 115;
public static final int STOP_SERVICE= 116;
...
}
ActivityThread 通过 ApplicationThread 和 AMS 进行进程间通信,AMS 以进程间通信的方式完成 ActivityThread 的请求后调用 ApplicationThread 中的 Binder 方法,然后 ApplicationThread 会向 H 发送消息,H 姐收到消息后会将 ApplicationThread 中的逻辑切换到 ActivityThread 中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。
推荐阅读
- 宽容谁
- 我要做大厨
- 增长黑客的海盗法则
- 画画吗()
- 2019-02-13——今天谈梦想()
- 远去的风筝
- 三十年后的广场舞大爷
- 叙述作文
- 20190302|20190302 复盘翻盘
- 学无止境,人生还很长