美文网首页Android开发经验谈Android开发Android技术知识
Android 消息机制之 Message 与消息对象池的深入源

Android 消息机制之 Message 与消息对象池的深入源

作者: __Y_Q | 来源:发表于2020-08-21 18:09 被阅读0次

    接上一章的 Looper, 本章开始分析消息机制中的 Message 与消息对象池.

    什么是 Message

    • 一个可以发送给 Handler 的描述和任意数据对象的消息对象, 这个对象包含两个额外的 int 字段和一个额外的对象字段. 这样就可以使用在很多情况下不用做分配工作.
    • 尽管 Message 的构造函数是公开的, 但是获取 Message 的对象最好的方式是调用 Message.obtain() 或者 Handler.obtainMessage(), 因为这样是直接从一个可回收的消息对象池中获取 Message 对象.

    成员变量 what

    • 官方: 它是用户定义 Message 的标识符, 用以分辨消息的内容, Handler 拥有自己消息代码的命名空间, 因此你不用担心与其他的 Handler 冲突.

    成员变量 arg1 与 arg2

    • arg1 与 arg2 都是 Message 的可选变量, 可以用来存放两个整数值, 不用访问 obj 对象就能读取的变量. 如果只是几个整形的数值, 相对于使用 setData() 方法, 使用 arg1/arg2 是较低成本的替代方案.

    成员变量 obj

    • 官方: 将一个独立的对象发给接收者. 当使用 Messenger 去发送消息, 并且这个对象包含 Parcelable 类的时候, 它必须是非空的, 对于其他的数据传输, 建议使用 setData() 方法.

    剩余成员变量

    //回复跨进程的 Messenger
    public Messenger replyTo;
    
    //Messenger 发送的 UID
    public int sendingUid = -1;
    
    //正在使用的标志值,表示当前 Messgae 正处于使用状态,
    //当 Message 处于消息队列中,处于消息池中或者 Handler 正在处理 Messgae 的时候,它就处于使用状态
    /*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;
    
    //消息标志, 上面 3 个常量 FLAG 用在这里.
    /*package*/ int flags;
    
    //用于存储发送消息的时间点, 以毫秒为单位
    /*package*/ long when;
    
    //用于存储比较复杂的数据
    /*package*/ Bundle data;
    
    //用于存储发送当前 Message 的 Hadnler 对象, 说明 Handler 其实和 Message 是相互持有引用的.
    /*package*/ Handler target;
    
    //用于存储将会执行的 Runnable 对象,
    //除了 HandlerMessgae(Message msg) 方法,还可以使用 Runnable 执行操作.要注意的是这种方法并不会创建新的线程
    /*package*/ Runnable callback;
    
    //指向下一个 Messgae, 
    /*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;
    

    1. Message.obtain

    在 Message.java 中, 通过 Message.obtain() 方式来获取 Message 对象的, 一共有 8 个方法.下面将会针对这些逐个分析一下.

    Message.obtain

    1.1 public static Message obtain( )

    代码位于 Message.java 126 行

    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 的设计原理了. 这里就引出了 Message 中的消息对象池了.

    消息对象池

    private static Message sPool;
    

    乍一看, 好像没什么, 就是一个 Message 对象而已, sPool 对象默认是 null.
    这时候回头来看上面的无参 obtain 方法, 内部直接 new Message() 返回了. 但是官方又不推荐直接 new Message(), 所以推断 sPool 在大部分情况下, 是不会为 null 的. 那么就找一下, 看在什么地方会对 sPool 进行赋值.
    经过搜索后发现, 整个 Message 就有两次地方对 sPool 进行赋值.

    1. 上面的无参 obtain 方法中.
    2. void recycleUnchecked() 方法中.

    首先第一点已经排除了. 那么就直接进入到第二点方法内查看.

    Message.recycleUnchecked()
    为了更好的理解 我把 recycleUnchecked() obtain() 放在一起., 并省略一些不重要的代码

    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;
            }
        }
    }
    

    recycleUnchecked()
    注意: 变量 nextsPool 都是 Message.

    • 第一步: next = sPool, 首先因为消息对象池一开始就为 null, 所以此时的 next 应该也是为 null.
    • 第二步: sPool = this, 将当前 Message 对象作为消息池中第一个可被复用的对象.
    • 第三步: sPoolSize++, sPoolSize 默认为 0, 这个时候就变成了 1, 将消息对象池内的数量+1, 这个数量是全局共享的.

    假设现在又调用了一遍 recycleUnchecked 这个方法, 之前第一遍调用 recycleUnchecked 中那个第一个可被复用的对象为 Message1, 依旧执行上面三步.

    • 第一步: next = sPool, 因为消息对象池中有一个 Message1, 所以此时 sPoolMessage1, 同时赋值给 next.
    • 第二步: sPool = this, 将当前 Message 对象作为消息池中第一个可被复用的对象. 假设当前这个为 Message2.
    • 第三步: sPoolSize++, sPoolSize 此时为 1, ++ 后, 变为 2.

    那么现在, sPool 消息对象池数量为 2, sPool 对应 Message2, sPool 内持有的另一个 Message 对象 next 对应 Message1.
    依次类推, 直到 sPoolSize = MAX_POOL_SIZE (MAX_POOL_SIZE = 50) .

    接着看 obtain(), 假设消息对象池中已经有两个对象了. 就是 Message1, Message2.

    • 第一步: 判断 sPool 是否为 null, 为 null 就直接创建一个 Message 并返回.
    • 第二步: Message m = sPool, 将消息对象池的头部赋值给 m, 刚才 sPool 对应的是 Message2.
    • 第三步: sPool = m.next, 将消息对象池的下一个可复用的 Message 取出并赋值给消息对象池的头部, 那么此时, sPool 对应 Message1, m 对应 Message2. Message1.next 是为 null的.
    • 第四步: m.next = null, 因为之前已经把 m.next 也就是 Message1 赋值给 sPool 了. 所以就把 m 做为单独的一个 Message 来对待.
    • 第五步 m.flags = 0 , 设置 m 的标记位, 标记正在被使用.
    • 第六步. sPoolSize-- , 因为已经取出了一个,赋值给 m了, 这时候消息对象池的容量减一.

    那么现在, m 就是单独的一个 Message 对象. sPool 消息对象池数量为 1, 对应的是 Message1, 并且 sPool.next = null , 因为从消息对象池中取出了 Message2 给了 m.
    那么剩下的是什么操作呢, Message2 取出来了, 就要开始分发了, 还记得昨天分析的 Looper.loop() 方法吗, 分发完 Message2 后最后一步就是调用了 Message2.recycleUnchecked() 方法, 又将 Message2 放入了消息对象池了, 这样就完成了一次 Message 的复用.

    (一开始消息对象池就是 null , 当我们第一次调用 Message.obtain() 的时候, 其实就是直接 new 了一个 Message 的. 然后在昨天学习的 Looper.loop() 方法的最后, 分发完消息后, 调用了 msg.recycleUnchecked() 将我们 newMessage 放入了消息对象池.)

    • 可以将sPool看成一个指针, 通过next来将对象组成一个链表. 因为每次只需要从池子中拿出一个对象, 所以不需要关心池子里具体有多少个对象, 而是拿出当前这个sPool所指向的这个对象就可以了.sPool从思路上理解就是通过左右移动来完成复用和回收.
      image.png
    • Message m = sPool, 然后 sPool = m.next 因此第一个m.next就等于第二个 message, 从上图上看相当于指针向后移动了一位, 随后将第一个 message.next() 的值设置为 null.如下图
      image.png
    • 现在这个链表看上去就断了, 如果in-use这个message使用完毕了, 怎么回到链表中? 这就是 recycleUnchecked()回收了.
      这时候再看下recycleUnchecked()中的代码,next = sPool, 将当前sPool所指向的message对象赋值给in-usenext. 然后sPool=this, 将sPool指向第一个message对象.从而达到回收 . 如下图
      image.png

    这样链表就恢复了,而且不管是复用还是回收都是保证线程同步的. 所以始终会形成一条链式结构.


     

    1.2 public static Message obtain(Message orig)

    public static Message obtain(Message orig) {
          Message m = obtain();
          m.what = orig.what;
          m.arg1 = orig.arg1;
          m.arg2 = orig.arg2;
          m.obj = orig.obj;
          m.replyTo = orig.replyTo;
          m.sendingUid = orig.sendingUid;
          if (orig.data != null) {
              m.data = new Bundle(orig.data);
          }
          m.target = orig.target;
          m.callback = orig.callback;
          return m;
      }
    
    • 解析

    obtain 差不多, 但是是将 Message 的所有内容赋值一份到新的消息中.
    代码中可以看到首先调用的是无参 obtain, 从消息对象池中获取一个 Message 对象 m, 然后把 origin 中所有的属性都赋值给 m .并返回 m.


     

    1.2 public static Message obtain(Handler h)

    public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;
        return m;
    }
    
    • 解析

    和无参 obtain 一样, 但是成员变量中 target 的值可以用以指定的值(入参)来替换.


    剩下几个 obtain 方法都基本类似, 都是先调用了obtain 无参函数, 在重置一些值. 这里就不再一一说明.

    主要就是对无参 obtain 方法的理解, recycleUnchecked() 方法的理解以及消息对象池 sPool 的理解. 这三个方面, 还是要捋清楚的. 下一章一起学习分析 MessageQueue.

    相关文章

      网友评论

        本文标题:Android 消息机制之 Message 与消息对象池的深入源

        本文链接:https://www.haomeiwen.com/subject/cnyejktx.html