Message对象复用(Android中的享元模式)

一、什么是享元模式

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对象,使用时再从链表表头取出。

image

大致结构可以参考上图,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获取对象时,从表头取出一个对象或者新建一个对象,回收对象时,将该对象属性重置存储到链表中。

猜你喜欢

转载自blog.csdn.net/lizebin_bin/article/details/91381542