Handler消息机制(一)Message复用原理

3,188 阅读3分钟

原文链接

本篇文章分享Handler消息机制中的Message的使用和Message的复用原理。

该系列的其他文章

  1. Handler消息机制(一)Message复用原理
  2. Handler消息机制(二)ThreadLocal原理
  3. Handler消息机制(三)MessageQueue原理
  4. Handler消息机制(四)Looper原理
  5. Handler消息机制(五)Handler原理

包含以下两个部分:

  • Message在Handler的使用方式
  • Message的复用原理

在Android开发中,使用Handler实现线程间通信,非常方便,当频繁的进行消息通信时,每次都去new消息对象,在创建对象时对系统资源的占用,同时GC频繁的回收对象等,对内存和系统性能还是会有一定影响的,当频繁的通过Handler处理消息事件时,推荐obtainMessage()的方式获取message实例,该方式在对象池中获取message,运用了享元模式的设计思想。

仅了解Message的使用方式之后,更应该知其然知其所依然,关于如何实现Message的复用,后面我会从源码层面分析复用的实现思想。

1. Message在Handler中的使用方式

Message在Handler中的使用非常简单,参考下面两种使用方式: 方式一:new 消息对象

  Message msg = new Message();    // new新的对象
  msg.what = 100;
  msg.obj = obj;
  mHandler.sendMessage(msg);

方式二:复用池中的消息对象

  Message msg = mHandler.obtainMessage();
  msg.what = 100;
  msg.obj = obj;
  mHandler.sendMessage(msg);

or

  Message msg = Message.obtain(mHandler);
  msg.what = 100;
  msg.obj = obj;
  mHandler.sendMessage(msg);

2. Message的复用原理

首先根据Message数据结构的定义,可以看出Message采用单向链表结构实现Message对象的管理,通过sPool记录表头数据。 为了理解复用原理,摘取Message如下关键参数,Message伪代码结构:

  int sPoolSize,        // 池中Message数量
  Message sPool,        // 当前Message,即表头
  Message  next,        // 当前Message的下一个节点
  int what...           // 其他参数

2.1 回收message对象

最新回收的message放入表头,通过sPool来记录当前表头的message,sPool的next节点是回收前的sPool记录的message

源码Message中回收消息 recycleUnchecked:

  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 = -1;
      when = 0;
      target = null;
      callback = null;
      data = null;

      synchronized (sPoolSync) {
          if (sPoolSize < MAX_POOL_SIZE) {
              next = sPool;
              sPool = this;
              sPoolSize++;
          }
      }
  }

抽象伪代码:

  next= sPool;       // next有可能为null
  sPool = this;
  sPoolSize++;

假设Message消息回收的顺序:msg1 -> msg2 -> msg3

第一步,消息msg1回收时:
  msg1.next = sPool;       // sPool 是null
  sPool = msg1;
第二步,消息msg2回收时:
  msg2.next = sPool;       // sPool 是msg1
  sPool = msg2;
第三步,消息msg3回收时:
  msg3.next = sPool;       // sPool 是msg2
  sPool = msg3;

回收后的链表结构是:

  sPool = msg3,msg3.next -> msg2,msg2.next -> msg1,msg1.next -> null

2.2 获取对象池消息

  • 如果sPool为null时,新建一个Message
  • 如果sPool不为null时,从对象池中获取Message对象,每次均从表头查询,即sPool记录的表头消息。

源码Handler中获取message:

  public final Message obtainMessage() {
      return Message.obtain(this);
  }

源码Message中获取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();
  }

抽象伪代码:

  if(null != sPool) {
       Message m = sPool;
       sPool = m.next
       sPoolSzie--;
       return m;
  }
  return new Message();

假设消息链表结构是(表头是msg3):

  sPool = msg3,msg3.next -> msg2,msg2.next -> msg1,msg1.next -> null
第一步,获取消息msg3:
  Message m = sPool;        // sPool 是msg3
  sPool = m.next;           // m.next即msg3.next 是msg2,sPool当前指向msg2
  return m;                 // m即表头msg1
第二步,获取消息msg2:
  Message m = sPool;        // sPool 是msg2
  sPool = m.next;           // m.next即msg2.next 是msg1
  return m;                 // m即表头msg2
第三步,获取消息msg1:
  Message m = sPool;        // sPool 是msg1
  sPool = m.next;           // m.next即msg1.next 是null
  return m;                 // m即表头msg1
第四步,对象池中无消息时:
  return new Message();

通过对Message对象复用的原理进行分析,其实在复用对象时进行的消息回收和获取,采用了栈的思想来实现,即先入后出(FILO)的原则,即最新回收的对象会被优先再次使用。相信看到这,大家就会明白Message消息的复用机制了,希望该文章可以帮助小伙伴们在开发过程中,合理的使用Message消息啦!

结语:在开发中不会频繁的使用消息对象时,直接new即可;如果需要频繁的使用消息对象,可以采用复用的方式,减少new对象的消耗,更高效的处理消息事件。

原文链接