定义AIDL接口的通用步骤:
1. 定义AIDL文件(声明方法签名)。
2. 编译项目,自动生成Java接口。
3. Service端实现生成的Stub类。
4. 客户端绑定Service,通过Stub.asInterface()获取接口代理进行调用。
本章聚焦于第一步,即“定义AIDL文件”。
#### 3.4 服务接口示例:`IMyService.aidl`
以下是一个典型的数据处理服务接口。假设我们需要一个Service提供计算和文件处理功能,使用`aidl`/目录下的`IMyService.aidl`。
**文件位置**: `app/src/main/aidl/com/framework/example/service/IMyService.aidl`
1. 定义AIDL文件(声明方法签名)。
2. 编译项目,自动生成Java接口。
3. Service端实现生成的Stub类。
4. 客户端绑定Service,通过Stub.asInterface()获取接口代理进行调用。
// IMyService.aidl
package com.framework.example.service;
import com.framework.example.service.DataResult; // 导入自定义Parcelable类
/**
* 自定义数据服务接口
*/
interface IMyService {
/**
* 同步方法:计算两数之和(阻塞调用,等待结果)
*/
int add(int a, int b);
/**
* 同步方法:处理复杂数据,返回自定义Parcelable对象
*/
DataResult processData(in String data);
/**
* oneway异步方法:触发耗时操作(不阻塞客户端,不等待结果)
*/
oneway void startHeavyTask(String taskName);
}
**对应的Parcelable类**:`DataResult.java`(需声明`implements Parcelable`)
// DataResult.java
package com.framework.example.service;
public class DataResult implements Parcelable {
public int code;
public String message;
// 必须实现writeToParcel()、createFromParcel()等
// 省略具体实现...
}
**关键点**:
- 包名必须与AIDL中的`package`一致。
- 非基本类型参数需要加方向指示符:`in`(输入参数,默认)、`out`(输出参数)、`inout`(双向参数)。缺少方向指示符会导致编译错误。
- `oneway`只能用于返回`void`的方法,表示异步调用。
#### 3.5 AIDL语法与数据类型
AIDL默认支持的类型如下表所示:
| Java类型 | AIDL表示 | 说明 |
|-----------|-----------|------|
| `int` | `int` | 32位整型 |
| `long` | `long` | 64位整型 |
| `float` | `float` | 32位浮点型 |
| `double` | `double` | 64位浮点型 |
| `boolean` | `boolean` | 布尔型 |
| `String` | `String` | UTF-16字符串 |
| `CharSequence` | `CharSequence` | CharSequence接口 |
| `List// DataResult.aidl
package com.framework.example.service;
parcelable DataResult; // 只声明,不写方法
#### 3.6 `oneway`关键字深入解析
`oneway`是AIDL中控制IPC调用行为的修饰符。它改变了Binder调用的线程模型。
| 特性 | 普通同步调用 | `oneway`异步调用 |
|-------|---------------|-------------------|
| **调用行为** | 客户端线程阻塞等待服务端返回结果 | 客户端线程立即返回,不等待服务端处理完成 |
| **事务序列号** | 有(保证服务端顺序处理) | 无(可能乱序,但降低了调度延迟) |
| **返回值** | 必须返回具体值或抛出异常 | 只能返回`void` |
| **应用场景** | 需要结果的RPC调用,如查询、计算 | 纯通知、触发型操作,如日志上报、UI刷新信号 |
| **异常处理** | 可抛出`RemoteException`,客户端需捕获 | 抛不出异常(因为已返回),错误会被静默丢弃 |
性能陷阱:不要滥用`oneway`。如果服务端处理太慢,`oneway`不会阻塞客户端,但会导致Binder事务积压。当服务端`onTransact`速度慢于客户端发送速度时,Binder驱动缓冲填满会触发`DeadObjectException`或`TransactionTooLargeException`。建议仅用于“发送后不关心结果”的场景。
#### 3.7 编译与自动生成
将AIDL文件放在`src/main/aidl/`对应包路径下后,编译项目(`make` 或 Android Studio的`Build → Make Project`)。系统会调用`aidl`工具,生成对应的Java接口文件。
**生成后的接口示例**(位于`build/generated/aidl_source_output_dir/`):
// 自动生成的 IMyService.java
package com.framework.example.service;
public interface IMyService extends android.os.IInterface {
// 嵌套的Stub抽象类
public static abstract class Stub extends android.os.Binder implements IMyService {
// asInterface():将IBinder转换为接口
public static IMyService asInterface(android.os.IBinder obj) { ... }
// onTransact():Binder线程池处理IPC请求
@Override
public boolean onTransact(int code, android.os.Parcel data, ...) { ... }
}
public int add(int a, int b) throws android.os.RemoteException;
public DataResult processData(String data) throws android.os.RemoteException;
public void startHeavyTask(String taskName) throws android.os.RemoteException;
}
开发者验证:检查`build/generated/`目录下是否生成了`IMyService.java`。如果未生成,请确认AIDL文件语法正确、包名与Java目录一致、自定义类型声明了`.aidl`声明文件。
#### 3.8 结构总览:AIDL接口定义与编译流程图
下面这个SVG图展示了从AIDL源文件到自动生成Java接口,再到服务端/客户端使用的完整链路。
#### 3.9 常见错误与排查
| 错误现象 | 可能原因 | 解决方案 |
|-----------|----------|----------|
| 编译失败:`non-ASCII characters in AIDL` | AIDL文件中包含中文注释或字符串 | AIDL文件必须使用ASCII字符集;用英文注释 |
| `cannot find symbol class DataResult` | 未在`aidl`目录下声明`DataResult.aidl` | 创建对应的`parcelable DataResult;`声明文件 |
| `oneway method must return void` | 在非void方法上使用了`oneway` | 移除`oneway`或改为`void`返回类型 |
| `parameter ... missing direction specifier` | 非基础类型参数没有`in/out/inout`标记 | 添加`in`(或`out`/`inout`)方向指示符 |
| 客户端调用时抛出`RemoteException: BadParcelableException` | Parcelable的`writeToParcel` / `createFromParcel`实现与AIDL声明不一致 | 检查Parcelable类的字段顺序和类型与AIDL中的声明严格对齐 |
#### 3.10 性能与稳定建议
- **参数方向**:尽可能使用`in`以减少数据拷贝。`out`和`inout`会导致内存拷贝开销增大,尤其对大对象(如List、Map)影响明显。
- **oneway使用边界**:一次性最多发起16个没有返回的`oneway`调用(Binder线程池默认最大16个线程)。超出后,后续`oneway`调用会被挂起。建议在客户端做流量控制。
- **避免在AIDL中传递大对象**:Binder数据缓冲区限制为1MB(不同Android版本有差异)。传递大对象会导致`TransactionTooLargeException`。大文件/大图片考虑使用文件描述符(`ParcelFileDescriptor`)替代。
- **接口粒度**:将高频调用的方法声明为`oneway`,但要有兜底设计(如定时轮询结果)。将需要实时响应的方法保持为同步。
调试小技巧:可以在服务端的`onTransact()`中打印日志,查看每次IPC调用的方法ID、线程ID和参数大小。这有助于发现`oneway`调用的积压情况和参数传输瓶颈。
通过本章的学习,你已经掌握了AIDL接口定义的核心语法和设计原则。接下来的章节将基于此接口,实现服务端的`Stub`逻辑,并完成客户端的绑定与调用。