Handler机制之Message介绍
提到handler消息机制,必然少不了要介绍Handler中最重要的消息载体Message
1,Message可以携带的信息
-
以下是直接对外暴露的属性
/** * 用户定义的消息代码,以便当接受到消息是关于什么的。其中每个Hanler都有自己的命名空间,不用担心会冲突 */ public int what; /** * 如果你只想存很少的整形数据,那么可以考虑使用arg1与arg2, * 如果需要传输很多数据可以使用Message中的setData(Bundle bundle) */ public int arg1; /** * 如果你只想存很少的整形数据,那么可以考虑使用arg1与arg2, * 如果需要传输很多数据可以使用Message中的setData(Bundle bundle) */ public int arg2; /** * 发送给接受方的任意对象,在使用跨进程的时候要注意obj不能为null */ public Object obj; /** * 在使用跨进程通信Messenger时,可以确定需要谁来接收 */ public Messenger replyTo; /** * 在使用跨进程通信Messenger时,可以确定需要发消息的uid */ public int sendingUid = -1;
-
以下是没有对外直接暴露的属性
/** * 如果数据比较多,可以直接使用Bundle进行数据的传递 */ Bundle data; /*package*/ static final int FLAG_IN_USE = 1 << 0; /** If set message is asynchronous */ /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1; /** Flags to clear in the copyFrom method */ /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE; /** * 消息的标记 * 1,是否使用 * 2,是否是异步消息 * 3, */ /*package*/ int flags; /** * 消息正在需要被处理的事件,是从开机到需要被处理的时间 * 并不是时间戳 */ /*package*/ long when; /** * 发送和要处理消息的Handler对象 */ /*package*/ Handler target; /** * Handler发送和要处理的Runable对象 */ /*package*/ Runnable callback; /** * 当前消息的下一个对象 */ // sometimes we store linked lists of these things /*package*/ Message next;
2,Message消息的实现方式
-
直接new Message()来实现,这种方式虽然可以实现,但是效率不高,因为如果Handler需要发送和处理消息过多,频率较高的情况下,可能出现频繁的创建和销毁Message对象,严重的情况下可能造成内存问题
基于此Android为我们提供了第二种实现方式
实现Message的第二种方式就是使用Message.obtain()方法,内部是在handler的消息池中获取message实例,好处就是可以循环利用,不必另外开辟内存空间,效率比第一种方式直接创建实例要高
public static final Object sPoolSync = new Object();
private static Message sPool;
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
从上方我们可以看出,
- 消息池的内部是以链表的形式实现的
- 获取消息的方式是以移除链表头sPool所指向的节点的形式
3,Handler消息池的实现原理
private static final Object sPoolSync = new Object();//控制获取从消息池中获取消息。保证线程安全
private static Message sPool;//消息池
private static int sPoolSize = 0;//消息池中回收的消息数量
private static final int MAX_POOL_SIZE = 50;//消息池最大容量
从Message的消息池设计,我们大概能看出以下几点:
- 该消息池在同一个消息循环中是共享的(sPool声明为static),
- 消息池中的最大容量为50,
- 从消息池获取消息是线程安全的。
4,回收消息池
void recycleUnchecked() {
//用于表示当前Message消息已经被使用过了
//清除之前Message的数据
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
//判断当前消息池中的数量是不是小于最大数量,其中 MAX_POOL_SIZE=50
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
//增加线程池数量
sPoolSize++;
}
}
}
5,Handler的消息回收机制
虽然Handler.remove消息的方式很多,但是最终都是使用了MessageQueue的移除消息方式
public final void removeCallbacksAndMessages(Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}
接下来我们看下mQueue.removeCallbacksAndMessages的实现
void removeCallbacksAndMessages(Handler h, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
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;
}
}
}
从上面我们可以看到,Handler是使用了俩次循环从消息链表中移除消息的
- 第一次是将当前Handler发送的所有消息从消息链表中移除,注意::这里并不会将整个MessageQueue中的当前Handler发送的消息全部移除,而是在遍历过程中如果有其他的handler发送的消息则跳出循环,将mMessages指向当前头节点
- 第二次是从第一次之后的头节点开始依次循环链表,找出当前Handler发送的消息,然后移除
至于为什么会要俩次循环移除消息呢?
在Handler机制中,多个Handler对应一个Looper和MessageQueue,它们三者的比例是N:1:1,所以说MessageQueue中有多个不同Handler发送的Message
并不能保证当移除消息的时候,对应的Handler就不继续发送消息了,也就是说该Handler发送的消息仍然会被添加到MessageQueue中,所以为了保证将整个MessageQueue中该Handler发送的消息全部被移除,在第一次循环移除之后,我们必须要再执行一次循环移除操作。
6,总结
- 在使用Handler发消息时,建议使用Message.obtin()方法,从消息池中获取消息。
- 在Message中消息池是使用链表的形式来存储消息的。
- 在Message中消息池中最大允许存储50条的消息。
- 在使用Handler移除某条消息的时候,该消息有可能会被消息池回收。(会判断消息池是否仍然能存储消息)