Android消息机制之Message缓存回收机制解析

Message

一、官方对这个类的注释:

/**
 * Defines a message containing a description and arbitrary data object that can be
 * sent to a {@link Handler}.  This object contains two extra int fields and an
 * extra object field that allow you to not do allocations in many cases.
 *
 * <p class="note">While the constructor of Message is public, the best way to get
 * one of these is to call {@link #obtain Message.obtain()} or one of the
 * {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
 * them from a pool of recycled objects.</p>
 */

翻译:

定义一条消息,其中包含可以发送给Handler的描述和任意数据对象。 该对象包含两个额外的int字段和一个额外的对象字段,使您在许多情况下不进行分配(避免重复使用)。
虽然Message的构造函数是公共的,但获取其中之一的最佳方法是调用Message.obtain()或Handler.obtainMessage()方法之一,这将从回收对象池中拉出它们。

二、成员变量解析:

// 消息的唯一标识符,用来区分不同Handler的相同消息(防止混淆);一般用16进制来表示。
public int what;

// Message类的可选变量,当我们只需要放简单的整型值时就可以直接赋给这俩个变量而不用去setData或设置obj。
public int arg1;
public int arg2;

// Message携带的任意数据类型的对象,并且这个对象包含Parcelable类的时候,它必须是非空的。对于其他数据的传输,建议使用setData()方法
public Object obj;

    // 回复跨进程的Messenger 
    public Messenger replyTo;

    // Messager发送这的Uid
    public int sendingUid = -1;

    // 正在使用的标志值 表示当前Message 正处于使用状态,当Message处于消息队列中、处于消息池中或者Handler正在处理Message的时候,它就处于使用状态。
    /*package*/ static final int FLAG_IN_USE = 1 << 0;

    // 异步标志值 表示当前Message是异步的。
    /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;

    // 消息标志值 在调用copyFrom()方法时,该常量将会被设置,其值其实和FLAG_IN_USE一样
    /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;

    // 消息标志,上面三个常量 FLAG 用在这里
    /*package*/ int flags;

    // 用于存储发送消息的时间点,以毫秒为单位
    /*package*/ long when;

    // 用于存储比较复杂的数据
    /*package*/ Bundle data;
    
    // 用于存储发送当前Message的Handler对象,前面提到过Handler其实和Message相互持有引用的
    /*package*/ Handler target;
    
    // 用于存储将会执行的Runnable对象,前面提到过除了handlerMessage(Message msg)方法,你也可以使用Runnable执行操作,要注意的是这种方法并不会创建新的线程。
    /*package*/ Runnable callback;
 
    // 指向下一个Message,也就是线程池其实是一个链表结构
    /*package*/ Message next;

    // 该静态变量仅仅是为了给同步块提供一个锁而已
    private static final Object sPoolSync = new Object();

    //该静态的Message是整个线程池链表的头部,通过它才能够逐个取出对象池的Message
    private static Message sPool;

    // 该静态变量用于记录对象池中的Message的数量,也就是链表的长度
    private static int sPoolSize = 0;
  
    // 设置了对象池中的Message的最大数量,也就是链表的最大长度
    private static final int MAX_POOL_SIZE = 50;

     //该版本系统是否支持回收标志位
    private static boolean gCheckRecycle = true;

三、获取Message对象

首先肯定看构造器,非常的朴实如华:

在这里插入图片描述

再来看提供的静态获取Message对象的方法:

在这里插入图片描述

有8个obtain方法可以获取到Message对象,这也是官方推荐使用的方法;我们先来给个编号方便跟踪:

public static Message obtain()public static Message obtain(Message orig)public static Message obtain(Handler h)public static Message obtain(Handler h, Runnable callback)public static Message obtain(Handler h, int what)public static Message obtain(Handler h, int what, Object obj)public static Message obtain(Handler h, int what, int arg1, int arg2)public static Message obtain(Handler h, int what, int arg1, int arg2, Object obj)

四、Message的消息对象池和无参的obtain()方法

既然官方推荐使用obtain方法获取Message对象,那么就来看一看这个方法到底长什莫样子:

1.obtain():

/**
 * Return a new Message instance from the global pool. Allows us to
 * avoid allocating new objects in many cases.
 *
 * 翻译:从消息对象池中返回一个新的Message实例,这样可以避免重复创建冗余的对象
 */
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();
}

这个方法设计的成员变量有sPoolnextflagssPoolSize,老规矩先来瞅一眼这几个变量的含义:

sPool:是个Message对象;单看名字,理解为“池”,消息对象池;其实就是指向复用消息的头指针(后面有配图说明);

next:是个Message对象;单看名字,理解为“下一个”,其实就是尾指针,通过这个next来连接俩个Message对象的;

flags:三种消息标记;

sPoolSize:消息对象池的大小。

好了,变量解读完毕,来看看这个方法干了啥子:首先判断sPool是不是空的,是空直接new Message()返回,尼玛,官方不是不推荐new Message()么,仔细想想,既然官方不推荐,那么大部分情况下,sPool是不为空的,我们来找下这个变量到底在什么时候被赋值的:

在这里插入图片描述

经发现有2个地方给sPool赋值了,一个是上面的obtain()方法,另一个是recycleUnchecked()方法。这就好办了,来看一下这个方法的实现。

2.recycleUnchecked():

/**
 * Recycles a Message that may be in-use.
 * Used internally by the MessageQueue and Looper when disposing of queued Messages.
 * 
 * 翻译:回收可能正在使用的消息。 在处理排队的消息时,由MessageQueue和Looper内部使用。
 */
@UnsupportedAppUsage
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 = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;
	// 同步锁保证线程安全
    synchronized (sPoolSync) {
    
    
        if (sPoolSize < MAX_POOL_SIZE) {
    
    
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

这个方法是回收消息,将回收的消息放到消息对象池中。接下来将obtain()recycleUnchecked()方法结合到一起探讨一下消息对象池。

3.消息对象池

综合一下obtain()recycleUnchecked()理解消息对象池和这个玩意到底是怎么回收消息的:

void recycleUnchecked() {
    
    
                ...
        if (sPoolSize < MAX_POOL_SIZE) {
    
    
                // ⑴
                next = sPool;
                 // ⑵
                sPool = this;
                 // ⑶
                sPoolSize++;
                 ...
         }
    }

public static Message obtain() {
    
    
    synchronized (sPoolSync) {
    
    
        // Ⅰ
        if (sPool != null) {
    
    
            // Ⅱ
            Message m = sPool;
            // Ⅲ
            sPool = m.next;
            // Ⅳ
            m.next = null;
            // Ⅴ
            m.flags = 0; 
            // Ⅵ
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

ok,标记玩顺序,我们来走一遍obtain()recycleUnchecked()的同步代码块:

假设一开始消息对象池为空,那么会从new Message()开始,这条消息被取出使用完毕准备回收 → recycleUnchecked()

next = sPool,一开始消息对象池为空,所以sPool为空,即next为空;这里是将sPool指向的对象赋值给in-use对象的next
sPool = thisthis表示的是当前的Message作为消息对象池中下一个被复用的对象;
sPoolSize++,默认为0,此时进入消息,+1。

我们来画图理解一下,假设之前第一个Message对象假设为m_1,假设有消息(m_2)需要进入对象池,再次调用recycleUnchecked():
在这里插入图片描述

再将流程简化一下,其实就是有消息进入消息对象池,sPool指针左右移动:

在这里插入图片描述

这样就能回收消息了,也就是链表的恢复,而且都是保证线程同步的,所以始终会形成一条链式结构,直到sPoolSize == 50最大值。

OKK,假设上面已经回收好一个Message对象,我们通过obtain()获取了一个message,会走什么样的流程呢?

Ⅰ. 判断头指针sPool是否不为空,显然这里的sPool已经不为空了;

Ⅱ. Message m = sPool;,从对象池中取出一个Message对象赋给m

Ⅲ. sPool = m.next;,将消息对象池的下一个可复用的对象(m.next)赋给sPool(对象池中的当前Message对象,如果之前池里就一个,那么此时sPool == null);通俗讲就是从池中拿一个对象出来复用嘛,把它从链中断开,那么头指针要一直指着下一个可复用的对象;

Ⅳ. m.next = null;,断开俩个消息之间的链接,通俗讲就是把连接2个消息的"next"剪断,让他们不再有瓜葛;

Ⅴ. m.flag = 0,打上“正在使用”标记;

Ⅵ. sPoolSize--;,取出一个消息那肯定减少一个单位的长度咯。

老规矩,画图理解:

在这里插入图片描述

这个图应该还是容易理解的,那么这个in-use对象使用完毕了,就会通过recycleUnchecked()回收。那么这个recycleUnchecked()是在哪调用的呢?打开Message源码,在recycle()方法中调用的:
在这里插入图片描述

至此,obtain()recycleUnchecked()方法解读完成。

五、obtain的其他7个有参方法

1.方法功能:

public static Message obtain(Message orig):从消息对象池中获取一个Message对象m,然后把orig中的所有属性赋值给m;

public static Message obtain(Handler h):从消息对象池中获取一个Message对象m,然后将m的target重新赋值;

public static Message obtain(Handler h, Runnable callback):从消息对象池中获取一个Message对象m,然后将m的target和m的callback重新赋值;

public static Message obtain(Handler h, int what):从消息对象池中获取一个Message对象m,然后将m的target和m的what重新赋值 ;

public static Message obtain(Handler h, int what, Object obj):从消息对象池中获取一个Message对象m,然后将m的target、what和obj这三个成员变量重新赋值而已 ;

public static Message obtain(Handler h, int what, int arg1, int arg2):从消息对象池中获取一个Message对象m,然后将m的target、what、arg1、arg2这四个成员变量重新赋值 ;

public static Message obtain(Handler h, int what, int arg1, int arg2, Object obj):从消息对象池中获取一个Message对象m,然后将m的target、what、arg1、arg2、obj这五个成员变量重新赋值 。

2.总结:

有参的obtain方法第一行都是先调用obtain()获取一个Message对象,只不过有参的开源通过传入参数来重置一些成员变量而已。

六、Message的缓存回收机制——享元模式

Android的消息机制都通过Message这个载体进行传递消息,如果每次我们都通过"new"这样的方式获取对象,那么必然会造成内存占用率高,降低性能。通过对其源码的学习,明白Message的缓存回收机制,其实这就是享元模式的体现。

猜你喜欢

转载自blog.csdn.net/C_biubiubiu/article/details/113558765