Skip to content

Message对象池

从 Message.obtain 到 recycleUnchecked,追踪池链表、状态位、字段清理和并发实现分支。

基于android-17.0.0_r1
AndroidMessageHandler对象池源码阅读

Message对象池 ​

本文承接MessageQueue消息入队和Handler消息处理。这里不把 Message 对象池只解释成“减少分配的享元模式”,而是追踪一条对象的状态:谁创建它、什么时候标记为 in-use、谁允许回收、回收后哪些字段被清掉,以及队列实现变化时为什么 recycleUnchecked() 不能简单理解为“放回全局池”。

1. 池结构 ​

源码文件:frameworks/base/core/java/android/os/Message.java

相关字段:sPool、sPoolSize、MAX_POOL_SIZE、sPoolSync

java
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()

java
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)

java
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()

java
/*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()

java
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()

java
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()

java
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()

java
msg.target.dispatchMessage(msg);
// 正常完成、日志和 identity 检查之后
msg.recycleUnchecked();

Looper 在正常分发完成后回收 Message;异常路径会从 loopOnce() 的 catch 重新抛出,不能据此假定后续正常回收代码一定执行。队列退出、取消和清空路径也会直接调用 recycleUnchecked(),所以对象可能没有经过 Handler 的业务回调。

9. 复制边界 ​

源码文件:frameworks/base/tests/testables/src/android/testing/TestableLooper.java

相关函数:消息重排路径

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. 动手验证 ​

定位对象状态转换:

bash
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(),还要结合消息生命周期和取消路径判断。