Message对象池
本文承接MessageQueue消息入队和Handler消息处理。这里不把 Message 对象池只解释成“减少分配的享元模式”,而是追踪一条对象的状态:谁创建它、什么时候标记为 in-use、谁允许回收、回收后哪些字段被清掉,以及队列实现变化时为什么 recycleUnchecked() 不能简单理解为“放回全局池”。
1. 池结构
源码文件:frameworks/base/core/java/android/os/Message.java
相关字段:sPool、sPoolSize、MAX_POOL_SIZE、sPoolSync
public static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;池是一个由 Message.next 串起来的单向链表,sPoolSync 保护头指针和数量。它是进程内静态池,不按 Handler、Looper 或线程分桶;任何线程都可能从同一个池取出对象。
2. 获取对象
源码文件:frameworks/base/core/java/android/os/Message.java
相关函数:obtain()
public static Message obtain() {
if (!MessageQueue.getUseConcurrent()) {
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();
}取出对象时只清除池链表链接和 FLAG_IN_USE,不在这里重新填充业务字段;各个重载随后设置自己的参数。池为空,或当前队列实现要求并发安全时,直接创建新对象。
3. 重载入口
源码文件:frameworks/base/core/java/android/os/Message.java
相关函数:obtain(Handler, Runnable)、obtain(Handler, int, Object)、obtain(Message)
public static Message obtain(Handler h, Runnable callback) {
Message m = obtain();
m.target = h;
m.callback = callback;
return m;
}
public static Message obtain(Handler h, int what, Object obj) {
Message m = obtain();
m.target = h;
m.what = what;
m.obj = obj;
return m;
}
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.target = orig.target;
m.callback = orig.callback;
return m;
}重载先统一取得一个“空闲对象”,再写入目标、回调或载荷。obtain(Message) 是浅复制加 Bundle 复制的组合,不复制链表链接、时间戳和 in-use 状态;它适合在需要独立生命周期时复制,而不是把原对象重新送回队列。
4. 使用状态
源码文件:frameworks/base/core/java/android/os/Message.java
相关函数:isInUse()、markInUse()
/*package*/ boolean isInUse() {
return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
}
/*package*/ void markInUse() {
int localFlags;
do {
localFlags = this.flags;
if ((localFlags & FLAG_IN_USE) != 0) {
return;
}
} while (!sFlags.compareAndSet(this, localFlags, localFlags | FLAG_IN_USE));
}FLAG_IN_USE 是对象生命周期闸门:入队前由 MessageQueue.enqueueMessage() 标记,出队时 next() 再确认,回调期间保持为 in-use。markInUse() 使用 CAS 合并标志位,不能把异步标志等其他状态无条件覆盖掉。
5. 显式回收
源码文件:frameworks/base/core/java/android/os/Message.java
相关函数:recycle()、recycleUnchecked()
public void recycle() {
if (isInUse()) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
recycleUnchecked();
}公开 recycle() 先阻止仍在队列中或正在分发的对象被回收。调用成功后,调用者不能再访问该 Message;对象可能已经进入池,也可能因为池已满而不再被保留。
6. 清理字段
源码文件:frameworks/base/core/java/android/os/Message.java
相关函数:recycleUnchecked()、clear()
void recycleUnchecked() {
if (MessageQueue.getUseConcurrent()) {
clearReferenceFields();
markInUse();
} else {
clear();
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
}
void clear() {
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
target = null;
callback = null;
data = null;
when = 0;
}普通实现会清空业务字段、引用和时间,再放入最多 50 个节点的池。注意 clear() 把 flags 设置为 FLAG_IN_USE,这是为了防止清理过程中被误当作可再次使用的对象;只有下一次 obtain() 从池取出时,才把 flags 置为 0。
7. 并发分支
当前固定源码中,LegacyMessageQueue.getUseConcurrent() 返回 false,因此 Java 对象池分支实际生效。但 Message 保留了 getUseConcurrent() 为 true 时的另一条回收语义:使用 clearReferenceFields(),保留 flags 和链表字段,以适配移除线程仍可能持有消息的并发实现。
源码文件:frameworks/base/core/java/android/os/Message.java
相关函数:clearReferenceFields()
void clearReferenceFields() {
obj = NULL_OBJECT;
replyTo = null;
data = null;
target = (target != null) ? NULL_HANDLER : null;
callback = NULL_RUNNABLE;
this.onClear();
}这条分支不能被简化为“池更快”或“池关闭”:它改变的是清理后对象仍可被并发消息栈识别的方式。阅读当前文章的 Java 链表路径时,不能把该分支的 sentinel 字段语义外推到 LegacyMessageQueue。
8. 回收消费者
源码文件:frameworks/base/core/java/android/os/Looper.java
相关函数:loopOnce()
msg.target.dispatchMessage(msg);
// 正常完成、日志和 identity 检查之后
msg.recycleUnchecked();Looper 在正常分发完成后回收 Message;异常路径会从 loopOnce() 的 catch 重新抛出,不能据此假定后续正常回收代码一定执行。队列退出、取消和清空路径也会直接调用 recycleUnchecked(),所以对象可能没有经过 Handler 的业务回调。
9. 复制边界
源码文件:frameworks/base/tests/testables/src/android/testing/TestableLooper.java
相关函数:消息重排路径
// The Message cannot be re-enqueued because it is marked in use.
Message newMessage = Message.obtain();
newMessage.copyFrom(message);
newMessage.setCallback(callback);
mQueueWrapper.recycle(message);
message = newMessage;
handler.sendMessageAtTime(message, when);测试框架在需要重新安排已经取出的消息时,不直接清掉 in-use 标志再复用原对象,而是创建副本、回收旧对象、把副本重新发送。这是对象池状态协议的一个实际消费者:复制解决的是生命周期冲突,不是单纯的性能优化。
10. 误用边界
| 动作 | 结果 |
|---|---|
对正在队列中的 Message 调 recycle() | 抛 IllegalStateException |
| 回收后继续读取字段 | API 契约不允许,字段可能已被清空或对象已复用 |
| 把回收对象重新发送 | 可能与其他使用者共享已复用状态,属于生命周期错误 |
| 池超过 50 个对象 | 超出部分不进入池,等待垃圾回收 |
认为 recycleUnchecked() 总是入池 | 并发实现分支不会走普通静态池 |
11. 动手验证
定位对象状态转换:
rg -n "public static Message obtain|public void recycle|recycleUnchecked|markInUse|isInUse|MAX_POOL_SIZE" \
frameworks/base/core/java/android/os/Message.java
rg -n "recycleUnchecked|Message\.obtain\(\)|cannot be re-enqueued" \
frameworks/base/core/java/android/os/Looper.java \
frameworks/base/tests/testables/src/android/testing/TestableLooper.java读源码时按状态顺序标记同一个对象:new/池中 → obtain → enqueue → dispatch → recycle。如果在 enqueue 和 dispatch 之间看到显式 recycle(),应立即判断它会在哪个检查点失败,而不是只看调用者是否“暂时不用”这个对象。
12. 边界说明
本文没有宣称对象池一定带来可测的性能收益,也没有展开 MessageStack 并发实现、GC 行为和所有 Message 字段的业务语义。当前固定源码能证明的是池链表、状态位、字段清理和调用者边界;具体应用是否应该手动 obtain(),还要结合消息生命周期和取消路径判断。
