智能指针:Binder 中的 sp 与 wp 智能指针如何管理对象生命周期?

说到 Binder 通信,有个绕不开的话题——对象生命周期管理。

你想想看,Binder 是跨进程的。客户端和服务端不在同一个进程里。如果服务端把一个对象传给了客户端,然后服务端自己把这个对象销毁了,客户端那边还在用,那不就崩了吗?

反过来也一样。客户端拿着一个 Binder 引用不放,服务端想销毁对象也销毁不了,内存泄漏就来了。

嗯,Android 的解决方案就是——智能指针。具体来说,是 sp(Strong Pointer)和 wp(Weak Pointer)。

为什么需要两套指针?

我刚开始接触 Binder 源码时,有个疑惑一直解不开:

「一个 sp 不就够了吗?引用计数归零就自动释放,多省心。」

后来在项目里踩了个坑才明白——循环引用

假设 A 持有了 B 的 sp,B 又持有了 A 的 sp。两个对象的引用计数永远不为 0,谁也释放不了。这就是典型的死锁式内存泄漏。

wp 就是来解决这个问题的。它不增加对象的强引用计数,只保留一个「弱引用」。你可以通过 wp 去尝试获取 sp,但如果对象已经被销毁了,你拿到的就是空指针。

核心思想: sp 负责「我还在用,你别删」;wp 负责「我想用,但我不强留」。

sp 的工作原理

sp 说白了就是一个模板类,内部封装了一个原始指针,外加引用计数管理。

它的行为很简单:

  • 构造时:增加对象的强引用计数
  • 析构时:减少对象的强引用计数
  • 赋值时:先减旧对象的计数,再加新对象的计数

当强引用计数降到 0 时,对象会被立即销毁。

// 伪代码,展示 sp 的核心逻辑
template <typename T>
class sp {
public:
    sp(T* other) : m_ptr(other) {
        if (m_ptr) m_ptr->incStrong();  // 增加强引用
    }

    ~sp() {
        if (m_ptr) m_ptr->decStrong();  // 减少强引用
    }

    T* get() const { return m_ptr; }
    T* operator->() const { return m_ptr; }

private:
    T* m_ptr;
};

注意,这里的 incStrongdecStrong 是定义在 RefBase 基类中的。所有需要被智能指针管理的对象,都必须继承自 RefBase

个人习惯: 我写 Binder 服务端时,所有需要跨进程传递的对象,一律继承 RefBase。省心,不会忘。

wp 的工作原理

wp 就更有意思了。它只增加弱引用计数,不增加强引用计数。

弱引用计数归零的条件是:强引用计数已经为 0,并且弱引用计数也为 0。

换句话说:

  • 强引用计数为 0 → 对象被销毁
  • 弱引用计数为 0 → 对象占用的内存被回收

这两步是分开的。对象销毁后,内存可能还在,因为还有 wp 指着它。直到所有 wp 也释放了,内存才真正归还给系统。

// wp 的核心用法
wp<SomeBinderService> weakPtr = service;

// 尝试升级为 sp
sp<SomeBinderService> strongPtr = weakPtr.promote();
if (strongPtr != nullptr) {
    // 对象还在,可以安全使用
    strongPtr->doSomething();
} else {
    // 对象已经被销毁了
    // 我曾经在这里吃过亏,忘了检查 nullptr,直接崩了
}

promote() 是关键操作。它尝试把弱引用升级为强引用。如果对象还活着,升级成功,返回一个有效的 sp。如果对象已经死了,返回空 sp

注意: 永远不要假设 promote() 一定成功。跨进程场景下,服务端随时可能挂掉或主动销毁对象。每次使用前都要检查返回值。

sp 与 wp 的协作流程

我画了一张图,帮你理清它们的关系:

sp 与 wp 生命周期管理流程 Binder 对象 (RefBase) 强引用计数 (mStrong) 弱引用计数 (mWeak) sp 增加 wp 增加 强引用计数 == 0 ? 是 → 销毁对象 (但内存可能还在) 弱引用计数 == 0 ? → 释放内存

流程其实很清晰:

  1. sp 增加强引用计数,wp 增加弱引用计数
  2. 强引用计数归零 → 对象被销毁(析构函数被调用)
  3. 弱引用计数归零 → 内存被释放

这里有个细节:对象销毁后,wppromote() 会返回空。但 wp 本身还是有效的,直到弱引用计数也归零。

Binder 中的实际应用

在 Binder 驱动层面,spwp 被大量使用。我举两个典型场景:

场景 使用方式 说明
服务端注册服务 sp<BnInterface> 服务端持有强引用,保证对象存活
客户端获取代理 wp<BpInterface> 客户端持有弱引用,避免阻止服务端销毁
死亡通知 wp + promote() 检测服务端是否还活着

我记得有一次排查线上问题,发现某个服务端进程退出后,客户端还在疯狂重连。原因就是客户端用 sp 持有了 Binder 代理,导致服务端对象无法完全释放。改成 wp 后,问题就解决了。

避坑指南: 我曾经在回调函数里直接传 sp,结果造成了循环引用。后来统一改成传 wp,在回调内部 promote() 一下,安全又干净。

总结一下

spwp 是 Binder 对象生命周期管理的基石。它们配合 RefBase 的引用计数机制,解决了跨进程对象释放的难题。

记住三条原则:

  • 需要长期持有的对象,用 sp
  • 只需要临时访问的对象,用 wp + promote()
  • 永远检查 promote() 的返回值

做到这三点,Binder 对象生命周期的问题基本不会找上你。


专注资料整理