Android 系统用 Binder 机制进行进程通信,用 Handler 进行线程通信,本文从 Message 的生命流程看懂 Handler 框架。
1、什么是 Handler 框架?
Handler 框架是 Android 用于线程通信的一种机制,采用消息队列的形式。主要包括消息队列(MessageQueue)、消息发送和处理器(Handler)、消息驱动器(Looper) 以及消息载体(Message)。
2、Handler 是怎样的工作流程?
通过 Handler 将一个 Message 发送给 MessageQueue,Looper 会不断的从 MessageQueue 中取,然后发送给相关联的 Handler 去处理。如上图。
1)Message 的获取是比较自由,可以自己 new,也可以 Message.obtain。Message 内部有一个 static pool,用于重用消息。
2,3)发送消息的方式非常多,最终都会通过 Handler.enqueueMessage 方法将消息添加到消息队列中。在这个过程中 Handler 会为 Message 设置执行时间(when)、处理器(target,就是当前 Handler) 两个关键属性。
4)MessageQueue 会根据 Message.when 在内部队列的排序,将 Message 插在一个合适的位置。MessageQueue 内部是使用单链表的形式实现的队列,便于插入和删除,Message.next 属性。
5,6,7)Looper 是一个驱动器,loop 方法会死循环调用 MessageQueue.next 方法,从消息队列中取消息,然后分发到消息的处理器(Message.targe)。
8,9)至此,这一次通信就完成了,消息会被回收进入池子。
3、延时执行是如何实现的?
1)Message 有一个 when 属性记录消息执行的时间;
2)MessageQueue.next 在取消息的时候,如果发现第一个消息的执行时间还没到或者没有消息可执行,就会阻塞,调用 nativePollOnce 进行休眠,这里会传递一个休眠时间。休眠结束或者有新的消息来到,都会结束休眠,继续取消息。
3)设置 when 属性和 next 方法比较时间的时候,使用的是 SystemClock,这个类记录系统从启动到现在的时间,他不受用户修改时间的影响。
4、线程通信是如何实现的?
每一个线程只会有一个 MessageQueue 和 Looper,他们可以关联非常多的 Handler。你可以在线程 A 通过关联线程 B 的 Handler 发送消息,即可完成通信。Handler 有一个构造方法可以指定 Looper,你只需创建 Handler 的时候指定想要通信线程的 Looper 即可。默认情况下,会使用当前线程关联的 Looper。
这里就要保证每个线程只有一个 Looper,其内部是使用 ThreadLocal 实现。Looper.prepare 方法会先检查当前线程是否已经有了 Looper(通过 ThreadLocal.get),如果没有,创建一个,并保存到 ThreadLocal 中。
5、子线程如何使用 Handler 框架?
Handler 能够工作,主要是当前线程的 Looper 及 MessageQueue 准备好了,所以子线程如果想使用 Handler,调用 Looper.prepare 和 Looper.loop 方法,让当前线程 Looper 和 MessageQueue 工作就好。
6、Android 主线程为什么没有因为 loop 死循环卡死?
谢谢 @Gityuan 老师 的答案。
其实死循环并不会卡死线程,这是属于正常执行的一部分。所谓“卡死”其实是指 UI 卡顿,造成这个问题其实是因为 Handler 执行的某个任务(Message)时间太长,导致后面更新 UI 的任务被阻塞了。这也是为什么不在主线程执行耗时操作的原因。
由此引出的一个问题是为什么只能在主线程更新 UI?根本原因是 View 操作是非线程安全的,如果多线程就会造成 UI 错乱。所以解决线程安全有两个方向,一是将 View 写成线程安全,二是在一个线程里执行。
7、其他
1)消息队列主要使用了生产者消费者的设计模式,因此消息队列(MessageQueue)保证了线程安全。
2)附上一篇非常好的 Handler 源码解析文章。
感谢 @Ruheng 老师 的博客。