4. 实现 Service Stub:生成 AIDL Stub 类、override 方法、权限检查、异常处理
4.1 原理:AIDL Stub 类与 Binder 通信模型
Android Framework 中,自定义系统级 Service 必须通过 Binder 对外暴露接口。AIDL(Android Interface Definition Language)编译器会为声明的接口自动生成一个名为 Stub 的抽象内部类,它继承自 android.os.Binder 并实现了你在 .aidl 文件中定义的接口。客户端通过 Stub.asInterface() 拿到代理,发起 IPC 调用;服务端则继承 Stub 类,重写接口方法,完成实际业务逻辑。
关键职责:Stub 类承担了编组/解组(marshalling/unmarshalling)、线程模型切换、事务码分发等底层工作,开发者只需专注于业务方法的实现、权限校验和异常处理。
4.2 实现流程
| 步骤 | 操作 | 产出物 |
|---|---|---|
| 1 | 编写 .aidl 接口文件 | 定义方法签名、参数、返回类型 |
| 2 | 编译 AIDL 文件,生成 Stub 抽象类 | IXXXService.Stub 类 |
| 3 | 在自己的 Service 类中创建内部类,继承 Stub | MyService.StubImpl |
| 4 | Override AIDL 接口的所有方法 | 业务逻辑实现 |
| 5 | 在方法入口添加权限检查 | checkCallingPermission() |
| 6 | 处理 RemoteException 及业务异常 | try-catch / throw |
| 7 | 在 onBind() 中返回 Stub 实例 | IBinder |
4.3 代码实现
4.3.1 AIDL 接口定义
// frameworks/base/core/java/com/example/IMyService.aidl
package com.example;
interface IMyService {
String getData(int id);
int setData(String value) throws android.os.RemoteException;
}
4.3.2 编译后生成的 Stub 类(简化展示)
// 自动生成:out/soong/.../IMyService.java
public interface IMyService extends android.os.IInterface {
public static abstract class Stub extends android.os.Binder implements IMyService {
private static final String DESCRIPTOR = "com.example.IMyService";
public Stub() {
attachInterface(this, DESCRIPTOR);
}
@Override
public boolean onTransact(int code, android.os.Parcel data,
android.os.Parcel reply, int flags)
throws android.os.RemoteException {
switch (code) {
case TRANSACTION_getData:
data.enforceInterface(DESCRIPTOR);
int _id = data.readInt();
// 权限检查可在此处
String _result = getData(_id);
reply.writeNoException();
reply.writeString(_result);
return true;
case TRANSACTION_setData:
data.enforceInterface(DESCRIPTOR);
String _value = data.readString();
// 权限检查
int _result = setData(_value);
reply.writeNoException();
reply.writeInt(_result);
return true;
default:
return super.onTransact(code, data, reply, flags);
}
}
// 以下为事务码常量
static final int TRANSACTION_getData = (IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_setData = (IBinder.FIRST_CALL_TRANSACTION + 1);
}
}
4.3.3 自定义 Service 继承 Stub 并 override 方法
// MyService.java
public class MyService extends Service {
private final IMyService.Stub mBinder = new IMyService.Stub() {
@Override
public String getData(int id) throws RemoteException {
// 权限检查
if (checkCallingOrSelfPermission("com.example.permission.ACCESS_MY_SERVICE")
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Permission denied");
}
// 业务逻辑
return "data for id=" + id;
}
@Override
public int setData(String value) throws RemoteException {
// 权限检查
enforceCallingPermission("com.example.permission.ACCESS_MY_SERVICE", null);
// 业务逻辑
return 0;
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
4.4 权限检查详解
Binder 调用方可以是任意进程,必须验证调用者是否具备权限。Framework 为 Service 提供了两种方法:
checkCallingOrSelfPermission(String permission)– 返回是否授予权限(不抛异常)enforceCallingPermission(String permission, String message)– 无权限时直接抛出SecurityException
权限字符串需在 AndroidManifest.xml 中声明(system 级 Service 通常使用 android.permission 或自定义 signature 级权限):
<permission android:name="com.example.permission.ACCESS_MY_SERVICE"
android:protectionLevel="signature" />
注意:权限检查应在
onTransact() 层或业务方法入口进行,避免在已经处理部分数据后才拒绝,造成不一致状态。同时建议使用 Binder.getCallingUid() 做更精细的授权决策。
4.5 异常处理策略
| 异常类型 | 说明 | 处理方法 |
|---|---|---|
RemoteException | Binder 通信失败(死进程、事务过大) | 通知客户端,记录日志,清理资源 |
SecurityException | 权限不足 | 由 enforceCallingPermission 自动抛出 |
BadParcelableException | 反序列化数据异常 | 在 onTransact 中 try-catch 并返回失败 |
| 业务自定义异常 | 例如参数校验失败 | 通过 reply.writeException() 传播给客户端 |
推荐在 Stub 的 onTransact() 内包裹 try-catch,统一处理未预期异常:
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
try {
return super.onTransact(code, data, reply, flags);
} catch (Exception e) {
reply.writeException(e);
return true;
}
}
4.6 调试与验证
- 日志:在 Stub 方法内添加
Log.d("MyService", "getData called from uid=" + Binder.getCallingUid()); - dumpsys:在 Service 中实现
dump()方法,通过adb shell dumpsys activity service <component>查看状态 - 测试客户端:编写一个简单的 App 绑定 Service,调用方法并捕获返回或异常
4.7 常见错误
| 错误现象 | 原因 | 修正 |
|---|---|---|
客户端抛出 SecurityException | 未声明或未授予权限 | 检查 Manifest 权限声明及保护级别 |
| 方法返回 null 引发 NPE | Stub 方法内未处理空指针 | 对返回对象做 Objects.requireNonNull() 检查 |
| 客户端一直阻塞 | Stub 方法执行耗时任务,阻塞 Binder 线程 | 改用 oneway 或异步处理 |
| Parce 读写顺序不一致 | 自定义 Parcelable 字段顺序与 AIDL 声明不同 | 确保 parcel 读写顺序完全一致 |
4.8 性能与稳定性优化
- 减少跨进程数据传输:避免在方法中传递大 Bitmap 或长 List,改用文件描述符或 ContentProvider
- 使用 oneway 声明:在 AIDL 方法前加
oneway关键字,使调用不阻塞客户端(适用于通知类接口) - 线程池隔离:如果 Service 需要执行耗时操作,在 Stub 方法内抛到自己的 HandlerThread 中,避免占用 Binder 线程池
- 资源释放:在
onTransact()或方法结束后,确保 Parcel 不会泄露
最佳实践:建议在 Stub 子类中统一实现
getInterfaceHash() 和 getInterfaceVersion()(AIDL 3.0+),便于客户端校验接口一致性,避免运行时因接口不匹配导致死机。
通过以上步骤,你就能在 Framework 层成功实现一个健壮、安全的 Service Stub,为后续注册到 ServiceManager 并提供给其他进程调用打下坚实基础。