Android Handler - tenji/ks GitHub Wiki

Android 中 Handler 机制原理

一、Handler 作用

handler 是 android 线程之间的消息机制,主要的作用是将一个任务切换到指定的线程中去执行,(准确的说是切换到构成 handler 的 looper 所在的线程中去出处理)android 系统中的一个例子就是主线程中的所有操作都是通过主线程中的 handler 去处理的。

二、Handler 架构

Handler 的运行需要底层的 messagequeue和 looper 做支撑。

三、Handler 原理

3.1 messagequeue

首先说 messagequeue,messagequeue 是 一 个 消 息 队 列 , 它是采用单链表的数据结构来存储消息的,因为单链表在插入删除上 的效率非常高。(Meaasgequeue 主要包含一个是插入消 息的 enqueuemessage 方法,和一个取出一条消息的 next 方法。)

3.2 Looper

然后说 looper,looper 在安卓的消息机制中是扮演着消息调度的角色。

Looper 取消息的过程是这样的:

  1. 判断队头消息的执行时间是否大于当前时间,如果大于,就调用 nativePollOnce 阻塞一段时间(队头消息的执行时间 - 当前时间)然后取出队头消息进行执行;
  2. 否则就立即取出队头消息进行执行;
  3. 如果队列中没有消息,就一直阻塞,直到下一个消息来到,才唤醒取消息的线程继续上述循环。

3.3 Handler

最后说 Handler。

四、主线程中的 Handler

Android 系统中的一个例子就是主线程中的所有操作都是通过主线程中的 Handler 去处理的。例如 Activity 的生命周期方法调用就是通过主线程中的 Handler 去处理的。

五、主线程的死循环一直运行是否特别消耗 CPU 资源?

这里就涉及到 Linux pipe/epoll 机制,在主线程的 MessageQueue 没有消息时,主线程便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,相当于 Java 层的线程 waite 机制,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达时调用 nativewake,通过往pipe管道写端写入数据来唤醒主线程工作。相当于 Java 层的 notify 机制,去唤醒主线程,然后处理消息,所以主线程大多数时候都是处于休眠状态,并不会消耗大量 CPU 资源。

从 Looper 取消息的过程,可以看出取消息的线程大部分时候处于阻塞状态,不会消耗 cpu 资源。

六、Android 中为什么主线程不会因为 Looper.loop() 里的死循环导致 ANR 卡死?

首先 ANR 和死循环不是一个概念。

  1. 主线程的工作就是处理主线程中的 message,所有需要到主线程执行的操作都是通过主线程的向主线程的 messagequeue 加入一条消息,looper 在适当的时机取出这条消息,来执行的,如果不是一个死循环,那么 loop 取出一条消息,执行完一条消息后,主线程就退出了,正是有这个死循环,它才保证了主线程不会退出,并且能处理队列中所有的消息。所以这个死循环是一个消息处理机制;
  2. 而 ANR 原因是,主线程中 messagequeue 中一个 message 的处理时间过长,导致接下来的某类消息无法处理,比如说一个消息的处理时间超过了 5 秒,导致用户的输入无法响应,才会出现 ANR;
  3. 另外,从 looper 取消息的过程来看,只有当此刻有需要执行的消息时,主线程才将此消息取出来执行,否则就进入休眠状态,释放 cpu。(就算不进入休眠一直循环,如果手机是多核,也是不会卡死的,只是主线程在不停的运行代码,消耗了更多资源)

为什么主线程中会采用死循环呢?

线程是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了。而对于主线程,我们是绝不希望会被运行一段时间就退出,所以采用死循环保证它不会被退出。

七、handler.postDelayed(Runnable r, long delayMillis)

八、如何保证延时消息精确执行?

从 Looper 取消息过程,以及从加入一条新消息的过程看,这两个过程都不存在任何延迟。

九、ThreadLocal

ThreadLocal 可以在多个线程内存储数据,使用 ThreadLocal 存储的数据在多个线程之间是隔离的,因为它是将数据存储在每个线程内的 ThreadLocalMap 中。

十、同步屏障,同步消息,异步消息

同步消息就是我们平时发送的消息,异步消息一般是系统发送的消息。

同步屏障就是给消息队列发送了一个屏障信息(target == null),消息队列在处理到这个屏障信息时就开启了”同步屏障”模式。在该模式下,只返回异步消息给 Looper 处理,屏蔽同步消息。

在处理完异步消息队列后,即使消息队列中还有同步消息也会通过 nativePollOnce() 进入线程阻塞状态。直到有新的异步消息进来。除非解除同步屏障模式,同步消息才能得到处理。

view 的刷新用到了同步屏障,因为界面刷新事件应处在第一优先级。

参考链接