4. 客户端绑定服务过程:bindService 流程、ServiceConnection 实现、获取 Binder 代理

客户端通过 bindService() 启动与远程 Service 的绑定通道,这是 Binder 跨进程通信的入口。绑定成功后,客户端获得 Binder 代理对象,从而能够调用 AIDL 接口中定义的同步或异步方法。本章将拆解这一过程的核心 API、生命周期变化以及代理获取的内部细节。

4.1 bindService 调用与系统服务交互

bindService() 是 Context 类的方法,调用后会触发 ActivityManagerService (AMS) 的调度逻辑:

  • Intent 匹配:AMS 根据 Intent 中的 Action/Package 找到目标 Service(若 Service 未运行则自动创建)。
  • Binder 引用传递:Service 端 onBind() 返回的 IBinder 对象通过 Binder 驱动传递到客户端进程。
  • 引用计数管理:AMS 维护每个 Service 的绑定计数,当所有客户端解绑后 Service 可被销毁。
关键 API 与生命周期回调
角色方法/回调触发时机
客户端bindService(Intent, ServiceConnection, int)调用后立即返回,绑定结果异步到达
客户端ServiceConnection.onServiceConnected(ComponentName, IBinder)AMS 成功传递 Binder 代理后
服务端onBind(Intent)当第一个客户端绑定时调用
服务端onUnbind(Intent)当最后一个客户端解绑时调用

4.2 ServiceConnection 实现与 Binder 代理转换

客户端必须实现 ServiceConnection 接口来监听绑定结果。在 onServiceConnected() 回调中,通过类型转换将 IBinder 转为 AIDL 接口的 Stub.Proxy:

// 实现 ServiceConnection
private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // 将 IBinder 转为 AIDL 接口的代理对象
        INavigationCallback callback = INavigationCallback.Stub.asInterface(service);
        Log.d(TAG, "onServiceConnected: proxy obtained, class=" + callback.getClass().getName());
        // 通过代理发起跨进程调用(同步或异步)
        try {
            boolean result = callback.navigateTo("dest_floor_3");
            Log.d(TAG, "navigateTo result: " + result);
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException in navigateTo", e);
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.w(TAG, "onServiceDisconnected: binder died or service crashed");
        // 清空代理引用,防止内存泄漏
        proxyRef = null;
    }
};
代理转换原理:Stub.asInterface() 内部判断 IBinder 是否来自本地进程;若是则返回 Stub 本身(避免跨进程开销),否则返回 Proxy 包装类。该包装类在发起调用时通过 transact() 将参数序列化后发送到服务端。

4.3 绑定流程完整步骤

  1. 创建 Intent:显式指定 Service 的包名和类名,或使用隐式 Intent+Action 并设置 setPackage()。
  2. 调用 bindService:传入 Intent、ServiceConnection 实例和绑定标志(通常为 BIND_AUTO_CREATE)。
  3. AMS 分派:若 Service 未启动,先通过 onCreate() + onBind() 初始化;若已运行则直接取出 Binder 引用。
  4. 传递 Binder:Binder 驱动将服务端 IBinder 句柄复制到客户端进程。
  5. 回调 onServiceConnected:客户端主线程收到回调,入参即为服务端 Binder 的代理。
  6. 类型转换:执行 Stub.asInterface(service) 获取可调用的代理对象。
  7. 发起调用:通过代理对象调用 AIDL 方法,触发跨进程通信。

4.4 绑定生命周期与解绑

客户端在 Activity.onDestroy() 或非预期销毁时必须调用 unbindService() 释放连接:

@Override
protected void onDestroy() {
    super.onDestroy();
    if (bound) {
        unbindService(connection);
        bound = false;
        proxyRef = null;
        Log.d(TAG, "Service unbound and proxy cleared");
    }
}
常见陷阱:
• 重复绑定:同一 ServiceConnection 实例不可多次调用 bindService,会导致 ServiceConnectionLeaked 警告。
• 跨进程死锁:主线程调用 Binder 同步方法时若服务端此时也向客户端发同步调用,可能造成线程死锁。建议服务端耗时操作放在 Binder 线程池处理。
• 空代理访问:在 onServiceConnected 回调之前访问 proxyRef 会导致空指针,必须通过回调通知。

4.5 绑定流程交互图

Client/Activity 应用进程 bindService() Intent + ServiceConnection Binder 驱动 IPC 调度 & 引用拷贝 onServiceConnected IBinder 代理到手 onBind() 返回 Binder Stub Binder 线程池 处理 transact() 调用 navigateTo() 序列化参数 & 执行 🔁 异步回调模式(非主线程阻塞) onServiceConnected 在主线程执行,Binder 通信在 Binder 线程池
要点总结:
• bindService() 是异步操作,不能立即获取 Binder 代理。
• ServiceConnection 必须持有对 AIDL 接口的强引用,避免回调时被回收。
• 获取代理后建议缓存为成员变量,避免每次调用都执行 Stub.asInterface()。
• 使用 BIND_AUTO_CREATE 标志确保 Service 在绑定时自动创建。