Android应用是事件驱动的,也可以说是Message来驱动的。每个进程中都有一个默认的消息队列MessageQueue,其维护了一个待处理的消息列表,Looper不断地从中取出消息、处理消息。此时我们不禁会抱有一个疑问,在应用运行期间,系统岂不是会不断地创建Message、处理Message、销毁Message?答案当然是否定的,Android作为一个成熟的系统平台,自然不会轻易地采用大量重复构建这种低效耗能的运作方式,本篇文章我们就来了解一下Message。
Message
首先看文档对Message的描述:
Defines a message containing a description and arbitrary data object that can be sent to a Handler. This object contains two extra int fields and an extra object field that allow you to not do allocations in many cases.
While the constructor of Message is public, the best way to get one of these is to call Message.obtain() or one of the Handler.obtainMessage() methods, which will pull them from a pool of recycled objects.
其大概意思是,Message类被用于发送给Handler,其可以存储一些描述信息和任意数据类型的对象。Message有两个额外的int字段和一个额外object字段,可以满足很多情况下的需求配置。虽然Message的构造方法是public的,但是创建Message最好的方法还是去调用Message.obtain()方法或者调用一系列Handler.obtainMessage()的方法,这些方法会从一个回收池中去获取Message对象。
/** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
*/
public Message() {}
从文档中我们可以大致了解到,Android的消息机制并不会大量重复创建Message对象,而是重复利用在消息池中已存在的Message对象。从Handler类源码中我们可以看到,Handler中所有需要构建Message对象的地方,包括一系列Handler.obtainMessage()的方法,其实质上都是通过Message.obtain来获得的。
在开始分析obtain方法之前我们先来看看Message类中几个重要的字段:next字段的注释我们可以发现Message的消息池实质上就是个链表,而这个next指向下一个Message对象;sPoolSync是一个普通Object对象,用于在获取Message对象时进行同步锁;mPool字段就是一个Message对象,mPool是链表中的第一个Message对象;sPoolSize字段表示的是消息池已有Message的数量;MAX_POOL_SIZE表示消息池的最大容纳数量。
// 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.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并不是在创建时而是在回收时被添加到消息池中的。
命令模式
Android消息机制实质上是一个非典型的命令模式,所以这里简单说一下命令模式。
定义:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
命令模式中主要有三种角色:
Receiver:接收者角色,命令的最终执行者。
Command:命令角色,需要执行的所有命令都是在这里声明的。
Invoker:调用者角色,接受命令并执行命令。
命令模式通用类图
命令模式的优点:
类间解耦,将调用者角色与接收者角色实现解耦,调用者实现功能时只需调用Command抽象类的execute方法就可以,不需要了解到底是哪个接收者执行;可扩展性好,Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合。
命令模式的缺点:
命令模式主要的缺点就是Command类膨胀的问题,如果有N个命令,Command的子类不是几个而是N个。
使用场景:
需要抽象出待执行的动作,然后以参数的形式提供出来——类似于过程设计中的回调机制,而命令模式正是回调机制的一个面向对象的替代品。
在不同的时刻指定、排列和执行请求。一个命令对象可以有与初始请求无关的生存期。
需要支持取消操作。
支持修改日志功能,这样当系统崩溃时,这些修改可以被重做一遍。
需要支持事务操作。
回到Android消息机制,上边说到Android消息机制是一个非典型的命令模式,其分工如下:
接收者:Handler,执行消息处理操作;
调用者:Looper,调用消息的的处理方法;
命令角色:Message,消息类。
在简单了解了命令模式之后,相信大家也就理解了为什么Android消息机制为什么会使用命令模式了。不难发现,Android消息机制的接收者和调用者之间是存在依赖的,似乎与命令模式相违和,但仔细一想倒也无可厚非,设计模式的存在意义就是为了使代码的更易于维护和扩展,之前说过接收者和调用者实现解耦,其最大的好处就是让未知对象之间的调用关系更加灵活,而Android消息机制中的接收者、调用者和命令角色三者角色固定而明确,自然也不必需要这个扩展性,命令角色的单一还解决了命令模式的类膨胀问题。
这就是本篇文章的全部内容了,本人的能力和水平有限,难免会出现错误,如有发现还望大家不吝指正。
参考书籍:《Android源码设计模式解析与实战》
网友评论