4. AIDL客户端实现:绑定服务、ServiceConnection回调、异步与同步调用、超时处理
在Camera HAL多进程架构中,客户端(通常是CameraService或上层Framework)通过AIDL与服务端(Camera HAL进程)通信。本章聚焦客户端实现的核心环节:服务绑定、连接回调、调用模式选择以及超时保护机制。这些是实现稳定、高性能多进程通信的基础。
核心要点:客户端必须正确处理ServiceConnection的生命周期,根据场景选择同步或异步调用,并通过超时机制防止进程死锁或ANR。
4.1 服务绑定与ServiceConnection回调
客户端通过bindService()发起与服务端AIDL服务的连接。该过程是异步的,客户端需实现ServiceConnection接口以接收连接结果。
4.1.1 绑定流程
- 创建Intent:指定服务端进程的包名和Service类名。
- 调用bindService:传入Intent、ServiceConnection实例和绑定标志(如
BIND_AUTO_CREATE)。 - 系统调度:Android系统启动或查找目标Service,跨进程建立Binder连接。
- 回调通知:连接成功或失败时,系统调用ServiceConnection对应方法。
4.1.2 ServiceConnection回调实现
客户端必须实现两个关键回调方法:
| 回调方法 | 触发时机 | 典型处理逻辑 |
|---|---|---|
onServiceConnected(ComponentName name, IBinder service) |
服务连接成功,Binder代理对象就绪 | 将IBinder转换为AIDL接口,保存引用,初始化监听器 |
onServiceDisconnected(ComponentName name) |
服务进程崩溃或异常断开 | 清理资源,标记服务不可用,触发重连逻辑 |
// CameraAIDLClient.java - 客户端核心实现片段
public class CameraAIDLClient {
private ICameraHALService mService;
private boolean mBound = false;
private Context mContext;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 将IBinder转换为AIDL接口
mService = ICameraHALService.Stub.asInterface(service);
mBound = true;
Log.d(TAG, "Service connected: " + name);
// 注册死亡通知
try {
service.linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
Log.e(TAG, "linkToDeath failed", e);
}
// 通知上层服务已就绪
onServiceReady();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.w(TAG, "Service disconnected: " + name);
mBound = false;
mService = null;
// 触发重连
attemptRebind();
}
};
private DeathRecipient mDeathRecipient = new DeathRecipient() {
@Override
public void binderDied() {
Log.e(TAG, "Binder died, cleaning up");
mBound = false;
mService = null;
// 清理并重连
attemptRebind();
}
};
public void bindToService() {
Intent intent = new Intent();
intent.setComponent(new ComponentName(
"com.android.camera.hal",
"com.android.camera.hal.CameraHALService"));
mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
}
最佳实践:始终在
onServiceConnected中调用linkToDeath()注册Binder死亡通知。这能捕获服务端进程被kill(非正常断开)的场景,而onServiceDisconnected仅在服务端进程崩溃时触发,两者互补。
4.2 异步调用与同步调用
AIDL默认支持同步调用(oneway=false)和异步调用(oneway=true)。在Camera HAL场景中,两者各有适用场景。
4.2.1 同步调用(阻塞)
客户端调用服务端方法后,当前线程阻塞等待服务端返回结果。适用于需要立即获取返回值的操作,如查询相机能力、获取帧数据。
// 同步调用示例 - 获取相机传感器信息
public CameraSensorInfo getSensorInfo(int cameraId) throws RemoteException {
// 当前线程会阻塞,直到服务端返回或超时
return mService.getSensorInfo(cameraId);
}
4.2.2 异步调用(oneway)
客户端调用后立即返回,不等待服务端处理完成。适用于配置下发、状态更新等不需要返回值的操作。
// AIDL定义
interface ICameraHALService {
oneway void setPreviewMode(int mode);
oneway void startPreview(int cameraId);
// 同步方法
CameraSensorInfo getSensorInfo(int cameraId);
}
| 特性 | 同步调用 | 异步调用(oneway) |
|---|---|---|
| 线程阻塞 | 是,调用线程等待 | 否,立即返回 |
| 返回值 | 支持 | 不支持(void) |
| 异常传递 | RemoteException可捕获 | RemoteException在服务端抛出,客户端无法直接捕获 |
| 适用场景 | 查询、配置确认、数据获取 | 命令下发、状态通知、日志 |
| 性能影响 | 可能阻塞UI线程,需在子线程调用 | 低延迟,不阻塞 |
注意:同步调用如果在主线程(UI线程)执行,可能导致ANR。Camera HAL客户端应始终在独立的工作线程中执行同步AIDL调用,或使用
AsyncTask/HandlerThread封装。
4.3 超时处理机制
AIDL同步调用没有内置超时机制,如果服务端因死锁、死循环或进程卡死而无法响应,客户端将永久阻塞。Camera HAL场景下,必须实现应用层超时保护。
4.3.1 超时策略对比
| 策略 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| Future + ExecutorService | 将AIDL调用提交到线程池,通过Future.get(timeout)等待 | 标准Java API,易于实现 | 超时后无法真正中断服务端线程 |
| Handler + 消息超时 | 发送延迟消息作为超时信号,配合计数锁 | 与Android消息循环集成好 | 实现较复杂,需处理竞态 |
| Binder自带超时 | 通过Binder的transact()设置超时参数(需定制) | 底层支持,精确 | 非标准AIDL,需修改框架 |
4.3.2 推荐实现:Future + 超时
// 带超时的AIDL调用封装
public class TimeoutAidlClient {
private static final int DEFAULT_TIMEOUT_MS = 3000;
private final ExecutorService mExecutor = Executors.newCachedThreadPool();
public CameraSensorInfo getSensorInfoWithTimeout(int cameraId, int timeoutMs)
throws TimeoutException, RemoteException {
Future<CameraSensorInfo> future = mExecutor.submit(() -> {
return mService.getSensorInfo(cameraId);
});
try {
return future.get(timeoutMs, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
// 超时处理:取消任务,记录日志
future.cancel(true);
Log.e(TAG, "getSensorInfo timed out after " + timeoutMs + "ms");
throw new TimeoutException("AIDL call timeout");
} catch (ExecutionException e) {
// 服务端抛出的RemoteException
Throwable cause = e.getCause();
if (cause instanceof RemoteException) {
throw (RemoteException) cause;
}
throw new RemoteException("Unexpected error", cause);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RemoteException("Interrupted", e);
}
}
}
4.4 客户端完整实现模板
综合以上要点,给出Camera HAL客户端的完整实现模板,包含绑定、回调、超时和资源管理。
// CameraHALClient.java - 完整客户端实现
public class CameraHALClient implements AutoCloseable {
private static final String TAG = "CameraHALClient";
private static final int AIDL_TIMEOUT_MS = 5000;
private ICameraHALService mService;
private final Context mContext;
private final ExecutorService mExecutor = Executors.newFixedThreadPool(2);
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
private boolean mBound = false;
// 连接状态监听器
public interface ConnectionListener {
void onConnected();
void onDisconnected();
void onError(int errorCode, String message);
}
private ConnectionListener mListener;
public CameraHALClient(Context context, ConnectionListener listener) {
mContext = context;
mListener = listener;
}
public void connect() {
Intent intent = new Intent();
intent.setComponent(new ComponentName(
"com.android.camera.hal",
"com.android.camera.hal.CameraHALService"));
boolean success = mContext.bindService(intent, mConnection,
Context.BIND_AUTO_CREATE);
if (!success) {
mListener.onError(-1, "bindService returned false");
}
}
// 带超时的同步调用封装
public <T> T callWithTimeout(Callable<T>> callable, int timeoutMs)
throws TimeoutException, RemoteException {
Future<T> future = mExecutor.submit(callable);
try {
return future.get(timeoutMs, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
future.cancel(true);
throw e;
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof RemoteException) {
throw (RemoteException) cause;
}
throw new RemoteException("Execution error", cause);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RemoteException("Interrupted", e);
}
}
// 示例:获取传感器信息(带超时)
public CameraSensorInfo getSensorInfo(int cameraId)
throws TimeoutException, RemoteException {
return callWithTimeout(() -> mService.getSensorInfo(cameraId), AIDL_TIMEOUT_MS);
}
// 异步调用(oneway)
public void startPreview(int cameraId) throws RemoteException {
mService.startPreview(cameraId);
}
@Override
public void close() {
if (mBound) {
mContext.unbindService(mConnection);
mBound = false;
}
mExecutor.shutdown();
mService = null;
}
private final ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = ICameraHALService.Stub.asInterface(service);
mBound = true;
try {
service.linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
Log.e(TAG, "linkToDeath failed", e);
}
mMainHandler.post(() -> mListener.onConnected());
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBound = false;
mService = null;
mMainHandler.post(() -> mListener.onDisconnected());
}
};
private final DeathRecipient mDeathRecipient = () -> {
Log.e(TAG, "Binder died");
mBound = false;
mService = null;
mMainHandler.post(() -> {
mListener.onDisconnected();
// 自动重连
connect();
});
};
}
工程落地检查清单:
- ✅ ServiceConnection回调中是否注册了DeathRecipient?
- ✅ 同步调用是否在子线程执行,避免ANR?
- ✅ 是否所有同步调用都设置了超时保护?
- ✅ 超时后是否正确处理了资源清理和重连?
- ✅ 是否在Activity/Fragment的onDestroy中调用了close()解绑?