in-out-inout语义
AIDL 参数方向不是注释,而是编译器和生成代码共同消费的协议:未写方向时 AST 默认 in;in 参数在请求 Parcel 写入并由 Stub 读取;out 参数通常只在 reply 中返回;inout 同时进入请求和 reply。Android 17 还在语义检查中拒绝 oneway 方法的 out 参数,因为 oneway 没有同步 reply 通道。
本文面向已经读过 AIDL语法详解、AIDL编译器原理 和 Stub与Proxy代码生成 的读者。本文追踪 parser AST、方向合法性和 Java generator 分支,不泛讲业务 API 设计。
1. AST方向
1.1 Grammar token
源码文件:system/tools/aidl/aidl_language_y.yy
%token IN "in"
%token INOUT "inout"
%token OUT "out"
direction
: IN { $$ = AidlArgument::IN_DIR; }
| OUT { $$ = AidlArgument::OUT_DIR; }
| INOUT { $$ = AidlArgument::INOUT_DIR; };parser 将 token 转为 AidlArgument::Direction。如果参数只写 type/name,构造函数把 direction 设为 IN_DIR,但 direction_specified_ 保持 false,用于后续诊断。
1.2 AST字段
源码文件:system/tools/aidl/aidl_language.cpp
AidlArgument::AidlArgument(
const AidlLocation& location,
Direction direction,
AidlTypeSpecifier* type,
const std::string& name)
: AidlVariableDeclaration(location, type, name),
direction_(direction),
direction_specified_(true) {}
AidlArgument::AidlArgument(
const AidlLocation& location,
AidlTypeSpecifier* type,
const std::string& name)
: AidlVariableDeclaration(location, type, name),
direction_(IN_DIR),
direction_specified_(false) {}默认 in 与显式 in 在运行时读写方向相同,但 AST 能区分是否显式写出;这影响类型检查错误信息和语言规则。
2. 合法性检查
2.1 类型方向
bool AidlArgument::CheckValid(
const AidlTypenames& typenames) const {
if (!GetType().CheckValid(typenames)) return false;
const auto& aspect =
typenames.GetArgumentAspect(GetType());
if (aspect.possible_directions.empty()) return false;
if (!DirectionWasSpecified() &&
aspect.possible_directions !=
std::set{AidlArgument::IN_DIR}) {
return false;
}
if (aspect.possible_directions.count(
GetDirection()) == 0) {
return false;
}
return true;
}类型本身声明可用方向集合;未显式写方向时只有“只能 in”的类型允许省略,否则编译器要求作者明确写方向。方向合法性依赖类型,不是三个关键字无条件适用于所有类型。
2.2 Oneway限制
if (IsOneway() && arg->IsOut()) {
AIDL_ERROR(this)
<< "oneway method cannot have out parameters";
return false;
}oneway 接口/方法没有同步 reply,因此任何 out 或 inout(包含 out 位)都被拒绝。该错误在 semantic validation,不是 parser grammar error。
2.3 参数名
同一方法参数名不能重复,且不能使用 Java/AIDL keyword 或以 _aidl 开头;这属于生成命名空间约束,与方向检查同时发生。
3. Java Stub生成
3.1 请求读取
源码文件:system/tools/aidl/generate_java_binder.cpp
for (const auto& arg : method.GetArguments()) {
if (arg->GetDirection() & AidlArgument::IN_DIR) {
CreateFromParcelFor(context);
} else {
// out parameter is instantiated before impl call
instantiate out value;
}
}in/inout 参数在调用实现前从 request Parcel 创建;纯 out 参数不从请求读取,而是在服务端调用前创建一个可写回的对象。容器/数组的实际初始化还由类型和 backend helper 决定。
3.2 Reply写回
for (const auto& arg : method.GetArguments()) {
if (arg->GetDirection() & AidlArgument::OUT_DIR) {
GenerateWriteToParcel(
statements, typenames, arg->GetType(),
transact_reply->name, arg->GetName());
}
}inout 和 out 都带 OUT_DIR,因此服务实现对它们的修改在 reply 中序列化;in 只有请求读取,不生成参数回写。
4. Java Proxy生成
4.1 请求写入
for (const auto& arg : method.GetArguments()) {
auto dir = arg->GetDirection();
if (dir == AidlArgument::OUT_DIR &&
arg->GetType().IsDynamicArray()) {
out << "_data.writeInt(" << arg->GetName()
<< ".length);";
} else if (dir & AidlArgument::IN_DIR) {
GenerateWriteToParcel(
out, typenames, arg->GetType(),
"_data", arg->GetName());
}
}Proxy 写 in/inout;纯 out 动态数组先写长度,让远端按相同大小创建输出数组。纯 out 非数组参数不会作为输入值写入。
4.2 Reply读取
if (!oneway) {
out << "_reply.readException();";
for (const auto& arg : method.GetArguments()) {
if (arg->GetDirection() & AidlArgument::OUT_DIR) {
ReadFromParcelFor(context);
}
}
}同步方法读取 exception 和 out/inout 参数;oneway 没有 reply,因此 semantic validation 已禁止 out/inout 组合。
5. 实际影响
in 是单向输入快照;inout 是双向参数但通常需要可变容器/对象;out 是返回载体。方向不会自动复制业务对象,也不保证调用端修改原始对象会被远端看到,实际效果由生成的 Parcel helper 和类型定义决定。
6. 测试边界
源码文件:system/tools/aidl/generate_java_binder.cpp
Generator 代码本身按 GetDirection() 分支生成 request/reply;AIDL golden Java output 用真实接口覆盖数组、Parcelable、异常和方向组合。解析器测试只证明 token/AST,不证明生成后的数据流。
rg -n "Direction|IN_DIR|OUT_DIR|INOUT_DIR|direction_specified" system/tools/aidl/aidl_language_y.yy system/tools/aidl/aidl_language.cpp
rg -n "GetDirection|IsIn|IsOut|CreateFromParcelFor|GenerateWriteToParcel|ReadFromParcelFor" system/tools/aidl/generate_java_binder.cpp
rg -n "oneway method|cannot have out parameters|inout|out" system/tools/aidl读者可以给同一 method 分别写 implicit in、explicit in、inout、out 和 oneway+out,先观察 AST/semantic error,再查看生成 Proxy 请求与 Stub reply 的差异;不要只看接口签名判断实际 Parcel 流向。
