18、Binder 的匿名服务:什么是匿名 Binder 服务?如何传递与使用?

聊完了实名 Binder 服务,咱们今天来聊聊它的“孪生兄弟”——匿名 Binder 服务。

说实话,我刚接触 Android 的时候,对“匿名服务”这个概念挺懵的。实名服务好歹有个名字,你通过 ServiceManager 一查就能找到。匿名服务呢?它连个名字都没有,那别人怎么找到它?

后来我在一个多媒体项目中真正用到了它,才恍然大悟。说白了,匿名 Binder 服务就是不需要注册到 ServiceManager 的 Binder 服务。它通过 Binder 传递机制,直接把服务端的 Binder 引用发给客户端。

18.1 什么是匿名 Binder 服务?

先看个直观的定义:

  • 实名 Binder 服务:注册到 ServiceManager,有全局唯一的名字,任何进程都能通过名字查到。
  • 匿名 Binder 服务:不注册到 ServiceManager,没有名字,只能通过 Binder 传递来获取引用。

你想想看,ServiceManager 就像一个电话本。实名服务就是上面登记了号码的公司,你查一下就能打过去。匿名服务呢?它不登记,但你朋友可以直接把它的号码发给你。

我在项目中遇到过这样一个场景:一个应用需要给另一个应用传递一个临时的回调接口。这个回调接口只在这个会话中有效,用完就作废。如果把它注册成实名服务,还得起个名字、注册、用完再注销,太麻烦了。匿名 Binder 服务正好解决这个问题。

核心要点:匿名 Binder 服务的生命周期由持有它的 Binder 引用决定。当所有引用都被释放时,服务自动销毁。

18.2 匿名 Binder 服务的传递机制

匿名 Binder 服务的传递,依赖的是 Binder 驱动的一个核心能力——Binder 节点迁移

当一个进程把 Binder 对象作为参数或返回值传递给另一个进程时,Binder 驱动会在目标进程中创建一个新的 Binder 引用,指向同一个 Binder 节点。

这个过程我画了张图,你看完就明白了:

匿名 Binder 服务传递流程 进程 A(服务端) 创建匿名 Binder 对象 实现 IMyCallback 接口 进程 B(客户端) 接收 Binder 引用 通过引用调用服务 传递 Binder 对象 Binder 驱动 创建节点 分配引用 生成引用 关键步骤: 1. 进程 A 创建匿名 Binder 对象(实现 Binder 接口) 2. 进程 A 通过 Binder IPC 调用,将对象作为参数传给进程 B 3. Binder 驱动在进程 B 中创建对应的 Binder 代理(BpBinder) 4. 进程 B 通过代理对象调用进程 A 中的服务方法

18.3 如何传递匿名 Binder 服务?

传递匿名 Binder 服务,最常用的方式就是通过已有实名服务的接口。举个例子:

// 服务端:定义回调接口
interface IMyCallback {
    void onResult(int code, String data);
}

// 服务端:实名服务接口中增加注册回调的方法
interface IMyService {
    void registerCallback(IMyCallback callback);
    void doSomething();
}

// 服务端:实现类
public class MyService extends IMyService.Stub {
    private IMyCallback mCallback;
    
    @Override
    public void registerCallback(IMyCallback callback) {
        // 这里接收到的 callback 就是匿名 Binder 服务
        mCallback = callback;
    }
    
    @Override
    public void doSomething() {
        // 执行某些操作后,通过回调通知客户端
        if (mCallback != null) {
            try {
                mCallback.onResult(200, "操作成功");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
}

// 客户端:实现回调接口
public class MyCallback extends IMyCallback.Stub {
    @Override
    public void onResult(int code, String data) {
        // 这里运行在 Binder 线程池中
        Log.d("Callback", "收到结果: code=" + code + ", data=" + data);
    }
}

// 客户端:注册回调
IMyService service = IMyService.Stub.asInterface(binder);
service.registerCallback(new MyCallback());
service.doSomething();

个人经验:我习惯在注册回调时,把回调对象用 WeakReference 包一层。这样可以避免服务端持有了客户端的强引用,导致客户端无法被 GC 回收。曾经有个线上 bug,就是因为回调没释放,导致 Activity 泄漏了。

18.4 匿名 Binder 服务的典型应用场景

在实际开发中,匿名 Binder 服务主要有这几个用途:

场景 说明 示例
回调接口 服务端需要异步通知客户端 Service 向 Activity 报告进度
临时会话 只在某个操作期间有效的服务 文件传输会话、临时授权
私有服务 不想暴露给所有进程的服务 系统内部组件通信
动态代理 需要动态创建和销毁的服务 插件化框架中的组件通信

18.5 匿名 Binder 服务的生命周期管理

这里有个坑,我得提醒你注意。

匿名 Binder 服务的生命周期,完全由 Binder 驱动中的引用计数决定。每个进程持有一个 Binder 引用,驱动就会增加一次计数。当所有进程都释放了引用,驱动就会销毁对应的 Binder 节点。

我曾经在项目中遇到过一个诡异的问题:客户端注册了回调,但服务端调用回调时总是抛 RemoteException。排查了半天,发现是客户端进程被杀了,但服务端不知道,还在傻傻地调用。

避坑指南

  • 调用匿名 Binder 服务时,一定要捕获 RemoteException
  • 服务端可以注册 DeathRecipient 监听客户端的死亡通知
  • 不要在匿名 Binder 服务中持有 Activity 或 Context 的强引用

18.6 匿名 vs 实名:怎么选?

我个人的选择标准很简单:

  • 需要全局访问 → 实名 Binder 服务(注册到 ServiceManager)
  • 只在特定进程间通信 → 匿名 Binder 服务(通过已有接口传递)
  • 临时回调 → 匿名 Binder 服务(用完即弃)
  • 系统级服务 → 实名 Binder 服务(需要统一管理)

说白了,实名服务是“广播找人”,匿名服务是“点对点传话”。没有谁好谁坏,看场景选就对了。

总结一下:匿名 Binder 服务是 Android 进程间通信中非常灵活的一种方式。它不需要 ServiceManager 的参与,通过 Binder 传递机制直接在进程间建立通信通道。理解它的原理和生命周期,能帮你写出更健壮、更高效的跨进程通信代码。


公众号:蓝海资料掘金营,微信deep3321