目录
退出Looper循环移除Message的两种方式
大家都知道,消息机制在Android系统运行中扮演着重要的角色,通过消息发送、添加消息队列、分发等一整个流程驱动Android的运行。
主线程是在ActivityThread.main()
中调用了Looper.loop()
,开启消息循环遍历执行的,这个消息循环可以退出吗,接下来我们仔细研究下;
上源码:
void quit(boolean safe) { //1.不允许退出就抛出异常 if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { return; } //2. mQuitting = true; if (safe) { //3.安全退出 removeAllFutureMessagesLocked(); } else { //4.非安全退出 removeAllMessagesLocked(); } nativeWake(mPtr); } }
1.对于主线程而言,mQuitAllowed
的值是false,也就是说主线程的Looper循环不允许手动调用quit()
退出,否则就抛出异常;
2.将退出标识mQuitting
置为true,这样当从消息队列中取消息时,会先判断下这个标识mQuitting
是否为true,是就会经过调用链一步步退出Looper
消息循环;
3.安全的退出Looper
开启的消息循环,深入下removeAllFutureMessagesLocked()
看下:
private void removeAllFutureMessagesLocked() { final long now = SystemClock.uptimeMillis(); Message p = mMessages; if (p != null) { //1. if (p.when > now) { removeAllMessagesLocked(); } else { Message n; for (;;) { n = p.next; if (n == null) { return; } if (n.when > now) { break; } p = n; } p.next = null; do { p = n; n = p.next; p.recycleUnchecked(); } while (n != null); } } }
- 首先如果消息队列队头的消息的执行
时间戳when
大于当前时间,则直接调用removeAllMessagesLocked()
方法移除所有的消息 ,这个方法之后会讲解; - 上面条件不满足,就不断的遍历消息队列,直到找出
执行时间戳大于当前时间的消息
,然后通过do-while()
循环,将该消息及之后的消息全部进行回收处理,放入到我们之前讲解的对象池;
4.非安全的退出是直接调用了removeAllMessagesLocked()
方法,我们深入看下:
private void removeAllMessagesLocked() { Message p = mMessages; while (p != null) { Message n = p.next; p.recycleUnchecked(); p = n; } mMessages = null; }
可以看到这种移除方法大杀特杀,不会去比较消息执行的时间戳啥的,直接全部干翻回收到消息对象池,简单粗暴。
这里对于非安全和安全退出Looper
循环做个总结:
安全退出Looper循环只会移除回收大于当前时间戳的消息,而不大于当前时间戳的消息都可以保证正常执行;而非安全的退出比较粗暴,直接清空回收整个消息队列。这两种情况大家根据需要选择性的使用。
removeXXXMessages()移除指定的消息
可以看到移除消息的方法一大堆,比如通过指定Message
的what
、obj
、callback
等信息移除指定Message
,这里我们就以removeCallbacksAndMessages()
举例。
removeCallbacksAndMessages()
方法大家应该很梳理,是我们在某个界面中使用Handler
发送消息时,避免发生内存泄漏的一种方式,接下来我们深入分析下:
void removeCallbacksAndMessages(Handler h, Object object) { //... synchronized (this) { Message p = mMessages; //1.从头移除消息 while (p != null && p.target == h && (object == null || p.obj == object)) { Message n = p.next; mMessages = n; p.recycleUnchecked(); p = n; } //2. 从中间移除消息 while (p != null) { Message n = p.next; if (n != null) { if (n.target == h && (object == null || n.obj == object)) { Message nn = n.next; n.recycleUnchecked(); p.next = nn; continue; } } p = n; } } }
如果这个方法传入的object
不为null,就会移除指定的Message
,如果指定为null,就会移除传入的Handler
发送的所有消息。
上面的源码中可以看到,移除Message
分为两个部分,为什么要这么做呢?
假设消息队列中存在下面一系列消息集合:
如果Message1
满足移除条件,那么直接回收这条消息,并将消息队列的队头指针指向下一个消息即可mMessages = mMessages.next
,对应上面源码中前半部分移除消息的逻辑。
但假设Message1
不满足移除条件,Message2
满足移除条件,这样移除就不是直接将消息队列的队头指针指向next
即下一个Message
就能简单解决的。
正确的做法是:先要保存Message1
,然后通过Message2.next
获取到Message3
的引用保存起来,最后将Message1.next
指向上面保存的Message3
引用。这部分就对应上面源码中后半部分移除消息的逻辑。
总结
本篇文章主要是对MessageQueue
提供的各种移除Message
的方法做了一个简单的介绍,方法很多主要分为两种:移除指定标识的Handler
发送的Message
和移除所有Handler
发送的Message
。