该文章属于Android Handler系列文章,如果想了解更多,请点击Android Handler机制之总目录
前言
在前面的文章中我们讲解了Handler、Looper、MessageQueue的具体关系,了解了具体的消息循环的流程。下面将一起来探讨最为整个消息循环的消息载体Message。
Message中可以携带的信息
Message中可以携带的数据比较丰富,下面对一些常用的数据进行了分析。
1 | /** |
其中关于what的值为什么不会冲突的原因是,之前我们讲过的handler是与线程进行绑定的。也就是说不同消息循环消息的发送,处理的线程是不一样的。当然是不会冲突的。对于Messenger,因为涉及到Binder机制,这里就不过多的描述了,有兴趣的小伙伴可以自行查询相关资料学习。
创建消息的方式
官方建议使用Message.obtain()系列方法来获取Message实例,因为其Message实例是直接从Handler的消息池中获取的,可以循环利用,不必另外开辟内存空间,效率比直接使用new Message()创建实例要高。其中具体创建消息的方式,我已经为大家分好类了。具体分类如下:
1 | //无参数 |
其中在Message的obtain带参数的方法中,内部都会调用无参的obtain()方法来获取消息后。然后并根据其传入的参数,对Message进行赋值。(关于具体的obtain方法会在下方消息池实现原理中具体描述)
消息池实现原理
既然官方建议使用消息池来获取消息,那么在了解其内部机制之前,我们来看看Message中的消息池的设计。具体代码如下:
1 | private static final Object sPoolSync = new Object();//控制获取从消息池中获取消息。保证线程安全 |
从Message的消息池设计,我们大概能看出以下几点:
- 该消息池在同一个消息循环中是共享的(sPool声明为static),
- 消息池中的最大容量为50,
- 从消息池获取消息是线程安全的。
从消息池中获取消息
在上文中,我们已经知道了在使用消息池获得消息时,都会调用无参的obtain()方法。具体代码如下:
1 | public static Message obtain() { |
从上述代码中,我们可以了解,也就是当前 消息池不为空(sPool !=null)的情况下,那么我们就可以从消息池中获取数据,相应的消息池中的消息数量会减少。消息池的内部实现是以链表的形式,其中spol指针指向当前链表的头结点,从消息池中获取消息是以移除链表中sPool所指向的节点的形式,具体原理如下图所示:
回收消息到消息池
在Meaage的消息回收中,消息的实际回收方法是recycleUnchecked()方法,具体如下图所示:
1 | void recycleUnchecked() { |
在recycleUnchecked()方法中,大致分为三步,第一步将该条回收的消息状态设置为正在使用,第二步将Message所有的存储信息都变为初始值,第三步,如果当前消息池仍能够存储回收的消息,那么就将消息存储在消息池中。其中将回收消息加入消息池中是使用链表的形式,具体回收消息到消息池如下图所示:
Message 消息回收时机
这里为了方便大家梳理逻辑,我提前将几种会调用消息进行回收的情况都描述出来了,具体的情况如下所示:
当Handler指定删除单条消息,或所有消息的时候
1 | void removeMessages(Handler h, int what, Object object) |
当使用Handler删除某条消息的时候,会分别调用MessageQueue的
- removeMessages(Handler h, int what, Object object)
- removeCallbacksAndMessages(Handler h, Object object)
- removeMessages(Handler h, Runnable r, Object object)
这三个方法逻辑比较类似。这里直接选取removeCallbacksAndMessages()方法来进行讲解。具体代码如下:
1 | void removeCallbacksAndMessages(Handler h, Object object) { |
在removeCallbacksAndMessages(Handler h, Object object)方法中,在该方法中分别进行了两次循环,肯定有很多读者朋友会很好奇,为什么这里会进行两次循环呢?下面我就具体来讲解一下。
我们都知道,在Handler机制中,多个handler对应同一个MessageQueue对应同一个Looper,Handler与MessageQueue与Looper之间的关系是N:1:1
。也就是说在MessageQueue中我们可以有多个不同Handler发送的Message。那么我们再结合上面的代码,我们来分析这两次循环。
第一次循环
根据上文对代码的理解,第一次循环会将MessageQueue中,当前Handler发送的所有消息移除,注意!!!!!!!!!这里并不会将整个MessageQueue中的当前Handler发送的消息全部移除
,而是在遍历过程中,如果有其他Handler发送的消息,那么就会将mMessages指向头结点并跳出循环。如下图所示:
第二次循环
经过上文的分析,我们已经知道了,在进行第一次循环后,已经将在removeCallbacksAndMessages方法执行时所有对应的Handler发送的消息移除掉了,但是MessageQueue中可能任然会残留没有移除掉的消息。那么第二次循环,根据代码来理解的话,我们可以得到下图:
所以为了保证将整个MessageQueue中该Handler发送的消息全部被移除,在第一次循环移除之后,我们必须要再执行一次循环移除操作
。
ps:非常感谢@承香墨影指出的错误的地方,之前没有考虑到同步的关系,误导了大家。内心感到无比的愧疚。
当Loooper取出消息时
1 | public static void loop() { |
我们都知道消息的取出是通过Looper类中的loop方法。从代码中我们可以看出,当消息取出并执行相应操作后。最后会将消息回收。
当Looper取消循环消息队列的时候
1 | public void quitSafely() { mQueue.quit(true);} |
当退出消息队列的时候,也就是调用Loooper的quitSafely()或quit()方法,从代码中我们可以看出,会调用其内部的MessageQueue的quit(boolean safe)方法。我们继续跟踪代码。
1 | void quit(boolean safe) { |
在MessageQueue的quit(boolean safe)方法中,会将mQuitting (用于判断当前消息队列是否已经退出)置为true,同时会根据当前是否安全退出的标志 (safe)来走不同的逻辑,如果安全则走removeAllFutureMessagesLocked()方法,如果不是安全退出则走removeAllMessagesLocked()方法。下面分别对这两个方法进行讨论。
非安全退出
1 | private void removeAllMessagesLocked() { |
非安全退出其实很简单,就是将所有消息队列中的消息全部回收。具体示意图如下所示:
安全退出
1 | private void removeAllFutureMessagesLocked() { |
观察上诉代码,在该方法中,会判断当前消息队列中的头消息的时间是否大于当前时间,如果大于当前时间就会removeAllMessagesLocked()方法(也就是回收全部消息),反之,则回收部分消息,同时没有被回收的消息任然可以被取出执行。具体示意图如下所示:
当消息队列退出的,但是仍然发送消息过来的时候
在Looper调用quit()方法时,也就是Looper退出消息循环的时候,我们已经知道了其内部会调用MessageQueue的quit(boolean safe)方法。当MessageQueue退出的时候,会将mQuitting置为true。那么当对应的Handler发送消息时,我们都知道会调用MessageQueue的enqueueMessage(Message msg, long when)方法。那么现在我们观察下列代码:
1 | boolean enqueueMessage(Message msg, long when) { |
观察该代码我们得知,当循环消息退出的时候,如果这个时候Handler继续发送消息来。会将该消息回收。但是现在这里有个问题。既然我们的消息队列已经结束循环了。那么我们回收该消息又有什么用呢?我们又不能重新的开启消息循环。不知道Google这里为什么会这么设计。
总结
- 在使用Handler发消息时,建议使用Message.obtin()方法,从消息池中获取消息。
- 在Message中消息池是使用链表的形式来存储消息的。
- 在Message中消息池中最大允许存储50条的消息。
- 在使用Handler移除某条消息的时候,该消息有可能会被消息池回收。(会判断消息池是否仍然能存储消息)