一、什么是享元模式
1、介绍
享元模式,即FlyWeight,旨在复用对象,避免重复、大量创建对象,从而节省系统资源的消耗
享元模式介绍:https://www.cnblogs.com/adamjwh/p/9070107.html
享元模式实践:https://www.jianshu.com/p/b925b8cb6494
二、Message中的实现
我们知道,Android是事件驱动机制,会有各种各样的事件组合而成应用的显示与交互,也涉及到线程之间的通信,而这些,我们都需要用到Message,通过Message封装一个对象,描述了事件的类型、参数、处理者等内容。由此可见,随着事件的持续不断,或者并发,Message是会有大量对象创建生成的,而如果频繁大量创建对象,会给内存带来严重的消耗,频繁触发GC等,影响应用性能,所以使用享元模式,对message对象进行复用,不必要每次都创建新的对象。
1、对象获取
Message对象获取方式有两种,一种是直接通过构造方法new一个新的对象,一种是使用obtain方法获取一个对象,官方推荐是使用obtain,为啥,因为它是从对象池中获取对象,通过复用机制确保不会有大量对象产生;而new的话就是每次都生成一个新的对象了。
我们来看obtain的实现:
/**
* 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();
}
看完的话,大概有这么一个逻辑,如果满足sPool不为空,则返回sPool作为一个Message对象,否则创建一个新的对象,那么这个sPool是什么?m.next又是什么?flags又是什么?sPoolSize呢?
ok,带着这些疑问,我们去Message的全局变量找:
// sometimes we store linked lists of these things
/*package*/ Message next;
/** @hide */
public static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
OK,我们看到,sPool、next都是Message类型的,再想想,是不是像个链表!是的,其实就是维护了一个链表,用于存储这些被回收的Message对象,使用时再从链表表头取出。
大致结构可以参考上图,sPool类似于指针的作用,next指向下一个可以被复用的message对象,如果没有可用对象,则指向null。同样,sPool初始值也是指向null引用,此时链表中是没有任何对象的,也就是会进入上面代码中的return new Message();
,直接创建新对象返回。
我们还看到有一个 sPoolSize 字段和 MAX_POOL_SIZE 字段,比较容易可以理解,一个表示当前链表的长度,一个表示最大长度。所以上面代码中obtain一个对象之后,size–。
获取对象后,还有一个设置flags = 0,这是为啥呢?其实就是个标记,标记这个对象现在已经有人用啦,这个标记后续还会用到,回收的时候会判断该对象是否正在被使用。
2、对象保存
上面我们看到,有这么一个链表,存储着提供被复用的message对象,那这些对象什么时候插入到链表中的呢?简单的享元模式中,我们是在new了一个对象之后,以内部状态为key,对象为value,保存到map中了,而这里创建对象的时候并没有保存啊?
OK,继续查看,找到了关键位置:recycle()
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
这里可以看到,刚才说的标记在这里用到了,首先会先去判断是否可用,可以被回收,不行的话直接return或者抛出异常,能回收则进入回收程序。
/**
* 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;
//类似于reset,将各个变量状态重置
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
//插入到链表表头
//链表长度限制为50
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
这一段代码可以分为两部分,第一部分重置这些个属性,毕竟复用嘛,不给个全新的也得给个空的是吧,不能给个有内容的对象,那样就出问题了。第二部分就是插入到链表中,可以看到这就是插入到链表表头的操作,将当前sPool指向的message对象给到next,再将sPool指向当前对象,最后size++。
sPool是指向当前链表表头的,这个理解就OK了。
回收
那这个recycle方法的调用时机呢?什么时候回收?
方法上按住Ctrl,发现了一堆调用该方法的地方,但其实我们关注几个地方就好了。
Message msg = mHandler.obtainMessage(MSG_DISABLE, state1, state2, animate);
if (Looper.myLooper() == mHandler.getLooper()) {
// If its the right looper execute immediately so hides can be handled quickly.
mHandler.handleMessage(msg);
msg.recycle();
} else {
msg.sendToTarget();
}
这里可以看到,在handleMessage方法调用之后,调用了recycle方法。
if (Looper.myLooper() == mMainLooper) {
mCallback.executeMessage(msg);
msg.recycle();
return;
}
callback回调执行完后,调用recycle方法。
3、总结
Message这个享元复用,Message类承担了享元抽象、享元对象、享元工厂类多个角色,但其实也蛮好理解:内部维护了一个链表存储至多50个对象,使用obtain获取对象时,从表头取出一个对象或者新建一个对象,回收对象时,将该对象属性重置存储到链表中。