Message(消息)类是通信内容的载体。线程间的通信包含哪些数据、是什么目的、是异步还是同步、要求什么时候执行其中封装的操作等等信息,都是由Message来保存。
1.通信内容 – arg1+arg2+obj 或者 data字段
希望接收通信的线程得到哪些数据,我们就往消息对象中存放哪些数据。在消息类中,提供了低消耗和高消耗两种内容存放方案:
1).低消耗方案使用arg1 + arg2 + obj三个字段来存放数据:其中arg1与arg2是int类型,obj是Object类型。虽然能够存放的数据较少,但同时消耗的资源也更少。适合大多数不需要携带具体数据只更改状态的场景。
2).高消耗方案使用data字段存放数据:其中data字段为Bundle类型。Bundle对象允许我们写入任意的数据,但仍不建议使用Message传递过多数据。当线程需要传递大量数据时,建议将数据存放在共享区域中。
2.通信内容标识 – what字段
在线程间通信的过程中,我们不仅仅需要传递数据,还需要告诉接收数据的线程此次传递的数据是什么。消息类为我们提供了int类型的what字段,通过将自定义的内容标识保存在what字段中,便可以说明此次传递的数据内容。
3.何时处理 – when字段
希望 被处理的时刻。
这个时刻不是用日常生活中的时间表示,为了节省存储,Android用系统从启动直到指定时刻的毫秒数来表示(系统处于深度睡眠状态时不会记录时间)。举个粟子,如果when等于123456,就说明当前消息 希望 在Android系统从启动开始算起的第123456毫秒被执行。这个123456毫秒可能是日常生活中的123456毫秒,也可能是日常生活中的123459毫秒,这取悦于系统深度睡眠时长(在粟子中的睡眠时长是3毫秒)。
当Handler对象post(发布)一条消息时,可以1)设置具体的when值;2)设置相对于post(发布)时刻的延迟时间;3)如果两者都没设置,那么when等于当前时刻。
特别注意,消息被处理的时刻并不一定等于when值指定的时刻。线程阻塞、执行耗时任务、设置SyncBarrier等场景都会影响最终处理时间。
4.谁来处理 – target / callback字段
希望 处理的时刻后,下一步就该考虑最核心的一个问题了:接收线程收到消息后,谁来处理?
在消息类中,有两个可以指定处理程序的字段 — Handler类型的target字段、Runnable类型的callback字段。
首先,无论在上述哪个字段指定处理程序,最后将当前消息对象加入消息队列都要通过Handler实现。这个Handler会在入队之前就被保存在target字段中。然后,入队时会检查消息对象的target字段是否为空,如果为空则入队失败。入队成功后,消息对象会一直保存在消息队列中直到被取出处理或被移除。
当条件允许时,消息对象msg会从消息队列中取出。如果是普通的消息对象那么其target必定不为null,Looper会调用方法消息的target.dispatchMessage(msg)处理消息,其代码如下:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);// 函数体为:message.callback.run();
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在分发msg时,callback.run()优先级最高,mCallback.handleMessage(msg)次之,handleMessage(msg)最低。callback.run()是创建callback时需要实现的方法,handleMessage(msg)是创建Handler时需要重写的方法。而mCallback是一个接口,其代码如下:
public interface Callback {
/**
* @param msg {@link android.os.Message Message}对象
* @return 返回true,如果不需要更深层次的处理。返回false则会继续调用{@link #handleMessage(Message)}
*/
public boolean handleMessage(Message msg);
}
Handler的mCallback字段只能在创建Handler时赋值,其效果等同于Handler的handleMessage(msg)。当一个Handler发布的消息共享同一个处理程序时,推荐使用handleMessage()或者Callback接口来完成Message的处理任务;当一个Handler发布的消息需要不止一种处理程序时,可再结合Runnable类型的callback字段。
5.消息的创建与消息缓存池
在创建一个对象时,比较普遍的方法都是直接使用new关键词。但是相对于创建频率比较高的类对象,反复创建并且在使用后留下一大堆对象等待GC回收是一种很消耗资源的方式。既然创建频率比较高,与其让使用完的对象都等待GC回收,不如直接把它们的内容清空后放在一起,当应用需要创建时直接从其中取出一个对象。这样既避免了反复创建,也减轻了GC的回收压力。
&esmp;Message就采用了这种方式,我们把整个废弃消息集合叫做消息缓存池。消息缓存池设计上很简单但是很巧妙:
/**指向下一个消息(在消息池时才使用,其他情况下为null)**/
Message next;
/**消息缓存池出池入池时,施加的同步对象锁**/
private static final Object sPoolSync = new Object();
/**消息缓存池首部的消息,初始默认值为null*/
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
注意涉及到缓存池的成员变量只有一个消息类型的next字段,其他包括队首sPool,当前缓存量sPoolSize以及缓存用的虚拟同步对象锁sPoolSync等字段都是静态的final变量。这样的设计极大地减少了消息对象需要占用的资源。
我们可以使用Message.obtain()(或者Handler.obtainMessage())系列方法搭配需要的提前指定参数从消息缓存池获得一个空闲消息,使用Message.recycle() 回收处在 未使用 状态的消息。凡是涉及到更改消息缓存池的操作都使用synchronized关键字施加了同步锁,所以我们不需要考虑同步问题。
6.isInUse 与 isAsynchronous – flags字段
Message有两类状态,第一种状态表明消息当前是否正在使用中(isInUse),第二种状态表明消息是异步消息还是同步消息。两类状态使用int型的flags字段表现,flags最低位标记是否正在使用(0-否 | 1-是),次低位标记是否是异步消息(0-否 | 1-是)。
一个消息的使用状态变化通常是这样的:首先使用Message.obtain()获取一个空闲的消息,在消息缓存池没有消息时返回直接创建的消息,在消息缓存池有消息时则会取出一个消息将其状态从“使用中”更改成“未使用”并返回。Handler拿到消息之后,填入各项内容并使用sendMessage/post方法将消息加入消息队列中,此时消息的状态会变为“使用中”。消息在消息队列中等待处理并且在处理后通过Message.recycleUnchecked()方法回收,这个回收方法在回收时不会判断消息是否在使用中。回收完成后,消息内容被重置、状态变成“使用中”,并且从消息队列中移除进入了消息缓存池中。
当Message处于 使用中 时,将其加入MessageQueue会被抛出IllegalStateException 异常a,(API>=21时)调用它的recycle()方法会因为拒绝回收而抛出IllegalStateException 异常b:
IllegalStateException a:This message is already in use.
IllegalStateException b:This message cannot be recycled because it is still in use.
同步消息和异步消息的区别主要是两点。第一点,同步消息自始至终都会按照顺序执行(如果when相同的Message,哪个先入队就先执行哪个),异步消息的执行顺序则完全不确定。第二点,同步消息会同步障碍器拦截而异步消息不会受到影响
Message.markInUse():将当前Message设置为使用中;
Message.isInUse():判断当前Message是否正在使用中;
Message.isAsynchronous():判断当前Message是否是异步消息;
Message.setAsynchronous(boolean):设置当前Message是否是异步消息。
7.消息的回收 – recycle()与recycleUnchecked()
一般情况下并不需要我们手动回收消息,在Looper类处理完消息之后会自动释放消息(不检查状态)。如果您真的需要回收消息,应该先确保消息没有处在”使用中“,然后通过调用recycle()方法在非android.os包下回收消息。recycleUnchecked()方法只能在同一个类包下使用:
/**
* 归还一个消息对象给消息缓存池。
* <p>
* 调用该方法后请不要再使用这个消息对象,因为它很快会进入消息缓存池中并被其他Handler取出用于传递其
* 他的内容。
* @throws IllegalStateException 当试图回收正在使用中的消息时,如果当前Android Api版本大于等于21
* (LOLLIPOP/Android5.0), recycle()方法会抛出异常IllegalStateException,否则只是简单地结束方法。
*/
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
/**
* 回收一个可能还在使用中的消息对象。所谓回收只是将消息的其他字段恢复默认值并设置消息的状态为“使用
* 中”,随后从消息循环中移除放入消息缓存中。 该方法只有同一个包中的类可调用。
* </em>仅在MessageQueue和Looper类需要回收消息时调用。
*/
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;//设置为正在使用中的同步消息
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
//obtain()同样使用了该对象锁
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}