4.4Message深度理解
目录介绍
- 01.先来了解下Message
- 02.设计Message考虑点
- 03.Message是做什么用
- 04.Message如何创建
- 05.看一下几个成员字段
- 06.Message.obtain()分析
- 07.Message的命令模式
01.先来了解下Message
Android应用是事件驱动的
也可以说是Message来驱动的。每个线程中都有一个默认的消息队列MessageQueue,其维护了一个待处理的消息列表,Looper不断地从中取出消息、处理消息。
如何处理Message创建和销毁
此时我们不禁会抱有一个疑问,在应用运行期间,系统岂不是会不断地创建Message、处理Message、销毁Message?
答案当然是否定的,Android作为一个成熟的系统平台,自然不会轻易地采用大量重复构建这种低效耗能的运作方式。本篇文章我们就来了解一下Message。
02.设计Message考虑点
先来了解下Message,如果是你,你会如何设计消息体。那么考虑点有那些?
- 消息体需要能附带传递的内容【可以设计属性object】,发送消息和接受消息处理需要通过code来判断【可以设计属性what】,延迟消息需要附带时间【可以设计属性when】
- 使用handler发送,在处理消息的时候需要知道是那个handler发送的【可以设计属性target】,通过回调处理消息【可以设计属性runnable】
- 当开发者使用handler发送大量消息,系统可以优先处理系统UI级别消息,需要区分同步还是异步消息【可以设计属性flags】
最后,消息使用频率高,而且有时候会很多,比如1000个消息,我肯定希望消息体创建能否做到复用【避免高频创建和销毁】?
那么如何做,这种场景明显就是,如果存在,则重用该对象,而不是创建一个新的对象。如果不存在,则创建一个新的对象并将其添加到共享池中,以供以后使用。利用享元模式!
03.Message是做什么用
Message的作用
大概意思是,Message类被用于发送给Handler,其可以存储一些描述信息和任意数据类型的对象。
Message有两个额外的int字段和一个额外object字段,可以满足很多情况下的需求配置。
虽然Message的构造方法是public的,但是创建Message最好的方法还是去调用Message.obtain()方法或者调用一系列Handler.obtainMessage()的方法,这些方法会从一个回收池中去获取Message对象。
复用对象池
- Android的消息机制并不会大量重复创建Message对象,而是重复利用在消息池中已存在的Message对象。
- 从Handler类源码中我们可以看到,Handler中所有需要构建Message对象的地方,包括一系列Handler.obtainMessage()的方法,其实质上都是通过Message.obtain来获得的。
04.Message如何创建
创建Message对象的几种方式:
- Message msg = new Message();
- Message msg = Message.obtain();
- Message msg = handler1.obtainMessage();
几种方法对比
后两种方法都是从整个Message池中返回一个新的Message实例,能有效避免重复Message创建对象,因此更鼓励这种方式创建Message
Message核心字段含义
m.what = 定义常量,用来发送消息和匹配消息的数值;
m.target = 表示这个消息是从那个handler发出来的
m.arg1 = orig.arg1;
m.arg2 = orig.arg2;
m.obj = 赋值的对象;
m.replyTo = orig.replyTo;
m.sendingUid = orig.sendingUid;
05.看一下几个成员字段
代码如下所示
// sometimes we store linked lists of these things
/*package*/ Message next;
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类中几个重要的字段
- next字段的注释我们可以发现Message的消息池实质上就是个链表,而这个next指向下一个Message对象;
- sPoolSync是一个普通Object对象,用于在获取Message对象时进行同步锁;
- mPool字段就是一个Message对象,mPool是链表中的第一个Message对象;
- sPoolSize字段表示的是消息池已有Message的数量;MAX_POOL_SIZE表示消息池的最大容纳数量。
06.Message.obtain()分析
看看Message.obtain()方法
如果消息链中没有消息时,就会new一个新的Message对象并返回;如果消息链中有可用对象,就会获取到当前链表的mPool,而后将mPool指向下一个Message对象。
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
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();
}
当链表中有可用对象时,obtain方法是从链表中获取Message对象,但是没有对象时,obtain方法中创建的Message又是怎么添加到消息池中的呢?说到这里,不知大家还记不记得在Looper.loop方法中,每一条Message被处理后最终都会调用Message.recycleUnchecked()方法,没错,答案就在这个方法里,我们来看看Message.recycleUnchecked()方法的源码。
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
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;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
- 从代码中可以看出
- recycleUnchecked()方法有两个作用:首先,清空消息状态,同时设置flags = FLAG_IN_USE,表明该消息已被使用,这个flags在obtain()方法中会被设置为0;
- 然后,如果消息池的大小未超过最大容纳数量,就将自身添加到链表的表头。也就是说,Message并不是在创建时而是在回收时被添加到消息池中的。
05.Message的命令模式
- Android消息机制实质上是一个非典型的命令模式,所以这里简单说一下命令模式。
- 定义:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
- 命令模式中主要有三种角色:
- Receiver:接收者角色,命令的最终执行者。
- Command:命令角色,需要执行的所有命令都是在这里声明的。
- Invoker:调用者角色,接受命令并执行命令。
- 命令模式的优点:
- 类间解耦,将调用者角色与接收者角色实现解耦,调用者实现功能时只需调用Command抽象类的execute方法就可以,不需要了解到底是哪个接收者执行;可扩展性好,Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合。
- 命令模式的缺点:
- 命令模式主要的缺点就是Command类膨胀的问题,如果有N个命令,Command的子类不是几个而是N个。
- 使用场景:
- 需要抽象出待执行的动作,然后以参数的形式提供出来——类似于过程设计中的回调机制,而命令模式正是回调机制的一个面向对象的替代品。
- 在不同的时刻指定、排列和执行请求。一个命令对象可以有与初始请求无关的生存期。
- 需要支持取消操作。
- 支持修改日志功能,这样当系统崩溃时,这些修改可以被重做一遍。
- 需要支持事务操作。
- 回到Android消息机制,上边说到Android消息机制是一个非典型的命令模式,其分工如下:
- 接收者:Handler,执行消息处理操作;
- 调用者:Looper,调用消息的的处理方法;
- 命令角色:Message,消息类。
- 为什么Android消息机制为什么会使用命令模式了。
- 不难发现,Android消息机制的接收者和调用者之间是存在依赖的,似乎与命令模式相违和,但仔细一想倒也无可厚非,设计模式的存在意义就是为了使代码的更易于维护和扩展。
- 之前说过接收者和调用者实现解耦,其最大的好处就是让未知对象之间的调用关系更加灵活,而Android消息机制中的接收者、调用者和命令角色三者角色固定而明确,自然也不必需要这个扩展性,命令角色的单一还解决了命令模式的类膨胀问题。