浅析Android消息机制Handler
本文尝试对Handler及其相关组件在Java层进行简单分析。
最初学习Android开发时,经常会碰到使用Handler切换线程来更新UI的代码。而现在,大家似乎更喜欢用其它方式去实现多线程的切换。即便如此,作为Android Framework架构的一个基础控件,Handler机制依旧有其学习价值。
Handler由以下部分组成:
它们之间的运作方式可以以可视化的方式展示为:
接下来会对各个组件进行分析。
Looper
Looper 是在线程内维持 Message 循环的一个类。
我们一般在主线程里声明 Handler,而有时我们需要在其它线程里声明 Handler,若与在主线程里一样直接声明的话会抛异常,原因是主线程(ActivityThread
)已经调用了 Looper 的prepareMainLooper()
和loop()
方法。(实际开发中用HandlerThread即可,没有必要自己创建关联 Looper 的线程)
我们来分析 Looper 的两个主要方法prepare()
和loop()
:
Looper.prepare()
1 | private static void prepare(boolean quitAllowed) { |
可以看出,每个线程只能绑定一个Looper实例,prepare()
方法通过ThreadLocal
实现 Looper 实例与线程间的绑定,
通过观察 Looper 构造函数:
1 | private Looper(boolean quitAllowed) { |
可以得知,在 Looper 实例化时会创建一个MessageQueue
对象以及持有当前线程的引用。
Looper.loop()
1 | public static void loop() { |
Looper 对象不能为空,也即loop()
方法的调用必须放在prepare()
方法后。
loop()
方法里,Looper 会不断地MessageQueue.next()
方法从 MessageQueue 对象里的取出 Message,然后通过代码msg.target.dispatchMessage(msg)
让 Message 所绑定的 Handler 执行dispatchMessage()
方法来处理 Message。
Handler
Handler 可以在线程绑定的 MessageQueue 中传递和处理 Message。
Handler 有多个构造函数,篇幅所限,这里只分析默认构造方法:
1 | public Handler() { |
构造方法里获取了线程绑定的 Looper 实例,又通过该 Looper 实例获取了其中的 MessageQueue 实例。
同一线程里的多个Handler实例分享同一个 MessageQueue 实例,因为他们分享同一个 Looper 实例。
一般我们通过调用方法sendMessage()
及其变式来把 Message 添加到 MessageQueue的末尾,据分析方法源码可知,无论是sendMessage()
还是post()
其最终调用的都是Handler.enqueueMessage()
方法:
1 | private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { |
从代码可以得知上面 Looper 里的 loop()
方法里代码msg.target.dispatchMessage(msg)
中的 target 即是传递 Message 时绑定的 Handler 对象。
再来看看dispatchMessage()
这个方法:
1 | public void dispatchMessage(Message msg) { |
可以看到 Handler 有三种方式来处理 Message,依次为:
- 由用
Handler.post()
方式传递 Runnable 包装成的 Message 里的 Runnable 自行处理。 - 由 Handler 构造函数传入的 Callback 处理。
- 由 Handler 的
handleMessage()
方法处理。
Message
Message 是容纳任意数据的容器。
Message 一般通过调用obtain()
或者obtainMessage()
方法来获取实例,因为 Message 里维护了一个存储了 Message 对象最大值为50的 LinkedList 集合。
在 Looper 的loop()
方法里,当 Handler 完成对 Message 的处理后,会调用Message.recycleUnchecked()
方法来重置 Message。
MessageQueue
MessageQueue 是一个将被 Looper 分发的 Message 链表集合。
上面 Handler 里提到的将 Message 添加到 MessageQueue 的 Handler.enqueueMessage()
方法最终调用的是MessageQueue.enqueueMessage()
方法:
1 | boolean enqueueMessage(Message msg, long when) { |
以较为直观的层次(不涉及native层方法)来解释上面的逻辑:
- 判断 Message 是否指定 Handler
- 判断 Message 是否已经插入到链表中
- 判断 MessageQueue 是否在退出状态
- 退出状态抛出异常,回收 Message
- 非退出状态
- 把 Message 标记为已插入,为 Message 附上入参的时间戳
- 判断 Message 相对链表的位置
- 置于链表头部
- 比较并置于链表相应位置里
- 判断 MessageQueue 是否在退出状态
水平所限,MessageQueue 其它部分读不懂,故先分析到这里。
内存泄漏
当直接在主线程里直接声明 Handler 时,IDE 的 Lint 提醒代码可能会造成内存泄漏。
泄漏的原因
所有使用 Handler 发送的 Message 都会调用到 Handler.enqueueMessage()
方法,正如上面分析的,在该方法里,Handler 的引用被显式地赋予了msg.target
,用于 Looper 对应 MessageQueue 里的 Message 从 MessageQueue 出列时能选择相对应的 Handler 进行处理。
Message 添加到 MessageQueue 后,MessageQueue 就持有了 Message 的引用。而 MessageQueue 又被跟程序有相同生命时长的主线程 Looper 所引用。因此,Message 中持有的 Handler 会一直持续到 Message 被 MessageQueue 回收。
虽然 Handler 有很长的存活时间,但不能确定 Activity 是否发生了泄漏。要检查是否发生了泄漏,我们需要确定 Handler 是否在类里持有了 Activity 的引用。若 Handler 是以一个非静态内部类的形式声明于类中,那么便会发生泄漏,因为非静态内部类对其外部类持有一个隐式引用。
解决泄漏
通过组合使用弱引用和静态修饰符可防止 Activity 内存泄漏。当 Activity 被销毁时,弱引用会让 GC 能回收你依旧持有的 Activity 引用。而在 Handler 内部类前添加静态修饰符可以避免持有外部类的隐式引用。
由于应用了弱引用,在使用 Actvity 引用时要格外小心,因为有可能已经被 GC 回收了,不妨先加个判断条件:
1 | if (activity == null || activity.isFinishing() || activity.isDestroyed()) { |