Skip to content

in-out-inout语义

追踪 AIDL in/inout/out 从 parser AST、语义检查到 Java Stub/Proxy Parcel 代码的真实分支。

基于android-17.0.0_r1
AndroidAIDL参数方向编译器

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

cpp
%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

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 类型方向 ​

cpp
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限制 ​

cpp
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

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写回 ​

cpp
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 请求写入 ​

cpp
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读取 ​

cpp
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,不证明生成后的数据流。

bash
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 流向。