在Android多进程开发中,服务端进程可能会因为各种原因意外终止(如系统资源不足、崩溃等)。作为客户端,我们需要一种机制来及时感知这种异常情况,这就是死亡回调(DeathRecipient)的核心价值。
死亡回调的实现依赖于Binder机制底层提供的linkToDeath方法。当我们在客户端绑定服务时,可以通过ServiceConnection获取服务端的Binder对象,然后调用其linkToDeath方法注册监听:
java复制private final IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.i(TAG, "服务端进程已终止");
// 这里可以执行重连或其他恢复逻辑
}
};
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
service.linkToDeath(deathRecipient, 0);
myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
注意:linkToDeath的第二个参数flags通常设为0,这是系统保留参数,当前版本未使用。
很多开发者会有疑问:是否需要手动调用unlinkToDeath?从Android源码分析可以看到,系统已经为我们处理了大部分清理工作:
java复制// 摘自ServiceDispatcher.java
public void doDeath(ComponentName name, IBinder service) {
synchronized (this) {
ConnectionInfo old = mActiveConnections.get(name);
if (old == null || old.binder != service) return;
mActiveConnections.remove(name);
old.binder.unlinkToDeath(old.deathMonitor, 0);
}
mConnection.onServiceDisconnected(name);
}
当服务端进程死亡时,系统会:
死亡回调的执行线程是需要特别注意的。不同于ServiceConnection的回调(在主线程执行),死亡回调会在Binder线程池中的某个线程执行:
java复制private final IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
// 执行在Binder线程,非主线程!
Log.i(TAG, "当前线程:" + Thread.currentThread().getName());
}
};
这意味着:
在实际项目中,死亡回调的使用有几个关键点需要注意:
重连策略:检测到服务死亡后,应该实现合理的重连机制。简单的做法是使用Handler.postDelayed进行延时重试,但要避免无限重试导致资源浪费。
状态同步:服务死亡后,客户端应该及时更新内部状态,避免继续调用已失效的服务接口。
资源释放:虽然系统会自动清理,但显式地释放相关资源仍是良好实践:
java复制@Override
protected void onDestroy() {
super.onDestroy();
if (myAidlInterface != null && isBound) {
myAidlInterface.asBinder().unlinkToDeath(deathRecipient, 0);
unbindService(connection);
}
}
无论客户端在哪个线程调用AIDL接口,服务端的接口实现总是在Binder线程池中的线程执行:
java复制private final IMyAidlInterface.Stub binder = new IMyAidlInterface.Stub() {
@Override
public int add(int a, int b) {
// 执行在Binder线程
Log.d(TAG, "服务端执行线程:" + Thread.currentThread().getName());
return a + b;
}
};
这意味着:
服务端自身的死亡监听(如监听底层服务)同样在Binder线程执行:
java复制IBinder.DeathRecipient dr = new DeathRecipient() {
@Override
public void binderDied() {
// Binder线程
Log.d(TAG, "死亡监听线程:" + Thread.currentThread().getName());
}
};
ServiceConnection的所有回调都在主线程执行:
java复制private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 主线程
Log.d(TAG, "onServiceConnected线程:" + Thread.currentThread().getName());
}
@Override
public void onServiceDisconnected(ComponentName name) {
// 主线程
}
};
这意味着:
AIDL接口调用的线程行为有以下几个特点:
java复制// 客户端调用示例
try {
// 调用线程(可能是主线程或子线程)
int result = myAidlInterface.add(5, 3);
// 返回后仍在原线程
} catch (RemoteException e) {
e.printStackTrace();
}
java复制new Thread(() -> {
try {
int result = myAidlInterface.add(5, 3);
runOnUiThread(() -> {
// 更新UI
});
} catch (RemoteException e) {
e.printStackTrace();
}
}).start();
当服务端通过回调接口通知客户端时,回调总是在主线程执行:
java复制callback = new IPlayerCallback.Stub() {
@Override
public void onSongChanged(String songName) {
// 主线程
Log.d(TAG, "回调线程:" + Thread.currentThread().getName());
}
};
这个特性非常有用,因为它免去了开发者手动切换到主线程的麻烦。但也要注意:
下表总结了多进程通信中各关键操作的执行线程:
| 操作类型 | 执行线程 | 注意事项 |
|---|---|---|
| 服务端AIDL实现 | Binder线程 | 注意线程安全 |
| 服务端死亡监听 | Binder线程 | 不能直接操作UI |
| 客户端ServiceConnection | 主线程 | 避免耗时操作 |
| 客户端AIDL调用 | 调用者线程 | 同步阻塞 |
| 客户端回调接口 | 主线程 | 自动切换 |
oneway是AIDL中的一个修饰符,用于改变方法调用的行为模式:
java复制oneway void doSomething();
关键特性:
oneway的实现依赖于Binder的事务标志FLAG_ONEWAY。当方法被标记为oneway时:
这种机制类似于消息队列的生产者-消费者模型,适用于不需要即时结果的场景。
oneway方法对参数有严格限制:
java复制oneway void sendData(in Parcelable data);
java复制oneway void getData(out byte[] data); // 编译错误
oneway void updateData(inout Config config); // 编译错误
这是因为out和inout参数需要返回数据,与oneway的无返回特性冲突。
oneway最适合以下场景:
java复制oneway void onTemperatureChanged(float temperature);
java复制oneway void logEvent(String eventType, Bundle params);
java复制oneway void sendAudioFrame(byte[] frameData);
执行顺序不保证:多个oneway调用的执行顺序可能与调用顺序不一致
异常无法捕获:服务端执行抛出的异常不会传递到客户端
不适用关键操作:如支付、重要数据存储等需要确认结果的操作
性能考量:虽然非阻塞,但过度使用可能增加Binder负担
| 特性 | oneway | 异步调用 |
|---|---|---|
| 线程阻塞 | 完全不阻塞 | 调用线程不阻塞 |
| 结果获取 | 不可能 | 可以通过回调获取 |
| 异常处理 | 不可知 | 可以捕获处理 |
| 实现复杂度 | 简单 | 需要额外回调接口 |
| 适用场景 | 单向通知 | 需要结果的异步操作 |
问题现象:
解决方案:
xml复制<service
android:name=".MyService"
android:process=":remote" />
java复制// 客户端绑定代码
Intent intent = new Intent(this, MyService.class);
boolean bound = bindService(intent, connection, Context.BIND_AUTO_CREATE);
if (!bound) {
// 处理绑定失败
}
java复制private static final int BIND_TIMEOUT_MS = 5000;
Handler handler = new Handler();
handler.postDelayed(() -> {
if (!isBound) {
Log.w(TAG, "服务绑定超时");
unbindService(connection);
}
}, BIND_TIMEOUT_MS);
问题现象:
解决方案:
java复制oneway void sendLargeData(in byte[] chunk, int index, int total);
java复制ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fd);
oneway void sendFileDescriptor(ParcelFileDescriptor pfd);
问题现象:
解决方案:
java复制private final Object lock = new Object();
@Override
public void updateValue(int newValue) {
synchronized (lock) {
this.value = newValue;
}
}
java复制private final ConcurrentHashMap<String, String> safeMap = new ConcurrentHashMap<>();
常见泄漏点:
正确做法:
java复制@Override
protected void onDestroy() {
super.onDestroy();
if (isBound) {
unbindService(connection);
isBound = false;
}
// 清除回调引用
if (myAidlInterface != null) {
myAidlInterface.unregisterCallback(callback);
}
}
最佳实践:
java复制// 服务端AIDL接口
interface IMyService {
void registerCallback(IMyCallback callback);
void unregisterCallback(IMyCallback callback);
}
java复制private final RemoteCallbackList<IMyCallback> callbacks = new RemoteCallbackList<>();
@Override
public void registerCallback(IMyCallback callback) {
callbacks.register(callback);
}
@Override
public void unregisterCallback(IMyCallback callback) {
callbacks.unregister(callback);
}
private void notifyCallbacks() {
int count = callbacks.beginBroadcast();
for (int i = 0; i < count; i++) {
try {
callbacks.getBroadcastItem(i).onEvent();
} catch (RemoteException e) {
e.printStackTrace();
}
}
callbacks.finishBroadcast();
}
在实际项目中,多进程开发需要特别注意生命周期管理、线程安全和性能优化。建议在架构设计阶段就充分考虑这些因素,避免后期出现难以调试的问题。