写博客总是需要动力和动机的,最近在看Android线程和进程的分析,其实就是Handler这个Android开发中最重要的东西。说来惭愧现在也只是会用的程度。本着要进步的心态我需要在这块下功夫研究。代码要一行一行的写,书要一本一本的看。
相信大家都写过这样的代码:
Message message= handler.obtainMessage();
message.what=0x1;
message.arg1=MSG_ARG;
message.obj=mData;
handler.sendMessage(message);
这是Google官方推荐的Message使用代码,同样的其实我们还可以new出Message实例来发送消息,但是为什么要用obtainMessage呢?这其实就是享元模式在Android的应用,这就是我这篇博客重点分析的内容。
我们在开发中大量的使用Handler来处理线程间通信回调(其实用RxJava就不怎么需要),在子线程中刷新UI界面。同样的在Android源码中也大量使用了Handler。这就需要构造大量的Message对象类发送消息,每次构造放入数据,发送到handler处理,然后弃用等待虚拟机GC回收。这明显是非常烂的代码,Google显然不会这么做,我们的代码也不能这么写。
这就需要使用到享元模式,先看它的定义:
享元模式的定义:运用共享技术有效的支持大量细粒度的对象。
回到刚才分析使用场景,我们为什么要每次构造Message对象然后使用完之后弃用,为什么不重用它缓存起来,等待下次的使用,从缓存中取出对象重新赋值再次发送。
这个问题刚好就是为什么要用obtainMessage呢?
的答案。
仔细分析Message和Handler的关系,和它们的逻辑。有时候我们只发送非常简单的Message对象sendEmptyMessage(int what)
,就一个int标志位。如果这都需要新建对象的话就会产生大量的细粒度对象,并且存在大量重复的数据。如果能够有效的减少对象数量,减少重复数据,就可以节省很多内存开销。
享元模式的基本思路:
缓存这些包含着重复数据的对象,让这些对象只出现一次,每次操作都从缓存中取数据,避免出现大量对象。
下面详细说明享元模式的结构逻辑和举例应用
用Android源码来说明享元模式有点困难,源码包含了多线程的处理和变形比较复杂,不易理解。
而其实享元模式本质是比较简单的,用UML图和模板代码就能很好的说明它的结构和逻辑。
享元接口:
/**
* Created by LiCola on 2016/05/15 16:39
* 享元接口 通过这个接口享元可以接受并作用于外部状态
*/
public interface Flyweight {
/**
* 实例操作 传入外部状态
* @param extrinsicState 外部状态值
*/
void operation(String extrinsicState);
}
具体的享元对象实现:
/**
* Created by LiCola on 2016/05/15 16:40
* 可共享的 享元对象
*/
public class ConcreteFlyweight implements Flyweight {
/**
* 描述内部状态 的变量
*/
private String intrinsicStatic;
/**
* 构造方法 传入享元对象的内部状态数据
* @param intrinsicStatic 内部状态数据
*/
public ConcreteFlyweight(String intrinsicStatic) {
this.intrinsicStatic = intrinsicStatic;
}
@Override
public void operation(String extrinsicState) {
//具体的功能处理 可能会用到享元内、外部的状态
}
}
不需要共享的享元对象:
/**
* Created by LiCola on 2016/05/15 16:40
* 不可共享的 享元对象
* 通常是将被共享的享元对象作为子节点组合出来的对象
*/
public class UnsharedConcreteFlyweight implements Flyweight {
/**
* 描述对象状态的变量
*/
private String allState;
@Override
public void operation(String extrinsicState) {
//具体的功能处理
}
}
享元工厂:
/**
* Created by LiCola on 2016/05/15 16:50
* 享元工厂
*/
public class FlyweightFactory {
/**
* 缓存多个Flyweight的集合对象
*/
private Map mMap=new HashMap<>();
/**
* 根据key值 获取缓存中的享元对象
* @param key 获取享元对象的key值
* @return 根据key值 得到的享元对象
*/
public Flyweight getFlyweight(String key){
//先从缓存中查找 是否存在key对应的享元对象
Flyweight flyweight=mMap.get(key);
//如果不在存在 为空
if (flyweight==null){
//创建新的享元对象
flyweight=new ConcreteFlyweight(key);
//放入缓存中
mMap.put(key,flyweight);
}
//返回 对象的享元对象
return flyweight;
}
}
具体的模板代码就这么多,没有实际案例感觉还是说不清楚。下面用个具体的例子说明使用。
以网络查询火车票某车次票价为例,我们每次通过网络请求向服务器请求某个车次票价数据。假设服务器从数据库查询到数据包装成Ticket对象返回给我们。当高峰时期这是非常可怕的访问量会产生大量的对象。使用享元模式就可以把Ticket对象缓存起来,控制对象的实例数量。
Ticket接口:对应享元接口
public interface Ticket {
public void showTicketInfo(String bunk);
}
TrainTicket火车票对象:对应需要共享的享元对象
public 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(700);//模拟查询到的票价
System.out.println("查询 从" + from + " - " + to + " 的 " + bunk + " 火车票 " + " 价格为:" + price);
}
}
TicketFactory工厂类:对应享元工厂,采用饿汉式单例模式。
/**
* Created by LiCola on 2016/05/15 17:32
* 工厂类 设计成单例模式
*/
public class TicketFactory {
private static TicketFactory factory = new TicketFactory();
//缓存集合
private Map mMapTicket = new HashMap<>();
private TicketFactory() {
}
public static TicketFactory getInstance() {
return factory;
}
/**
* 查询票价 根据始发地和目的地
*
* @param from 查询的始发地
* @param to 查询的目的地
* @return Ticket 返回对象
*/
public Ticket getTicket(String from, String to) {
String key = from + "-" + to;//包装key值
if (mMapTicket.containsKey(key)) {
System.out.println("取出缓存");
return mMapTicket.get(key);
} else {
System.out.println("新建对象");
Ticket ticket = new TrainTicket(from, to);
mMapTicket.put(key, ticket);
return ticket;
}
}
}
这里场景比较简单,就不需要 UnsharedConcreteFlyweight
非共享的享元对象。
TicketQueryTest:Client调用客户端测试查询结果
public class TicketQueryTest {
public static void main(String[] args) {
Ticket ticket01 = TicketFactory.getInstance().getTicket("深圳", "金华");
ticket01.showTicketInfo("一等座");
Ticket ticket02 = TicketFactory.getInstance().getTicket("深圳", "金华");
ticket02.showTicketInfo("二等座");
Ticket ticket03 = TicketFactory.getInstance().getTicket("深圳", "金华");
ticket03.showTicketInfo("商务座");
}
}
打印结果模拟查询到的数据:
新建对象--> 深圳-金华
查询 从深圳 - 金华 的 一等座 火车票 价格为:469
取出缓存--> 深圳-金华
查询 从深圳 - 金华 的 二等座 火车票 价格为:612
取出缓存--> 深圳-金华
查询 从深圳 - 金华 的 商务座 火车票 价格为:394
从输出结果上可以看到,只有当第一次查询结果的时候创建了Ticket对象,后两次的查询都是使用了缓存集合中的对象,3次查询只创建了1个对象,享元模式很好的控制缓存的对象数量,避免了频繁的新建对象浪费内存。
提示:在Java源码中String,也采用了类似的消息池缓存,也就是String常量池,基本逻辑就是享元模式的实现。