4. AIDL接口定义语言:AIDL语法详解、定向Tag、复杂类型传递、AIDL文件编译流程
各位同学,今天我们来聊聊AIDL。说实话,AIDL是车载Android系统里最基础也最核心的跨进程通信工具。我见过不少新人,一上来就照着模板写,结果遇到复杂数据类型传递就翻车。嗯,今天咱们就把AIDL的底裤扒干净。
4.1 AIDL语法详解:从Hello World到生产级
AIDL的语法,说白了就是定义接口的契约。你想想看,两个进程要通信,总得有个双方都认可的协议吧?AIDL就是这个协议。
先看一个最基础的例子:
// IMyService.aidl
package com.example.car;
interface IMyService {
int add(int a, int b);
String getCarInfo(String vin);
}
这里要注意几点:
- 包名必须一致:AIDL文件的包名要和生成的Java文件包名对应。我见过有人把包名写错,编译报错找了半天。
- 接口名以I开头:这是Android的惯例,不是强制要求,但建议遵守。
- 方法参数和返回值:支持基本类型、String、CharSequence、List、Map,以及Parcelable对象。
4.2 定向Tag:in/out/inout 到底怎么用?
这是AIDL里最容易让人迷糊的地方。我刚开始做车载项目时,也在这上面栽过跟头。
定向Tag决定了数据在进程间的传递方向:
| Tag | 含义 | 适用场景 |
|---|---|---|
| in | 数据从客户端流向服务端 | 客户端传参数给服务端,服务端只读 |
| out | 数据从服务端流向客户端 | 服务端填充数据,客户端读取 |
| inout | 双向流动 | 客户端传数据,服务端修改后返回 |
举个例子:
interface ICarService {
// 客户端传入vin,服务端查询
CarInfo getCarInfo(in String vin);
// 服务端填充CarInfo对象
void fillCarInfo(out CarInfo info);
// 客户端传入CarInfo,服务端修改后返回
void updateCarInfo(inout CarInfo info);
}
为什么会这样?因为AIDL的底层实现是Binder驱动。in参数会序列化后发送到服务端,out参数会先创建一个空对象,服务端填充后序列化回客户端。inout则是双向序列化,性能开销最大。
我的建议:能用in就别用out,能用out就别用inout。性能开销依次递增。在车载系统中,尤其是高频调用的场景(比如传感器数据),尽量用in。
4.3 复杂类型传递:Parcelable与自定义对象
基本类型好办,但车载系统里经常要传递复杂对象,比如车辆状态、诊断信息、导航路径等。这时候就需要Parcelable了。
步骤很简单:
- 定义Java类,实现Parcelable接口
- 创建同名的.aidl文件,声明为parcelable
- 在接口AIDL文件中使用该类型
看个例子:
// CarInfo.java
public class CarInfo implements Parcelable {
private String vin;
private int speed;
private double batteryLevel;
// 实现Parcelable接口的方法...
}
// CarInfo.aidl
package com.example.car;
parcelable CarInfo;
// ICarService.aidl
import com.example.car.CarInfo;
interface ICarService {
CarInfo getCarInfo(String vin);
void setCarInfo(CarInfo info);
}
另外,List和Map也是常用的复杂类型。AIDL支持泛型,但有限制:
- List<String>、List<Parcelable> 没问题
- Map<String, Parcelable> 也可以
- 但List<List<String>> 这种嵌套就不支持了
嗯,这里要注意:如果你需要传递嵌套结构,建议封装成一个Parcelable对象。
4.4 AIDL文件编译流程:从.aidl到.java
很多同学只知道写AIDL,不知道背后发生了什么。其实编译流程很简单:
- SDK工具链:Android SDK中的aidl工具会解析.aidl文件
- 生成Java接口:生成对应的.java文件,包含Stub和Proxy内部类
- 编译Java文件:和普通Java代码一起编译成.dex
具体来说,生成的Java文件结构是这样的:
// 生成的 IMyService.java
public interface IMyService extends android.os.IInterface {
// Stub类:服务端继承
public static abstract class Stub extends android.os.Binder implements IMyService {
// 实现了Binder机制的核心逻辑
// onTransact() 方法处理IPC请求
}
// Proxy类:客户端使用
private static class Proxy implements IMyService {
// 封装了transact()调用
// 负责序列化参数、反序列化返回值
}
}
我在调试车载系统时,经常直接看生成的Java代码。有时候AIDL语法没问题,但生成的代码逻辑不对,这时候就得手动检查Stub和Proxy的实现。
编译流程中还有一个容易忽略的点:AIDL文件的依赖关系。如果你的AIDL引用了其他AIDL中定义的类型,必须确保依赖的AIDL文件也在编译路径中。在Gradle中配置:
android {
...
sourceSets {
main {
aidl.srcDirs = ['src/main/aidl', 'src/common/aidl']
}
}
}
说白了,AIDL编译就是一次代码生成过程。你写的是接口定义,工具帮你生成跨进程通信的骨架代码。理解了这个,你就能更好地驾驭AIDL。
总结一下
今天的内容不少,我帮你理一下重点:
- AIDL语法:包名、接口、方法定义,注意基本类型和Parcelable的区别
- 定向Tag:in是单向输入,out是单向输出,inout是双向。性能依次降低,能用in就别用inout
- 复杂类型:Parcelable是王道,注意读写顺序一致。List和Map有限制,嵌套结构要封装
- 编译流程:aidl工具生成Java代码,Stub和Proxy是核心。调试时多看看生成的代码
下一章我们会讲Binder的工作原理,到时候你会更清楚AIDL生成的代码到底在干什么。嗯,今天就到这里,有问题随时问我。