Handler 消息机制二:Message 源码分析,带你从源码层面去理解复用机制

该篇主要介绍的是Handler 消息机制中的Message 类。Message 源码分析,带你从源码层面去解读Handler 机制中的Message 类,了解Message 类是如何实现消息复用的?这里面涉及到的是什么设计模式?了解 Message 类的正确使用姿势 。

Message 类即是消息的载体,将要传递的数据打包进Message 对象进行发送,在Handler 的 handleMessage(Message msg) 方法中接收发送的Message 对象

消息的获取


问:在Handler 的使用过程中,Message有几种创建方式?哪种效果更好,为什么?

new一个Message实例(不推荐)

使用 Message 默认的构造函数可以非常简单的创造出一个Message 实例。

Message message = new Message();

虽然Message的构造函数是公共的,我们可以通过new 关键字来自行创建一个Message 对象,但是系统更推荐我们使用 {Message.obtain()}或{Handler.obtainMessage()}相关的方法获取对象。在Message 的类注释和构造函数注释上均已说明。

    /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
    */
    public Message() {
    }

通过Message的obtain相关的静态方法(推荐)

Message的obtain相关的静态方法

通过Handler的obtain 相关的方法(推荐)

android.os.Handler#obtainMessage()
android.os.Handler#obtainMessage(int)
android.os.Handler#obtainMessage(int, java.lang.Object)
android.os.Handler#obtainMessage(int, int, int)
android.os.Handler#obtainMessage(int, int, int, java.lang.Object)

而当我们使用Handler 获取一个Message 对象时,其内部调用的同样也是Message 的相关方法。以android.os.Handler#obtainMessage()方法为例:

/**
 * Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
 * creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
 *  If you don't want that facility, just call Message.obtain() instead.
 */
public final Message obtainMessage()
{
    return Message.obtain(this);
}

当我们在创建一个Message 时,系统推荐使用{Message.obtain()}或{Handler#obtainMessage.obtainMessage()} 系列的方法获取一个Message 对象,不过不管使用哪种形式的方法,最后都会执行到 android.os.Message#obtain() 这个方法,而不同参数的重载方法无非是给我们在使用的过程中提供一些便捷而已。

结构


// 用户定义的消息码,以便收件人可以识别此消息的含义
public int what;

// 系统给我们提供的两个用来做数据传递的简单类型
public int arg1;
public int arg2;

 // 要发送给接收者的数据对象。
public Object obj;

// 触发时间,在执行入队操作时根据提供的时间来对消息进行排序、插队
/*package*/ long when;

// 目标Handler,即哪个Handler对此消息进行负责,发送消息和处理响应
/*package*/ Handler target;

// Message 是一个消息链表,持有下一个节点的引用
/*package*/ Message next;

// 锁对象,用来维持线程同步的
public static final Object sPoolSync = new Object();
// 对象缓存池,用于存放回收的Message,使用享元模式来节省资源开销。其实现方式采用的是链表的形式。
private static Message sPool;
// 当前缓存的可用Message 数量
private static int sPoolSize = 0;
// 最大缓存对象数
private static final int MAX_POOL_SIZE = 50;

复用机制(享元模式)


Message 内部是如何进行优化的呢?

Message 内部有一个全局的缓存池 sPool,用于保存回收的Message 对象。它是一个链表的结构,其 next 属性指向下一个Message 缓存对象,以这样的方式把所有缓存的Message 对象都串在了一起。这种缓存池的实现方式也正是享元模式的一种。

obtain() 方法内部,会先从缓存池中获取Message 对象,如果没有则再去创建一个新消息返回。在消息转发完成后,调用 recycleUnchecked() 方法对使用的消息进行资源释放、回收,插入到缓存池中以备再用,节约因频繁创建对象带来的内存开销。

从缓存池获取消息

/**
 1. Return a new Message instance from the global pool. Allows us to
 2. 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)本质上是一个消息链表,当调用obtain()方法时,检查缓存池中是否有可用的Message 对象

  1. 有可用的Message 对象时,即 sPool !=null ,从缓存池中取出一个Message 对象。
    a. 把sPool 赋值给一个的对象;
    b. 将持有的next 对象指向 sPool,成为缓存池新的链表头。
    c. 把新的message 的 next 置为null , 因为message 还持有下一个节点的引用,也就意味着把新的 Message 断链,形成一个独立的消息对象。
    d. 缓存池的可用数量减1
  2. 无可用的Message 对象时,即 sPool ==null ,new 一个新的Message实例对象返回

消息回收

void recycleUnchecked() {
    // 1.清除所有的特征信息。
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    // 2.插入到缓存池中进行回收,循环使用
    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            // 把当前对象当做对象池的链表头
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

该方法的目的就是回收Message 对象,以供循环使用。在处理排队的消息时,recycleUnchecked() 方法由MessageQueue和Looper内部使用。在Loop的loop() 方法中,在消息发送完毕后,会调用 msg.recycleUnchecked() 来回收消息。

recycleUnchecked() 方法内部可以分为2个步骤:

  1. 清除Message 的属性信息。因为message 携带了很多特定的信息(比如消息标识、执行时间、携带数据、目标Handler等等),而这些信息是属于具体消息的,对于下一个消息来说并不适用,所以在回收之前需要先把这些特定信息清掉。
  2. 添加到回收的对象缓存池中。这里是把要回收的对象放到缓存链表的头部。

猜你喜欢

转载自blog.csdn.net/JM_beizi/article/details/106236162
今日推荐