把“不可变且可复用”的细粒度对象缓存起来,用“共享”代替“新建”,从而节省内存。
经典场景
-
字符串常量池、
Integer.valueOf(-128~127)
、AndroidMessage.obtain()
-
游戏粒子、编辑器字形、地图瓦片、线程池中的任务包装器
android Message
Android 里 Message
对享元模式(Flyweight)的实现就是 “对象池 + 不可变字段拆分” 的典型案例。
Android 的 Message
把享元模式做成了 “运行期对象池”:
内部状态(what/arg1/arg2/obj 等字段结构)被所有消息共享;外部状态(具体数值、target、when)由调用者每次临时传入;用完调用 recycle()
把对象回收到 静态链表池(sPool),下次 obtain()
再取出来复用,从而避免大量 new Message()
造成的内存与 GC 压力
核心流程
-
借对象:
Message.obtain()
从链表头 sPool 弹出一个空闲节点,池空才new Message()
。 -
填充外部状态:调用者给
what
、arg1
、obj
等赋值。 -
回收对象:Handler 处理完消息后自动走
recycle()
→recycleUnchecked()
,把字段清零并插回链表,池大小上限 50
同步与性能
-
使用 sPoolSync 对象锁 保证多线程安全;
-
链表替代 Map,减少哈希计算,O(1) 取出/归还;
-
只缓存“空壳”字段结构,不缓存业务数据,实现 零状态污染 的共享
Message.obtain()
就是 Android 对享元模式的落地:把不变字段当模板共享,把变化数据当参数注入,用链表池循环复用,既省内存又保线程安全
享元池核心源码
// 系统隐藏类:android.os.Message
public final class Message implements Parcelable {
/* 享元字段:可复用的内部状态 */
public int what;
public int arg1;
public int arg2;
public Object obj;
/* 外部状态:Handler、时间戳等由调用者每次传入 */
/* ... */
// ===== 享元工厂 =====
private static final Object sPoolSync = new Object();
private static Message sPool; // 链表头
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
// 对外暴露的“借对象”方法
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
return m;
}
}
return new Message(); // 池空就新建
}
// 用完归还
public void recycle() {
// 清空可复用字段
what = 0; arg1 = 0; arg2 = 0; obj = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
}
客户端使用示例
// 借
Message msg = Message.obtain(); // 复用池内对象
msg.what = 1;
msg.arg1 = 100;
msg.obj = "Hello";
// 发送
handler.sendMessage(msg);
// 系统 Looper 处理完后会自动调 recycle() 归还
Message.obtain()
就是 Android 对享元模式的最佳实践:内部状态缓存复用,外部状态调用方提供,既省内存又免锁。