Android源码看设计模式(十)--------关于享元模式的Handler相关分析

享元模式

定义:

使用共享对象可以有效的支持大量的细粒度的对象

应用场景

  • 系统中存在大量的相似对象
  • 细粒度的对象都具备比较接近的外部状态,而内部状态与环境无关,也就是说对象没有特定身份
  • 需要缓冲池的场景

享元模式的写法

UML图如下:
这里写图片描述

  • Flyweight:享元对象抽象基类或者接口
  • ConcreteFlyweight:具体的享元对象
  • FlyweightFactory:享元工厂,负责管理享元对象池和创建享元对象

以买火车票为例说明,我们知道买票的时候,客户端每次刷新就会返回一个结果,当有很多人同时抢票的时候,若是每次都创建一个新结果对象返回,那么就会造成CPU占用率居高不下,GC任务繁重,很有可能造成OOM

定义抽象享元

//火车票信息
public interface Ticket {
    public void showTicketInfo(String bunk);
}

创建具体享元对象

class TrainTicket implements Ticket{
    public String from;   //始发站
    public String to;     //终点站
    public String bunk;   //铺位
    public int price;

    public TrainTicket(String from,String to) {
        this.from = from;
        this.to = to;
    }

    @Override
    public void showTicketInfo(String bunk) {
        price = new Random().nextInt(300);
        System.out.println("购买从"+from+"到"+to+"的"+bunk+"火车票"+",价格:"+price);
    }
}

创建享元工厂

//这种每次都是返回一个新的对象,其中会造成大量重复的对象,不可取
 public class TicketFactory{
        public static Ticket getTicket(String from,String to) {
            return new TrainTicket(from,to);
        }
    }

修改如下

 public class TicketFactory{
        private static Map<String ,Ticket> sTicketMap = new ConcurrentHashMap<>();
        public static Ticket getTicket(String from,String to) {
            String key = from+"-"+to;
            if (sTicketMap.containsKey(key)) {
                //使用缓存
                return sTicketMap.get(key);
            } else {
                //新建结果对象,存入缓存
                Ticket ticket = new TrainTicket(from,to);
                sTicketMap.put(key,ticket);
                return ticket;
            }
        }
    }

这里就是所说的享元模式通过消息池的形式有效的减少重复对象的存在,它通过内部状态表示某个种类的对象,外部程序根据这个不会变化的内部状态从此消息池中取出对象,使得这一类的对象可以重复使用,例如1000个人查询从北京到太原的火车票,通过享元模式就可以将1000个结果对象减少至1个!在这个例子中不变的内部状态是出发地和目的地,变化的外部状态是铺位和价格。

Handler原理图

这里写图片描述
其实Message、MessageQueue、Looper、Handler的工作原理像是生产线,Looper是发动机,MessageQueue是传送带,Handler是工人,Message就是待处理的产品。我们知道Android应用是事件驱动,每个事件都会转化为一个系统消息,即Message。消息中包含了事件的信息和发送消息的处理人Handler,每个进程中都有一个默认的消息队列,也就是MessageQueue,这个消息队列维护了一个待处理的消息列表,有一个消息循环不停的从这个队列中取出消息,处理消息,这样就使应用动态的运作起来了。那么Message就会产生很多的对象,因为整个应用都是由事件,也就是Message来驱动的,系统需要不断的产生Message、处理Message、销毁Message,这种重复大量的构建Message就是利用了享元模式

Handler的两种使用方式:post、sendMessage,我们就post来说明

 /**
   sendMessage中传入的参数是Message对象,而post中传入的参数是Runnable对象,值得注意的是此处是调用
   getPostMessage()方法将Runnable对象封装到一个Message对象
 */
 public final boolean post(Runnable r){
       return  sendMessageDelayed(getPostMessage(r), 0);
 }

getPostMessage(Runnable r)

private static Message getPostMessage(Runnable r) {
      //注意这里的Message对象的获取方式,稍后再说
      Message m = Message.obtain();
      m.callback = r;
      return m;
}

我们接着向下看,直到sendMessageAtFrontOfQueue方法

//Message被压入MessageQueue队列中
public final boolean sendMessageAtFrontOfQueue(Message msg) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
            this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, 0);
}

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
     msg.target = this;
     if (mAsynchronous) {
         msg.setAsynchronous(true);
     }
     return queue.enqueueMessage(msg, uptimeMillis);
 }

看过怎么将Message压入队列,我们来看一看怎么从队列中取出Message

public static void loop() {
      final Looper me = myLooper();
      if (me == null) {
          throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
      }
      final MessageQueue queue = me.mQueue;
        ......
      //死循环,不断的从队列中取Message
      for (;;) {
          Message msg = queue.next();
          if (msg == null) {
              return;
          }

          ......

          try {
              //注意这句,是真正执行Message
              msg.target.dispatchMessage(msg);
              end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
          } finally {
              if (traceTag != 0) {
                  Trace.traceEnd(traceTag);
              }
          }
          ......
          msg.recycleUnchecked();
      }
  }

dispatchMessage源码

public void dispatchMessage(Message msg) {
      //注意上述在使用post方式的时候调用getPostMessage
      if (msg.callback != null) {
          handleCallback(msg);
      } else {
          if (mCallback != null) {
              if (mCallback.handleMessage(msg)) {
                  return;
              }
          }
          handleMessage(msg);
      }
}

handleCallback

 //执行Message
 private static void handleCallback(Message message) {
        message.callback.run();
 }

我们说Message是享元模式在这里的一个应用,那么反过头来去看一看Message的获取方式

Message m = Message.obtain();

Message#obtain

 public static Message obtain() {
    synchronized (sPoolSync) {
          /**请注意!我们可以看到Message中有一个next字段指向下一个Message,这里就明白了,Message消息池中
             没有使用Map这样的容器,而是使用的链表!
          */
          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();
}

那么之前的时候都是从链表中取Message对象,可是在哪里放Message到链表中呢?在recycle方法中
Message#recycle

 public void recycle() {
     //判断消息是否还在使用
    if (isInUse()) {
          if (gCheckRecycle) {
              throw new IllegalStateException("This message cannot be recycled because it "
                      + "is still in use.");
          }
          return;
      }
      //清空状态,将消息添加到消息链表中
      recycleUnchecked();
}

recycleUnchecked

void recycleUnchecked() {
      //清除状态,设置该消息in_use flag
      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++;
          }
      }
}

Message内部维护一个尺度为50的链表去管理被回收的Message的对象,调用obtain方法时会优先从消息池中取Message对象,若是没有可复用的Message,就会创建该Message,之后回收时会被存放在消息池中,下次调用obtain时就可以被复用,但是这里不是经典的享元模式,更像是一个对象池,因为没有内部、外部状态,不过值得关注的是灵活运用模式,

public static Message obtain() {
      synchronized (sPoolSync) {
          //优先从池中获取Message,
           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();
}

享元模式的有点在于可以大幅度的降低内存中的对象的数量,但是这样一来系统的复杂程度就提高了,同时享元模式将享元模式的状态外部化,而读取外部状态是运行时间稍微变长

Android源码设计模式解析与实战
https://www.cnblogs.com/zzfsblog/p/4248836.html

猜你喜欢

转载自blog.csdn.net/qq_33768280/article/details/81160502
今日推荐