10、AIDL 语言基础:AIDL 的语法、支持的数据类型、定向 tag(in、out、inout)
好,我们开始聊 AIDL。很多同学一听到 AIDL 就觉得头大,觉得这是 Binder 通信里最绕的一环。其实说白了,AIDL 就是一份“接口说明书”。你想想看,两个进程要聊天,总得约定好聊什么、怎么聊吧?AIDL 就是干这个的。
我个人习惯把 AIDL 比作“跨进程的接口定义语言”。它不负责具体干活,只负责描述:我这个服务能提供哪些方法,参数是什么类型,返回值是什么。至于底层怎么序列化、怎么传数据,那是 Binder 驱动的事,AIDL 只负责生成模板代码。
10.1 AIDL 的基本语法
先看一个最简单的例子。假设我们要定义一个图书管理服务的接口:
// IBookManager.aidl
package com.example.aidl;
import com.example.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
嗯,这里要注意几点:
- 包名必须一致:AIDL 文件的包名,要和实际 Java 接口的包名保持一致。我见过不少新手在这里栽跟头,编译报错半天找不到原因。
- import 语句不能少:如果方法参数或返回值用到了自定义类型(比如上面的 Book),必须显式 import。就算在同一个包下,也得写。
- 接口名以 I 开头:这不是强制要求,但 Android 源码里都这么干,咱们也入乡随俗。
10.2 支持的数据类型
AIDL 不是万能的,它只支持有限的数据类型。我整理了一张表,你一看就明白:
| 类别 | 支持的类型 | 说明 |
|---|---|---|
| 基本类型 | int, long, float, double, boolean, byte, char, short | 直接支持,不需要任何额外操作 |
| String | String | 默认定向为 in,不可修改 |
| CharSequence | CharSequence | 和 String 类似,常用于文本 |
| List | ArrayList 或泛型 List | 元素类型必须是 AIDL 支持的 |
| Map | HashMap 或泛型 Map | key 和 value 都必须是 AIDL 支持的 |
| Parcelable | 自定义类实现 Parcelable 接口 | 必须显式 import,且在同包名下 |
| 其他 AIDL 接口 | IBinder 类型 | 用于传递 Binder 对象本身 |
为什么会这样?因为跨进程传输时,数据要经过序列化和反序列化。基本类型和 String 是“天生自带序列化”的,而自定义类必须实现 Parcelable 接口,告诉系统怎么拆包、怎么装包。
10.3 定向 tag:in、out、inout
这是 AIDL 里最容易让人迷糊的地方。我刚开始学的时候也绕了好久。其实你只要记住一句话:定向 tag 决定了数据往哪个方向流动。
我们画个图来理解:
看图说话:
- in:数据从客户端流向服务端。客户端把数据打包好,服务端收到后直接读。服务端改了参数,客户端那边不会感知到。
- out:数据从服务端流向客户端。客户端传一个“空壳子”过去,服务端往里面填数据,填完再传回来。
- inout:双向流动。客户端传数据过去,服务端可以改,改完再传回来。这是开销最大的模式。
看个实际例子就明白了:
// IStudentManager.aidl
interface IStudentManager {
// in:客户端传学生ID,服务端返回姓名
String getNameByStudentId(in int studentId);
// out:客户端传空对象,服务端填数据
void getStudentInfo(out Student student);
// inout:客户端传学生对象,服务端修改后返回
void updateStudentInfo(inout Student student);
}
这里有个细节:基本类型和 String 默认就是 in,而且不能改成 out 或 inout。为什么?因为基本类型是值传递,你改了副本,原值不会变。String 虽然是引用类型,但它是不可变的,改了也没意义。
10.4 定向 tag 的底层原理
你可能会问:定向 tag 到底影响了什么?其实它影响的是 Binder 驱动在序列化时的行为。
- in:只调用
writeToParcel(),把数据写进去。服务端收到后,调用createFromParcel()读出来。 - out:只调用
createFromParcel()创建一个空对象。服务端改完后,再调用writeToParcel()写回去。 - inout:先
writeToParcel(),再createFromParcel(),服务端改完后再writeToParcel(),客户端再createFromParcel()。来回折腾两趟。
所以你看,inout 比 in 多了一倍的序列化操作。如果你的对象里有很多字段,这个开销就很可观了。
10.5 总结一下
AIDL 其实不复杂,你只要记住三件事:
- 语法:包名要对,import 要写,接口名规范。
- 数据类型:基本类型、String、List、Map、Parcelable。别传不认识的对象。
- 定向 tag:in 是单向传给服务端,out 是服务端填数据传回来,inout 是双向。能用 in 就别用 inout。
嗯,这一节就到这里。下一节我们会聊 AIDL 的实际使用,包括如何定义 Parcelable 类、如何编译 AIDL 文件、以及生成的 Java 接口长什么样。到时候我会拿一个完整的项目例子来讲,保证你一看就懂。
公众号:蓝海资料掘金营,微信deep3321