探索 Android 消息机制

1. Android 消息机制概述

Android 消息机制.png

Android 消息机制是由 HandlerLooperMessageQueue 三者合作完成的,消息机制可以分为消息机制初始化消息轮询消息发送消息处理 4 个过程来理解,消息机制是基于 Linux 的事件轮询机制 epoll 和用来通知事件的 文件描述符 eventfd 来实现的 。

消息机制初始化过程是从消息轮询器 Looper 的 prepare() 方法开始的,当线程调用 Looper 的 prepare() 方法时,prepare() 方法会调用 Looper 的构造函数创建一个 Looper ,并放到线程私有变量 ThreadLocal 中。Looper 的构造函数中会创建一个消息队列 MessageQueue ,而消息队列的构造方法会调用 nativeInit() JNI 方法初始化 Native 层的消息队列,在 Native 层消息队列的构造方法中,会调用 Native 层 Looper 的构造函数初始化 Native 层的 Looper ,而在 Native 层 Looper 的构造函数中会调用 rebuildEpollLocked() 方法,在 rebuildEpollLocked() 方法中会调用 epoll_create1() 系统调用创建一个 epoll 实例,然后再调用 epoll_ctl() 系统调用给 epoll 实例添加一个唤醒事件文件描述符,到这里消息机制的初始化就完成了。

epoll 、select 和 poll 都是 Linux 中的一种 I/O 多路复用机制, poll 和 select 在每次调用时,都必须遍历所有被监视的文件描述符,文件描述符列表越大,性能就越差。而 epoll 则把监听注册从监听中分离了出来,这样就不需要每次调用时都遍历文件描述符列表了。创建 epoll 实例时,Linux 会创建一个 evnetpoll 结构体,这个结构体中有 rbrrdlist 两个成员,rbr 是红黑树的根节点,epoll 会用红黑树存储所有需要监控的事件 ,rdlist 则是存放着要通过 epoll_wait() 返回给用户的事件。

唤醒事件文件描述符是一个 eventfd 对象,是 Linux 中的一个用来通知事件的文件描述符,与 pipe 相比,pipe 只能在进程/线程间使用,而 eventfd 是广播式的通知,可以多对多。eventfd 的结构体 eventfd_ctx 中有 wqhcount 两个成员,wqh 是一个等待队列的头结点,类型为 __wait_queue_head ,是一个自带自旋锁双向链表的节点,而 count 则是一个计数器

消息轮询过程是从 Looper 的 loop() 方法开始的,当线程调用 Looper 的 loop() 方法后,loop() 方法中会调用 MessageQueuenext() 方法获取下一条要处理的消息,next() 方法中会通过 nativePollOnce() JNI 方法调检查当前消息队列中是否有新的消息要处理,nativePollOnce() 方法会调用 NativeMessageQueuepollOnce() 方法,NativeMessageQueue 的 pollOnce() 方法会调用 Native 层 Looper 的 pollOnce() 方法, Native 层 Looper 的 pollOnce() 方法中会把 timeout 参数传到 epoll_wait() 系统调用中,epoll_wait() 调用后会等待事件的产生,当 MessageQueue 中没有更多消息时,传到 epoll_wait() 中的 timeout 的值就是 -1 ,这时线程会一直被阻塞,直到有新的消息进来,这就是为什么 Looper 的死循环不会导致 CPU 飙高,因为主线程处于阻塞状态。当调用完 nativePollOnce() 方法后,MessageQueue 就会看下当前消息是不是同步屏障,是的话就找出并返回异步消息给 Looper ,不是的话则找出下一条到了发送时间的返回非异步消息。

消息发送过程一般是从 Handler 的 sendMessage() 方法开始的,当我们调用 Handler 的 sendMessage() 或 sendEmptyMessage() 等方法时,Handler 会调用 MessageQueue 的 enqueueMessage() 方法把消息加入到消息队列中。消息 Message 并不是真正的队列结构,而是链表结构。MessageQueue 的enqueueMessage() 方法首先会判断消息的延时时间是否晚于当前链表中最后一个结点的发送时间,是的话则把该消息作为链表的最后一个结点。然后 enqueueMessage() 方法会判断是否需要唤醒消息轮询线程,是的话则通过 nativeWake() JNI 方法调用 NativeMessageQueue 的 wake() 方法。NativeMessageQueue 的 wake() 方法又会调用 Native 层 Looper 的 wake() 方法,在 Native 层 Looper 的 wake() 方法中,会通过 write() 系统调用写入一个 W 字符到唤醒事件文件描述符中,这时监听这个唤醒事件文件描述符的消息轮询线程就会被唤醒

消息处理过程也是从 Looper 的 loop() 方法开始的,当 Looper 的 loop() 方法从 MessageQueue 的 next() 中获取到消息时,就会调用 Message 的 targetdispatchMessage() 的方法,Message 的 target 就是发送消息时用的 Handler ,Handler 的 dispatchMessage() 方法首先会判断 Message 是否设置了 callback 回调 ,比如用 post() 方法发送消息时,传入 post() 方法中的 Runnable 就是 Message 的 callback 回调,如果 Message 没有设置 callback ,则 dispatchMessage() 方法会调用 Handler 的 handleMessage() 方法,到这里消息处理过程就结束了。

另外在使用消息 Message 的时候,建议使用 Message 的 obtain() 方法复用全局消息池中的消息。

2. 消息机制初始化流程

消息机制初始化流程就是 Handler、Looper 和 MessageQueue 三者的初始化流程,Handler 的初始化流程比较简单,而 Looper 的初始化流程则是从 prepare() 方法开始的,当 Looper 的 prepare() 方法被调用后,Looper 会创建一个消息队列 MessageQueue ,在 MessageQueue 的构造方法中会调用 nativeInit() JNI 方法初始化 Native 层的消息队列,在 NativeMessageQueue 的构造方法中会创建 Native 层的 Looper 实例,而在 Native 层的 Looper 的构造函数中,则会把唤醒事件的文件描述符监控请求的文件描述符添加到 epoll 的兴趣列表中。

消息机制初始化流程.png

1.1 Handler 初始化流程

Handler 的初始化过程比较简单,这个过程中比较特别的两个点分别是不能在没有调用 Looper.prepare() 的线程创建 Handler以及异步 Handler

Handler 中有好几个构造函数,其中不传 Looper 的构造函数在高版本的 SDK 中已经被声明为弃用了,也就是我们要创建主线程消息处理器的话,就要把 Looper.getMainLooper() 传到 Handler 的构造函数中。

Handler 的构造函数有一个比较特别的一个 async 参数,async 为 true 时表示该 Handler 是一个异步消息处理器,使用这个 Handler 发送的消息会是异步消息,但是这个构造函数没有开放给我们使用,是系统组件自己用的。

HandlerCode.png

1.2 Looper 初始化流程

之所以我们能在 Activity 中直接用 Handler 给主线程发消息 ,是因为 ActivityThread 的主函数 main() 中初始化了一个主线程专用的 Looper ,也正是这个 Looper 一直在轮询主线程要处理的消息。

ActivityThread.png

Looper 的 prepareMainLooper() 方法会调用 prepare() 方法创建一个新的 Looper , prepare() 是一个公共静态方法,如果我们也要开一个新的线程执行一个任务,这个任务也需要放在死循环中执行并等待消息,而我们又不想浪费 CPU 资源的话,就可以通过 Looper.prepare() 来创建线程的 Looper ,也可以直接使用 Android SDK 中 的 HandlerThread ,HandlerThread 内部也维护了一个 Looper。prepare() 方法会把创建好的 Looper 会放在线程局部变量 ThreadLocal 中。

prepare() 方法可以传入一个 quitAllowed 参数,这个参数默认为 true ,用于指定是否允许退出,假如 quitAllowed 为 false 的话,那在 MessageQueue 的 quit() 方法被调用时就会抛出一个非法状态异常。

Looper.png

Looper 的构造函数中创建了 MessageQueue ,下面来看下 MessageQueue 的初始化流程。

1.3 MessageQueue 初始化流程

在 MessageQueue 的构造函数中调用了一个 JNI 方法 nativeInit() ,并且把初始化后的 NativeMessageQueue 的指针保存在 mPtr 中,发送消息的时候要用这个指针来唤醒消息轮询线程。

MessageQueue.png

nativeInit() 方法中调用了 NativeMessageQueue 的构造函数,在 NativeMessageQueue 的构造函数中创建了一个新的 Native 层的 Looper ,这个 Looper 跟 Java 层的 Looper 没有任何关系,只是在 Native 层实现了一套类似功能的逻辑。

NativeMessageQueue 的构造函数中创建完 Looper 后,会通过 setForThread() 方法把它设置给当前线程,这个操作类似于把 Looper 放到 ThreadLocal 中。

NativeMessageQueue.png

在 Native 层的 Looper 的构造函数中,创建了一个新的唤醒事件文件描述符(eventfd)并赋值给 mWakeEventFd 变量,这个变量是一个唤醒事件描述符,然后再调用 rebuildEpollLocked() 方法重建 epoll 实例,新的事件文件描述符的初始值为 0 ,标志为 EFD_NONBLOCKEFD_CLOEXEC ,关于什么是文件描述符和这两个标志的作用在后面会讲到。

NativeLooper.png

rebuildEpollLocked() 方法的实现如下,关于什么是 epoll 后面会讲到,在 rebuildEpollLocked() 方法的最后会遍历请求列表,这个请求列表中的请求有很多地方会添加,比如输入分发器 InputDispatcherregisterInputChannel() 方法中也会添加一个请求到 Native 层 Looper 的请求列表中。

rebuildEpollLocked().png

1.4 Unix/Linux 体系架构

由于 eventfd 和文件描述符都是 Linux 中的概念,所以下面来看一些 Linux 相关的知识。

Linux 体系架构.png

Linux 操作系统的体系架构分为用户态内核态(用户空间和内核空间),内核本质上看是一种软件,控制着计算机的硬件资源,并提供上层应用程序运行的环境。

而用户态就是上层应用程序的活动空间,应用程序的执行,比如依托于内核提供的资源,包括 CPU 资源、存储资源、I/O 资源等,为了让上层应用能够访问这些资源,内核必须为上层应用提供访问的接口,也就是系统调用

系统调用是受控的内核入口,借助这一机制,进程可以请求内核以自己的名义去执行某些动作,以 API 的形式,内核提供有一系列服务供程序访问,包括创建进程、执行 I/O 以及为进程间通信创建管道等。

1.5 文件描述符

Linux 继承了 UNIX 一切皆文件 的思想,在 Linux 中,所有执行 I/O 操作的系统调用都以文件描述符指代已打开的文件,包括管道(pipe)、FIFO、Socket、终端、设备和普通文件,文件描述符往往是数值很小的非负整数,获取文件描述符一般是通过系统调用 open() ,在参数中指定 I/O 操作目标文件的路径名。

通常由 shell 启动的进程会继承 3 个已打开的文件描述符:

  • 描述符 0 :标准输入,指代为进程提供输入的文件

  • 描述符 1 :标准输出,指代供进程写入输出的文件

  • 描述符 2 :标准错误,指代进程写入错误消息或异常通告的文件

文件描述符(File Descriptor) 是 Linux 中的一个索引值,系统在运行时有大量的文件操作,内核为了高效管理已被打开的文件会创建索引,用于指向被打开的文件,这个索引就是文件描述符

1.6 事件文件描述符 eventfd

eventfd 可以用于线程或父子进程间通信,内核通过 eventfd 也可以向用户空间发送消息,其核心实现是在内核空间维护一个计数器,向用户空间暴露一个与之关联的匿名文件描述符,不同线程通过读写该文件描述符通知或等待对方,内核则通过该文件描述符通知用户程序。

在 Linux 中,很多程序都是事件驱动的,也就是通过 select/poll/epoll 等系统调用在一组文件描述符上进行监听,当文件描述符的状态发生变化时,应用程序就调用对应的事件处理函数,有的时候需要的只是一个事件通知,没有对应具体的实体,这时就可以使用 eventfd

与管道(pipe)相比,管道是半双工的传统 IPC 方式,两个线程就需要两个 pipe 文件,而 eventfd 只要打开一个文件,而文件描述符又是非常宝贵的资源,linux 的默认值也只有 1024 个。eventfd 非常节省内存,可以说就是一个计数器,是自旋锁 + 唤醒队列来实现的,而管道一来一回在用户空间有多达 4 次的复制,内核还要为每个 pipe 至少分配 4K 的虚拟内存页,就算传输的数据长度为 0 也一样。这就是为什么只需要通知机制的时候优先考虑使用 eventfd 。

eventfd 提供了一种非标准的同步机制,eventfd() 系统调用会创建一个 eventfd 对象,该对象拥有一个相关的由内核维护的 8 字节无符号整数,它返回一个指向该对象的文件描述符,向这个文件描述符中写入一个整数会把该整数加到对象值上,当对象值为 0 时,对该文件描述符的 read() 操作将会被阻塞,如果对象的值不是 0 ,那么 read() 会返回该值,并将对象值重置为 0 。

struct eventfd_ctx {
    struct kref kref;
    wait_queue_head_t wqh;
    __u64 count;
    unsigned int flags;
    int id;
};

eventfd_ctx 结构体是 eventfd 实现的核心,其中 wqhcountflags 的作用如下。

wqh 是等待队列头,所有阻塞在 eventfd 上的读进程挂在该等待队列上。

count 是 eventfd 计数器,当用户程序在一个 eventfd 上执行 write 系统调用时,内核会把该值加在计数器上,用户程序执行 read 系统调用后,内核会把该值清 0 ,当计数器为 0 时,内核会把 read 进程挂在等待队列头 wqh 指向的队列上。

有两种方式可以唤醒等待在 eventfd 上的进程,一个是用户态 write ,另一个是内核态的 eventfd_signal ,也就是 eventfd 不仅可以用于用户进程相互通信,还可以用作内核通知用户进程的手段。

在一个 eventfd 上执行 write 系统调用,会向 count 加上被写入的值,并唤醒等待队列中输入的元素,内核中的 eventfd_signal 函数也会增加 count 的值并唤醒等待队列中的元素。

flags 是决定用户 read 后内核的处理方式的标志,取值有EFD_SEMAPHOREEFD_CLOEXECEFD_NONBLOCK三个。

EFD_SEMAPHORE表示把 eventfd 作为一个信号量来使用。

EFD_NONBLOCK 表示该文件描述符是非阻塞的,在调用文件描述符的 read() 方法时,有该标志的文件描述符会直接返回 -1 ,在调用文件描述符的 write() 方法时,如果写入的值的和大于 0xFFFFFFFFFFFFFFFE ,则直接返回 -1 ,否则就会一直阻塞直到执行 read() 操作。

EFD_CLOEXEC 表示子进程执行 exec 时会清理掉父进程的文件描述符。

3. 事件轮询 epoll

selectpollepoll都是 I/O 多路复用模型,可以同时监控多个文件描述符,当某个文件描述符就绪,比如读就绪或写就绪时,则立刻通知对应程序进行读或写操作,select/poll/epoll 都是同步 I/O ,也就是读写是阻塞的。

1. epoll 简介

epoll 是 Linux 中的事件轮询(event poll)机制,是为了同时监听多个文件描述符的 I/O 读写事件而设计的,epoll API 的优点有能高效检查大量文件描述符支持水平和边缘触发避免复杂的信号处理流程灵活性高四个。

当检查大量的文件描述符时,epoll 的性能延展性比 select() 和 poll() 高很多

epoll API 支持水平触发边缘触发,而 select() 和 poll() 只支持水平触发,信号驱动 I/O 则只支持边缘触发。

epoll 可以避免复杂的信号处理流程,比如信号队列溢出时的处理。

epoll 灵活性高,可以指定我们想检查的事件类型,比如检查套接字文件描述符的读就绪、写就绪或两者同时指定。

2. 水平触发与边缘触发

Linux 中的文件描述符准备就绪的通知有水平触发边缘触发两种模式。

水平触发通知就是文件描述符上可以非阻塞地执行 I/O 调用,这时就认为它已经就绪。

边缘触发通知就是文件描述符自上次状态检查以来有了新的 I/O 活动,比如新的输入,这时就要触发通知。

3. epoll 实例

epoll API 的核心数据结构称为 epoll 实例,它与一个打开的文件描述符关联,这个文件描述符不是用来做 I/O 操作的,而是内核数据结构的句柄,这些内核数据结构实现了记录兴趣列表维护就绪列表两个目的。

这些内核数据结构记录了进程中声明过的感兴趣的文件描述符列表,也就是兴趣列表(interest list)

这些内核数据结构维护了处于 I/O 就绪状态的文件描述符列表,也就是就绪列表(ready list),ready list 中的成员是兴趣列表的子集。

4 epoll API 的 4 个系统调用

epoll API 由以下 4 个系统调用组成。

epoll_create() 创建一个 epoll 实例,返回代表该实例的文件描述符,有一个 size 参数,该参数指定了我们想通过 epoll 实例检查的文件描述符个数。

epoll_creaet1() 的作用与 epoll_create() 一样,但是去掉了无用的 size 参数,因为 size 参数在 Linux 2.6.8 后就被忽略了,而 epoll_create1() 把 size 参数换成了 flag 标志,该参数目前只支持 EPOLL_CLOEXEC 一个标志。

epoll_ctl() 操作与 epoll 实例相关联的列表,通过 epoll_ctl() ,我们可以增加新的描述符到列表中,把已有的文件描述符从该列表中移除,以及修改代表文件描述符上事件类型的掩码。

epoll_wait()用于获取 epoll 实例中处于就绪状态的文件描述符。

5. epoll_ctl()
epoll_ctl.png

epoll_ctl() 用于操作与 epoll 实例相关联的列表,成功返回 0 ,失败返回 -1,的 fd 参数指明了要修改兴趣列表中的哪一个文件描述符的设定,该参数可以是代表管道、FIFO、套接字等,甚至可以是另一个 epoll 实例的文件描述符。

op 参数用于指定要执行的操作,可以选择的值如下。

EPOLL_CTL_ADD 表示把描述符添加到 epoll 实例 epfd 的兴趣列表中。

EPOLL_CTL_MOD 表示修改描述符上设定的事件。

EPOLL_CTL_DEL 表示把文件描述符从 epfd 的兴趣列表中移除。

6. epoll_wait()
epoll_wait.png

epoll_wait() 方法用于获取 epoll 实例中处于就绪状态的文件描述符,其中参数 timeout 就是 MessageQueue 的 next() 方法中的 nextPollTimeoutMillis ,timeout 参数用于确定 epoll_wait() 的阻塞行为,阻塞行为有如下几种。

  • -1 :调用将一直阻塞,直到兴趣列表中的文件描述符有事件产生,或者直到捕捉到一个信号为止
  • 0 :执行一次非阻塞式检查,看兴趣列表中的文件描述符上产生了哪个事件
  • 大于 0 :调用将阻塞至 timeout 毫秒,直到文件描述符上有事件发生,或者捕捉到一个信号为止
7. epoll 事件

下面是几个调用 epoll_ctl() 时可以在 ev.events 中指定的位掩码,以及由 epoll_wait() 返回的 evlist[].events 中的值。

  • EPOLLIN:可读取非高优先级的数据
  • EPOLLPRI:可读取高优先级的数据
  • EPOLLRDHUP:套接字对端关闭
  • EPOLLOUT:普通数据可写
  • EPOLLET:采用边缘触发事件通知
  • EPOLLONESHOT:在完成事件通知后禁用检查
  • EPOLLERR:在错误时发生
  • EPOLLHUP:出现挂断

4. 消息轮询过程

1. 消息轮询过程概述

消息循环过程主要是由 Looper 的 loop() 方法、MessageQueue 的 next() 方法、Native 层 Looper 的 pollOnce() 这三个方法组成。

消息轮询过程是从 Looper 的 loop() 方法开始的,loop() 方法中有一个死循环,死循环中会调用 MessageQueue 的 next() 方法,获取到消息后,loop() 方法就会调用 Message 的 target 的 dispatchMessage() 方法分发消息,target 其实就是最初发送 Message 的 Handler 。loop() 方法最后会调用 recycleUnchecked() 方法回收处理完的消息。

在 MessageQueue 的 next() 方法中,首先会调用 nativePollOnce() JNI 方法检查队列中是否有新的消息要处理,没有时线程就会被阻塞。有的话就会尝试找出需要优先执行的异步线程,没有异步消息的话,就会判断消息是否到了要执行的时间,是的话就返回给 Looper 处理,否则重新计算消息的执行时间。

2. Looper.loop()

前面讲到了在 ActivityThread 的 main() 函数中会调用 Looper 的 loop() 方法让 Looper 开始轮询消息,loop() 方法中有一个死循环,死循环中会调用 MessageQueue 的 next() 方法获取下一条消息,获取到消息后,loop() 方法就会调用 Message 的 target 的 dispatchMessage() 方法,target 其实就是发送 Message 的 Handler 。最后就会调用 Message 的 recycleUnchecked() 方法回收处理完的消息。

loop().png
3. MessageQueue.next()

在 MessageQueue 的 next() 方法中,首先会调用 nativePollOnce() JNI方法检查队列中是否有新的消息要处理,如果没有,那么当前线程就会在执行到 Native 层的 epoll_wait() 时阻塞。如果有消息,而且消息是同步屏障,那就会找出或等待需要优先执行的异步消息。调用完 nativePollOnce() 后,如果没有异步消息,就会判断当前消息是否到了要执行的时间,是的话则返回消息给 Looper 处理,不是的话就重新计算消息的执行时间(when)。在把消息返回给 Looper 后,下一次执行 nativePollOnce() 的 timeout 参数的值是默认的 0 ,所以进入 next() 方法时,如果没有消息要处理,next() 方法中还可以执行 IdleHandler。在处理完消息后,next() 方法最后会遍历 IdleHandler 数组,逐个调用 IdleHandler 的 queueIdle() 方法。

下图是 MessageQueue 中找出异步消息后的链表变化。

MessageQueue 异步消息处理机制.png

光看 next() 方法的代码的话会觉得有点绕。ViewRootImpl 的 scheduleTraversals() 方法在很多地方都会被调用,当 scheduleTraversals() 方法被调用时,ViewRootImpl 就会调用 MessageQueuepostSyncBarrier() 方法插入一个同步屏障到消息链表中,然后再调用 ChoreographerpostCallback() 方法执行一个 View 遍历任务 ,然后再调用 MessageQueue 的 removeSyncBarrier() 方法移除同步屏障。Choreographer 的 postCallback() 方法会调用 postCallbackDelayedInternal() 方法,postCallbackDelayedInternal() 方法会调用 scheduleFrameLocked() 方法,scheduleFrameLock() 方法会从消息池中获取一条消息,并调用 Message 的 setAsynchronous() 方法把这条消息的标志 flags 设为异步标志 FLAG_ASYNCHRONOUS,然后调用内部类 FrameHandlersendMessageAtFrontOfQueue() 方法把异步消息添加到队列中。

scheduleFrameLocked().png

下面是 MessageQueue 的 next() 方法的具体实现代码。

MessageQueue.next().png

IdleHandler 可以用来做一些在主线程空闲的时候才做的事情,通过 Looper.myQueue().addIdleHandler() 就能添加一个 IdleHandler 到 MessageQueue 中,比如下面这样。

addIdleHandler().png

当 IdleHandler 的 queueIdle() 方法返回 false 时,那 MessageQueue 就会在执行完 queueIdle() 方法后把这个 IdleHandler 从数组中删除,下次不再执行。

4. Looper.pollOnce()(Native 层)

继续往下看。在 NativeMessageQueuepollOnce() 方法中,会调用 Native 层的 Looper 的 pollOnce() 方法。

NativeMessageQueuePollOnce.png

在 Looper 的 pollOnce() 方法中,首先会遍历了响应列表,如果响应的标识符(identifier)ident 值大于等 0 ,则返回标识符,响应是在 pollInner() 方法中添加的。

NativeLooperPollOnce.png
6. Looper.pollInner() (Native 层)

在 pollInner() 方法中,首先会调用 epoll_wait() 获取可用事件,获取不到就阻塞当前线程,否则遍历可用事件数组 eventItems ,如果遍历到的事件的文件描述符是唤醒事件文件描述符 mWakeEventFd ,则调用 awoken()方法 唤醒当前线程。然后还会遍历响应数组信封数组,这两个数组是在 Native 层消息机制里用的,和我们上层用的关系不大,这里就不展开讲了。

LooperPollInner.png

awoken() 方法的实现很简单,只是调用了 read() 方法把 mWakeEventFd 的数据读取出来,mWakeEventFd 是一个 eventfd ,eventfd 的特点就是在读的时候它的 counter 的值会重置为 0

awoken().png

4. 消息发送机制

当我们用 Handler 的 sendMessage()sendEmptyMessage()post() 等方法发送消息时, 最终都会走到 Handler 的 enqueueMessage() 方法。Handler 的 enqueueMessage() 又会调用 MessageQueue 的 enqueueMessage() 方法。

![sendMessage()](/Users/oushaoze/Documents/Projects/giteePages/assets/images/Android 消息机制/sendMessage().png)

MessageQueue 的 enqueueQueue() 方法的实现如下。enqueueMessage() 首先会判断,当没有更多消息消息不是延时消息消息的发送时间早于上一条消息这三个条件其中一个成立时,就会把当前消息作为链表的头节点,然后如果 IdleHandler 都执行完的话,就会调用 nativeWake() JNI 方法唤醒消息轮询线程。

如果把当前消息作为链表的头结点的条件不成立,就会遍历消息链表,当遍历到最后一个节点,或者发现了一条早于当前消息的发送时间的消息,就会结束遍历,然后把遍历结束的最后一个节点插入到链表中。如果在遍历链表的过程中发现了一条异步消息,就不会再调用 nativeWake() JNI 方法唤醒消息轮询线程。

![enqueueMessage()](/Users/oushaoze/Documents/Projects/giteePages/assets/images/Android 消息机制/enqueueMessage().png)

nativeWake() 的实现如下,只是简单调用了 Native 层 Looper 的 wake() 方法。

nativeWake().png

Native 层 Looper 的 wake() 方法的实现如下,TEMP_FAILURE_RETRY 是一个用于重试,能返回 EINTR 的函数 ,write() 方法会向唤醒事件文件描述符写入一个 W 字符,这个操作唤醒被阻塞的消息循环线程 。

LooperWake.png

5. 消息处理过程

消息处理过程是从 Looper 的 loop() 方法开始的,当 Looper 从 MessageQueue 中获取下一条要处理的消息后,就会调用 Message 的 target 的 dispatchMessage() 方法,而 target 其实就是发送消息的 Handler 。

LooperLoop().png

设置 Message 的 target 的地方就是在 HandlerenqueueMessage() 方法中。

HandlerEnqueueMessage.png

在 Handler 的 dispatchMessage() 方法中,如果消息是通过 post() 方法发送,那么 post() 传入的 Runnable 就会作为 msg 的 callback 字段。如果 callback 字段不为空,dispatchMessage() 方法就会调用 callback 的 run() 方法 ,否则调用 Handler 的 callback 或 Handler 本身的 handleMessage() 方法,Handler 的 callback 指的是在创建 Handler 时传入构造函数的 Callback

dispatchMessage.png

6. 消息 Message

下面我们来看下 Message 的实现。Message 中的 what消息的标识符。而 arg1arg2objdata 分别是可以放在消息中的整型数据Object 类型数据Bundle 类型数据when 则是消息的发送时间

sPool全局消息池,最多能存放 50 条消息,一般建议用 Message 的 obtain() 方法复用消息池中的消息,而不是自己创建一个新消息。如果在创建完消息后,消息没有被使用,想回收消息占用的内存,可以调用 recycle() 方法回收消息占用的资源。如果消息在 Looper 的 loop() 方法中处理了的话,Looper 就会调用 recycleUnchecked() 方法回收 Message 。

Message.png

参考资料

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容