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 可被销毁。
| 角色 | 方法/回调 | 触发时机 |
|---|---|---|
| 客户端 | 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 绑定流程完整步骤
- 创建 Intent:显式指定 Service 的包名和类名,或使用隐式 Intent+Action 并设置
setPackage()。 - 调用 bindService:传入 Intent、ServiceConnection 实例和绑定标志(通常为
BIND_AUTO_CREATE)。 - AMS 分派:若 Service 未启动,先通过
onCreate()+onBind()初始化;若已运行则直接取出 Binder 引用。 - 传递 Binder:Binder 驱动将服务端 IBinder 句柄复制到客户端进程。
- 回调 onServiceConnected:客户端主线程收到回调,入参即为服务端 Binder 的代理。
- 类型转换:执行
Stub.asInterface(service)获取可调用的代理对象。 - 发起调用:通过代理对象调用 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 线程池处理。
• 空代理访问:在
• 重复绑定:同一 ServiceConnection 实例不可多次调用 bindService,会导致 ServiceConnectionLeaked 警告。
• 跨进程死锁:主线程调用 Binder 同步方法时若服务端此时也向客户端发同步调用,可能造成线程死锁。建议服务端耗时操作放在 Binder 线程池处理。
• 空代理访问:在
onServiceConnected 回调之前访问 proxyRef 会导致空指针,必须通过回调通知。
4.5 绑定流程交互图
要点总结:
•
•
• 获取代理后建议缓存为成员变量,避免每次调用都执行
• 使用
•
bindService() 是异步操作,不能立即获取 Binder 代理。•
ServiceConnection 必须持有对 AIDL 接口的强引用,避免回调时被回收。• 获取代理后建议缓存为成员变量,避免每次调用都执行
Stub.asInterface()。• 使用
BIND_AUTO_CREATE 标志确保 Service 在绑定时自动创建。