【艺术探索笔记】第 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 操作进行了验证:
【艺术探索笔记】第 10 章 Android 的消息机制
文章图片

系统提供 Handler 主要是为了解决子线程中我发访问 ui 的矛盾。

  • 为什么不允许在子线程访问 ui?
    Android 的 ui 控件不是线程安全的,在多线程中并发的访问可能导致 ui 控件处于不可预期状态。
  • 为什么系统不对 ui 控件的访问加上锁机制?
    1. 会使 ui 访问逻辑变得复杂
    2. 锁机制降低 ui 访问效率,因为锁机制会阻塞某些线程的执行
Handler 创建时会采用当前线程的 Looper 来构建内部消息循环系统,如果当前线程没有 Looper,就会报错。
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
    【艺术探索笔记】第 10 章 Android 的消息机制
    文章图片

    首先通过 getMap 方法获取当前线程中的 ThreadLocal 数据 ThreadLocalMap。Thread 类的内部有一个成员变量专门存储线程的 ThreadLocal 数据:ThreadLocal.ThreadLocalMap ,因此获取当前线程的 ThreadLocal 数据就异常简单了。
    如果 map 的值为 null,就需要调用 createMap 方法用当前需要设置的值去为当前线程创建 ThreadLocalMap 对象;如果不为 null,就直接将当前值进行存储。
  • ThreadLocal.ThreadLocalMap#set() api-27
    【艺术探索笔记】第 10 章 Android 的消息机制
    文章图片

  • ThreadLocal#get() api-27
    【艺术探索笔记】第 10 章 Android 的消息机制
    文章图片

    首先获取当前线程的 ThreadLocalMap 对象,如果不为 null 并且 ThreadLocal 存储的数据也不为 null,就返回存储的对象;否则调用 setInitialValue 初始化当前线程的 ThreadLocalMap 对象并初始化 ThreadLocalMap 对象里边的 value,返回初始的 value 值(null)。
从 ThreadLocal 的 set 和 get 方法可以看出,他们所操作的对象都是当前线程的 threadLocals 对象的 table 数组,因此在不同线程中访问同一个 ThreadLocal 的 set 和 get 方法,它们对 ThreadLocal 所做的读/写操作仅限于各自线程的内部,这就是为什么 ThreadLocal 可以在多个线程中互不干扰的存储和修改数据
10.2.2 消息队列的工作原理
MessageQueue 主要包含两个操作:插入和读取。
读取操作本身会伴随着删除操作,插入和读取对应的方法分别为 enqueueMessagenext,enqueueMessage 的作用是往消息队列中插入一条消息,next 的作用是从消息队列中取出一条消息并将消息从消息队列移除。
内部实现是用单链表的数据结构来维护消息列表,单链表在插入删除上比较有优势。
  • MessageQueue#enqueueMessage() api-27
    【艺术探索笔记】第 10 章 Android 的消息机制
    文章图片

  • MessageQueue#next() api-27
    【艺术探索笔记】第 10 章 Android 的消息机制
    文章图片

    next 方法是一个无限循环方法,如果消息队列没有消息,它会阻塞在这里,有消息时,next 方法返回这条消息并将其从单链表移除
10.2.3 Looper 的工作原理
Looper 扮演者消息循环的角色。它会不停的从 MessageQueue 中查看是否有新消息,有的话就立刻处理,否则就阻塞在那里。
  • Looper 构造方法 api-27
    【艺术探索笔记】第 10 章 Android 的消息机制
    文章图片

    创建一个 MEssageQueue;把当前线程的对象保存起来。
在主线程以外的线程中创建 Handler,需要手动创建 Looper。调用 Looper.prepare() 为当前线程创建 Looper,调用 Looper.loop() 开启消息循环
Looper 除了 prepare 方法外,还提供了 prepareMainLooper 方法,这个方法主要给主线程 ActivityThread 创建 Looper 使用,本质也是调用了 prepare 方法来实现的。主线程比较特殊,Looper 还提供 getMainLooper 方法,可以在任何地方获取到主线程 Looper。
Looper 也是可以退出的,通过 quitquitSafely 来推出一个 Looper。二者区别:quit 会直接退出;quitSafely 只是设定一个退出标记,然后把消息队列里边的消息处理完毕后才安全的退出。
Looper 退出后,通过 Handler 发送消息会失败,send 方法会返回 false。
在子线程中如果手动创建了 Looper,在所有事情处理完后也应该主动调用 quit 来种植消息循环,否则这个线程会一直处于等待状态,退出 Looper 后,这个线程会立刻中止,因此建议不需要的时候终止 Looper。
  • Looper#loop() api-27
    【艺术探索笔记】第 10 章 Android 的消息机制
    文章图片

    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 中执行的,这样就成功将代码逻辑切换到指定线程中去执行了。
10.2.4 Handler 的工作原理
Handler 的工作主要包含消息的发送和接收过程。
消息的发送通过一系列的 send 和 post 方法来实现,post 的一系列方法最终都是通过 send 方法来实现的
  • 消息发送 api-27
    【艺术探索笔记】第 10 章 Android 的消息机制
    文章图片

    发送过程只是向消息队列中插入了一条消息,MessageQueue 的 next 方法就会返回这条消息给 Looper,Looper 收到消息后就开始处理了,最终消息由 Looper 交给 Handler 处理,Handler 的 dispatchMessage() 方法会被调用。
  • Handler#dispatchMessage() api-27
    【艺术探索笔记】第 10 章 Android 的消息机制
    文章图片

  • Handler 消息处理流程图
    【艺术探索笔记】第 10 章 Android 的消息机制
    文章图片

Handler 的构造方法:
【艺术探索笔记】第 10 章 Android 的消息机制
文章图片

10.3 主线程的消息循环 Android 的主线程就是 ActivityThread,主线程的入口方法为 main,main 方法中会通过 Looper 的 prepareMainLooper() 来创建主线程的 Looper 以及 MessageQueue,并通过 Looper.loop() 方法来开启主线程的消息循环。
  • ActivityThread#main() api-27
    【艺术探索笔记】第 10 章 Android 的消息机制
    文章图片

主线程消息循环开始以后,ActivityThread 还需要一个 Handler 和消息队列进行交互,这个 Handler 就是 ActivityThread.H,它内部定义了一组消息类型,包括四大组件的启动和停止等:
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 中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。

    推荐阅读