网络栈配置工具

Android操作系统包含标准的Linux网络工具,如ifconfig、ip和ip6tables。这些实用程序驻留在系统映像上,并支持整个Linux网络堆栈的配置。在运行Android 7.x的设备和更早的时候。供应商代码可以直接调用这些二进制文件,这将带来以下问题:

  • 因为网络实用程序在系统映像中被更新,所以它们不能提供稳定的实现。
  • 网络实用工具的范围如此广泛,在保证可预测行为的同时,很难改进系统映像。

在运行Android 8.0的设备上,当系统分区收到更新时,供应商分区可以保持不变。为了实现这一点,Android 8.0提供了定义一个稳定的、版本化的接口的能力,同时还可以使用SELinux限制来保持供应商和系统映像之间的相互依赖关系,从而达到一个已知的好的组合。

供应商可以使用平台提供的网络配置实用程序来配置Linux网络栈,但是这些实用程序还没有包含HIDL接口包装器。为了定义这样一个接口,Android 8.0包含了netutils-wrapper-1.0工具。

Netutils wrapper

netutils wrapper 工具提供了不受系统分区更新影响的Linux网络堆栈配置的子集。Android 8.0包含了wrappers的1.0版本,它允许您将与封装的实用程序相同的参数传递给系统分区/系统/bin,如下所示:

>u:object_r:system_file:s0           /system/bin/ip-wrapper-1.0 -> netutils-wrapper-1.0
>u:object_r:system_file:s0           /system/bin/ip6tables-wrapper-1.0 -> netutils-wrapper-1.0
>u:object_r:system_file:s0           /system/bin/iptables-wrapper-1.0 -> netutils-wrapper-1.0
>u:object_r:system_file:s0           /system/bin/ndc-wrapper-1.0 -> netutils-wrapper-1.0
>u:object_r:netutils_wrapper_exec:s0 /system/bin/netutils-wrapper-1.0
>u:object_r:system_file:s0           /system/bin/tc-wrapper-1.0 -> netutils-wrapper-1.0

Symlinks显示了由netutils wrapper 打包的网络工具,其中包括:

  • ip
  • iptables
  • ip6tables
  • ndc
  • tc

要在Android 8.0和之后使用这些实用程序,供应商实现必须遵守以下规则:

  • 供应商进程不能直接执行 /system/bin/netutils-wrapper-1.0 ;这样做会导致错误。
  • 所有由netutils-wrapper-1.0 打包的工具都必须使用它们的symlinks 启动。例如,更改以前的供应商代码(/system/bin/ip < FOO> < BAR> )到 /system/bin/ip-wrapper-1.0 < FOO> < BAR>。
  • 在平台SELinux策略中禁止执行没有域转换的包装器。这条规则不能更改,并且在Android兼容测试套件(CTS)中进行测试。
  • 在平台SELinux策略中,从供应商进程中直接执行工具(例如 /system/bin/ip < FOO> < BAR>)也是被禁止的。此规则不可更改,并在CTS中进行测试。
  • 需要启动包装器的任何供应商域(进程)都必须在SELinux策略中添加以下域转换规则:domain_auto_trans(VENDOR-DOMAIN-NAME netutils,netutils_wrapper_exec, netutils_wrapper)。

Netutils wrapper filters

打包的工具几乎可以用于配置Linux网络堆栈的任何方面。但是,为了确保能够维护一个稳定的接口并允许对系统分区进行更新,只允许某些命令行参数的组合;其他命令将被拒绝。

Vendor interfaces and chains

wrapper 有供应商接口的概念。这些接口通常由厂商代码管理,比如蜂窝数据接口。通常,其他类型的接口(比如Wi-Fi)由HALs和框架管理。wrapper 通过名称(使用正则表达式)识别供应商接口,并允许供应商代码对它们执行许多操作。目前,厂商接口有:

  • 名称以“oem”结尾的接口,后跟一个数字,如 oem0 或r_oem1234
  • 当前SOC和OEM实现所使用的接口,如rmnet_data[0-9]。

通常由框架(比如 wlan0)管理的接口的名称从来不是供应商接口。

wrapper 的概念类似于 vendor chains。这些在iptables命令中使用,并且也被命名。目前,vendor chains 有:

  • oem_ 开头的
  • 被当前的SOC和OEM的实现使用,例如,nm_或qcom_开头的chains.。

Allowed commands

下面列出了当前允许的命令。通过执行的命令行上的一组正则表达式实现限制。

ip

ip命令用于配置ip地址、路由、IPsec加密和其他一些网络参数。包装器允许以下命令:

  • 从供应商管理的接口中添加和删除IP地址。
  • 配置IPsec加密。

iptables/ip6tables

iptables 和 ip6tables 命令用于配置防火墙、包管理、NAT和其他包的处理。wrapper 允许以下命令:

  • 添加和删除 vendor chains。
  • 添加和删除任何 chain 中的规则,这些规则指的packets going into (-i) or out of (-o) a vendor interface.。
  • 从任意vendor chain的任何点跳到一个vendor chain。

ndc

ndc用于与在Android上执行大多数网络配置的netd守护进程通信。包装器允许以下命令:

  • 创建和销毁OEM网络(oemXX)。
  • 向OEM网络添加供应商管理的接口。
  • 为OEM网络添加路由。
  • 启用或禁用全局和供应商接口的IP转发。

tc

tc命令用于配置在供应商接口上的流量控制。

Threading Models

标记为oneway的方法不阻塞。对于未标记为oneway的方法,客户端的方法调用将阻塞直到服务器完成执行或调用同步回调。服务端方法实现最多可以调用一个同步回调;额外的回调用被丢弃并记录为错误。如果一个方法应该通过回调返回一个值,并且不调用它的回调,那么这个方法就会被记录为一个错误,并报告给客户端传输错误。

Threads in passthrough mode

在passthrough模式中,大多数调用是同步的。然而,为了保持预期的行为,oneway 调用不阻塞客户端,为每个进程创建一个线程。有关详细信息,请参见HIDL overview。

Threads in binderized HALs

为了服务传入的RPC调用(包括从HALs到HAL用户的异步回调)和死亡通知,一个threadpool与使用HIDL的每个进程相关联。如果一个进程实现多个HIDL接口和/或死亡通知处理程序,那么它的threadpool将在它们之间共享。当进程接收来自客户端的传入方法调用时,它从threadpool中选择一个空闲线程,并在该线程上执行调用。如果没有空闲线程可用,它将阻塞直到有可用线程。

如果服务端只有一个线程,那么对服务器的调用将按顺序完成。即使客户端只有一个线程,具有多个线程的服务端也可能不安顺序完成调用。由于 oneway 调用不阻塞客户端,多个单向调用可以同时处理或由具有多个线程的服务器进行处理,并且单向调用可以并发地处理后续的阻塞调用。

Server threading model

除了passthrough模式之外,服务端的HIDL接口实现在与客户端不同的进程,需要一个或多个线程等待传入的方法调用。这些线程是服务端的threadpool;服务端可以决定在它的threadpool中需要运行多少线程,并且可以使用一个线程池大小来序列化其接口上的所有调用。如果服务器在threadpool中有多个线程,它可以在任何接口上接收并发的传入调用(在c++中,这意味着必须小心地锁定共享数据)。

Oneway 在相同的接口的调用被序列化。如果一个多线程客户端在接口IFoo上调用method1和method2,在接口IBar上调用method3, method1和method2将永远被序列化,但是method3可以与method1和method2并行运行。

单个客户端执行线程可以在多个线程的服务器上以两种方式并发执行:

  • oneway 不阻塞。如果一个 oneway 调用被执行,然后调用一个非oneway,服务器可以同时执行oneway调用和非oneway调用。
  • 通过同步回调传递数据的服务器方法可以在从服务器调用回调时解除客户机的阻塞。

对于第二种方法,调用回调后执行的服务端函数中的任何代码都可以并发执行,服务端处理来自客户机的后续调用。这包括服务端函数中的代码和在函数末尾执行的自动析构函数。如果服务器在它的线程池中有多个线程,那么即使只有一个客户机线程调用,并发问题也会出现。(如果进程需要多个线程,那么所有HALs都会有多个线程,因为threadpool是每个进程共享的。)

一旦服务器调用回调,传输就可以调用客户机上实现的回调,并打开客户端。客户机在调用回调(可能包括运行析构函数)之后,与服务器实现并行执行。在回调之后,服务提供的函数中的代码不再阻塞客户机(只要服务器threadpool有足够的线程来处理传入的调用),但是可以并发地执行来自客户机的未来调用(除非服务器threadpool只有一个线程)。

除了同步回调之外,单线程客户端的 oneway 调用可以由具有多个线程的服务器并发处理,但只有在不同的接口上执行这些单向调用时才可以。在同一接口上的单向调用总是序列化的。

例子(C++):

Return<void> someMethod(someMethod_cb _cb) {
    // Do some processing, then call callback with return data
    hidl_vec<uint32_t> vec = ...
    _cb(vec);
    // At this point, the client's callback will be called,
    // and the client will resume execution.
    ...
    return Void(); // is basically a no-op
};

Client threading model

客户机上的线程模型不同于非阻塞调用(以oneway关键字标记的函数)和阻塞调用(没有指定oneway关键字的函数)。

Blocking calls

对于阻塞调用,客户端阻塞,直到其中一个发生:

  • 传输发生错误;返回对象包含一个错误状态,可以用Return::isOk()检索。
  • 服务端实现调用回调(如果有的话)。
  • 服务端实现返回一个值(如果没有回调参数)。

在成功的情况下,客户端作为参数传递的回调函数通常在函数本身返回之前被服务器调用。回调是在函数调用的同一线程上执行的,所以在函数调用期间,实施者必须小心持有锁(并且在可能的情况下尽量避免)。没有 generates 语句或oneway关键字的函数仍然阻塞;客户端阻塞,直到服务器返回 Return< void> 对象。

Oneway calls

当一个函数被标记为oneway时,客户端立即返回,并且不等待服务器完成其函数调用。

Converting HAL Modules

您可以通过在 hardware/libhardware/include/hardware 中转换头文件来更新已存在的HAL模块到HIDL HAL模块。

Using c2hal

c2hal工具处理大部分转换工作,减少所需的手动更改的数量。例如,为NFC HAL生成HIDL .hal文件:

$ make c2hal
c2hal -r android.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport -p android.hardware.nfc@1.0 hardware/libhardware/include/hardware/nfc.h

这些命令添加 hardware/interfaces/nfc/1.0/ 中的文件。从 ANDROID_BUILD_TOP 文件夹运行 hardware/interfaces/update-makefiles.sh 也会向HAL添加所需的makefile。从这里,您可以手动更改以完全转换HAL。

c2hal activities

当运行c2hal时,头文件中的所有内容都被转移到.hal文件。

c2hal识别在提供的头文件中包含函数指针的结构,并将每个结构转换成一个单独的接口文件。例如,alloc_device_t被转换为IAllocDevice HAL模块(在文件IAllocDevice.hal中)。

所有其他数据类型都被复制到一个 types.hal 文件。psound - definition被移动到enums中,而不是HIDL或不可转换(例如static-function声明)的部分被复制到带有文本“NOTE”的注释中。

Manual activities

c2hal工具在遇到某些结构时不知道该做什么。例如,HIDL没有原始指针的概念;因此,当c2hal在头文件中遇到一个指针时,它不知道指针是否应该被解释为一个数组,或者作为对另一个对象的引用。空指针也同样不可译。

在向HIDL转化期间,必须手动删除 int reserved[7] 等字段。返回值的名称应该更新为更有意义的内容;例如,将从自动生成的NFC的 write方法 int32_t write_ret中的返回参数转换为 Status status (Status 是一个包含可能的NFC状态的新枚举)。

Implementing the HAL

在创建了. hal文件以表示您的HAL之后,您必须生成c++和Java(除非HAL使用Java中不支持的特性)语言支持的makefile (Make或Soong)。./hardware/interfaces/update-makefiles.sh 脚本可以为位于 hardware/interfaces 目录中的HALs自动生成makefile(对于其他位置的HALs,只需更新脚本)。

当makefile更新时,您就可以生成头文件和实现方法了。有关实现生成接口的详细信息,请参阅HIDL c++(用于c++实现)或HIDL Java(用于Java实现)。

Data Types

本节描述HIDL数据类型。对于实现细节,请参阅HIDL c++(对于c++实现)或HIDL Java(对于Java实现)。

c++的相似之处包括:

  • struct 使用c++语法;在默认情况下,unions 支持c++语法;不支持匿名 struct 和 union。
  • 可以将c++风格的注释复制到生成的头文件中。

与JAVA的相似之处包括:

  • 对于每个文件,HIDL都定义了一个java风格的命名空间,必须以 android.hardware. 开头。生成c++名称空间是 ::android::hardware::…
  • 文件的所有定义都包含在java风格的接口包装中。
  • HIDL数组声明遵循Java风格,而不是c++风格。
  • 注释与javadoc格式类似

例子:

// JAVA 数组定义
struct Point {
    int32_t x;
    int32_t y;
};
Point[3] triangle;   // sized array

Data representation

一个由标准布局组成的 struct 或 union(普通数据类型的子集)在生成的c++代码中具有一致的内存布局,在 struct 和 union 成员上执行显式的对齐属性。

原始的HIDL类型,以及 enum 和 bitfield 类型(它们总是从原始类型派生),映射到标准c++类型,如 cstdint 中 std::uint32_t 。

由于Java不支持无符号类型,无符号的HIDL类型被映射到相应的有符号Java类型。Struct 映射到Java Class;数组映射到Java数组;目前Java中还不支持 unions。字符串内部存储为UTF8。由于Java只支持UTF16字符串,所以从Java实现发送到或从Java实现发送的字符串值被翻译,并且在重新翻译时可能不完全相同,因为字符集并不总是很顺利地映射。

c++中通过IPC接收到的数据被标记为const,它位于只读内存中,仅在函数调用的持续时间内存在。Java中通过IPC接收到的数据已经被复制到Java对象中,因此可以不需要额外的复制就可以保留它(并且可以修改)。

Annotations

可以在类型声明中添加java风格的注释。在HIDL编译器的供应商测试套件(VTS)后端会解析注释,但是这些解析的注释实际上并没有被HIDL编译器理解。相反,解析VTS注释由VTS编译器(VTSC)处理。

注释使用Java语法:@annotation 或 @annotation(value) 或 @annotation(id=value, id=value…) ,其中值可以是常量表达式、字符串或 {} 内的值列表,就像在Java中一样。相同名称的多个注释可以附加到相同的项上。

Forward declarations

在HIDL中,struct可能不是预先声明的,这使 user-defined、self-referential 的数据类型是不可能的(例如,您无法在HIDL中描述列表或树)。大多数现有的(pre-Android 8.x) HALs都限制了预先声明的使用,因为这可以通过重新安排数据结构声明来去除。

这种限制允许数据结构以简单的深度复制的方式进行复制,而不是跟踪在自引用数据结构中多次出现的指针值。如果相同的数据被两次传递,比如有两个方法参数或 vec< T>s指向相同的数据,就会生成并交付两个副本。

Nested declarations

HIDL支持多级嵌套声明(在下面有一个例外)。例如:

interface IFoo {
    uint32_t[3][4][5][6] multidimArray;

    vec<vec<vec<int8_t>>> multidimVector;

    vec<bool[4]> arrayVec;

    struct foo {
        struct bar {
            uint32_t val;
        };
        bar b;
    }
    struct baz {
        foo f;
        foo.bar fb; // HIDL uses dots to access nested type names
    }
    …

例外情况是,接口类型只能嵌入到 vec< T>中,只有一个级别的深度(没有 vec< vec< IFoo>>)。

Raw pointer syntax

HIDL语言不使用*,也不支持C/ c++原始指针的完全灵活性。

Interfaces

接口关键字有两个用法。

  • 他开始了.hal 文件中的接口定义
  • 它可以作为 struct/union字段、方法参数和返回值的特殊类型。它被看作是一个通用的接口,它与android.hidl.base@1.0::IBase 意义相同。

例如,IServiceManager 有以下方法:

get(string fqName, string name) generates (interface service);

该方法根据名称查找某个接口。用 android.hidl.base@1.0::IBase 来替代接口也完全相同。

接口只能通过两种方式传递:作为顶级参数,或者作为 vec< IMyInterface> 的成员。它们不能是嵌套的vecs、struct、数组或union的成员。

MQDescriptorSync & MQDescriptorUnsync

MQDescriptorSync 和 MQDescriptorUnsync 类型通过HIDL接口传递同步或非同步的快速消息队列(FMQ)描述符。

memory type

内存类型用于表示HIDL中未映射的共享内存。它只在c++中支持。该类型的值可以用于接收端初始化IMemory对象,映射内存并使其可用。有关详细信息,请参阅HIDL c++。

pointer type

指针类型仅用于HIDL内部使用。

bitfield< T> type template

bitfield< T> 中 T 是用户定义的一个枚举,我们建议它的值是 T 中枚举值的位与结果,在生成的代码中,bitfield< T>作为T的底层类型出现。

enum Flag : uint8_t {
    HAS_FOO = 1 << 0,
    HAS_BAR = 1 << 1,
    HAS_BAZ = 1 << 2
};
typedef bitfield<Flag> Flags;
setFlags(Flags flags) generates (bool success);

编译器处理Flags类型与uint8_t相同。

为什么不使用 (u)int8_t /(u)int16_t /(u)int32_t /(u)int64_t? 使用 bitfield 为读者提供了额外的HAL信息,读者现在知道了setFlags采用了Flag 位与值(即知道用16调用setFlags是无效的)。没有bitfield,这些信息只能通过文档传递。此外,VTS还可以检查标志的值是否为bitwise或Flag。

handle primitive type

HIDL语义是按值复制的,这意味着要复制参数。任何大的数据,或者需要在进程之间共享的数据,都是通过传递文件描述符来处理的,这些文件描述符指向持久的对象:共享内存 ashmem,实际的文件,或者任何可以隐藏在文件描述符后面的东西。绑定器驱动程序将文件描述符复制到另一个进程中。

native_handle_t

Android支持native_handle_t,这是在libcutils中定义的通用句柄概念。

typedef struct native_handle
{
  int version;        /* sizeof(native_handle_t) */
  int numFds;         /* number of file-descriptors at &data[0] */
  int numInts;        /* number of ints at &data[numFds] */
  int data[0];        /* numFds + numInts ints */
} native_handle_t;

原生句柄是由传值得到的 ints 和文件描述符的集合。单个文件描述符可以存储在一个没有ints和一个文件描述符的原生句柄中。通过使用包含处理原始类型的原生句柄来传递句柄,可以确保原生句柄直接包含在HIDL中。

因为 native_handle_t 具有可变大小,它不能直接包含在 struct 中。句柄字段生成指向单独分配的 native_handle_t 的指针。

在早期版本的Android中,原生句柄是使用 libcutils 中的相同功能创建的。在Android 8.0和更高版本中,这些功能现在被复制到android::hardware::hidl 命名空间或移动到NDK。HIDL自动生成代码自动地对这些函数进行序列化和反序列化,而不涉及用户编写的代码。

Handle and file descriptor ownership

当您调用一个HIDL接口方法来传递(或返回)一个 hidl_handle 对象(不论一个复合类型的顶级或部分)时,它包含的文件描述符的所有权如下:

  • 调用者传递一个 hidl_handle 对象作为参数,该对象保留了native_handle_t 中包含的文件描述符的所有权;调用者必须在完成时关闭这些文件描述符。
  • 返回hidl_handle对象(通过将其传递到_cb函数)的过程保留了由该对象包装的native_handle_t中包含的文件描述符的所有权;进程完成时必须关闭文件描述符。
  • 接收 hidl_handle 的传输拥有 native_handle_t 中文件描述符的所有权;接收方可以在事务回调期间使用这些文件描述符,但是必须克隆原生句柄以在回调之外使用文件描述符。当传输完成时,传输将自动关闭文件描述符。

HIDL不支持Java句柄(因为Java根本不支持句柄)。

Sized arrays

对于HIDL结构中的大小固定的数组,它们的元素可以是struct可以包含的任何类型:

struct foo {
uint32_t[3] x; // array is contained in foo
};

Strings

在c++和Java中字符串的表现不同,但是底层的传输存储类型是一个c++结构。有关详细信息,请参阅HIDL c++数据类型或HIDL Java数据类型。

vec type template

vec< T>模板表示一个可变大小的缓冲区,其中包含T的实例,T 可以是任何hidl提供的或用户定义的类型,除了句柄。(vec< T>类型的vec<>将指向vec struct的数组)。

T 可以使下面之一:

  • Primitive types (e.g. uint32_t)
  • Strings
  • User-defined enums
  • User-defined structs
  • Interfaces, or the interface keyword (vec, vec is supported only as a top-level parameter)
  • Handles
  • bitfield
  • vec< U>, where U is in this list except interface (e.g. vec< vec< IFoo>> is not supported)
  • U[ ](sized array of U), where U is in this list except interface

User-defined types

本节描述用户定义的类型。

Enum

HIDL不支持匿名的枚举。另外,HIDL中的enums类似于c++ 11:

enum name : type { enumerator , enumerator = constexpr , …  }

枚举是用HIDL中的一种原始类型定义的,或者作为其他枚举的扩展。例如:

enum Color : uint32_t { RED = 0, GREEN, BLUE = 2 } // GREEN == 1

枚举的值是用冒号语法(而不是作为嵌套类型的点语法)来引用的。语法是 Type:VALUE_NAME。如果值在相同的枚举类型或子类型中引用,则不需要指定类型。例子:

enum Grayscale : uint32_t { BLACK = 0, WHITE = BLACK + 1 };
enum Color : Grayscale { RED = WHITE + 1 };
enum Unrelated : uint32_t { FOO = Color:RED + 1 };

Struct

HIDL不支持匿名结构。否则,HIDL中的struct非常类似于C。

HIDL不支持完全包含在结构中的可变长度数据结构。这包括有时被用作C/ c++中结构的最后一个字段的不确定长度的数组(有时可以看到大小为[0])。HIDL vec< t>代表动态大小的数组,数据存储在单独的缓冲区中;在 struct 中,这样的实例用vec的实例表示。

类似地,字符串可以包含在一个struct中(关联的缓冲区是独立的)。在生成的c++中,HIDL句柄类型的实例通过一个指向实际本机句柄的指针来表示,因为底层数据类型的实例是可变长度的。

Union

HIDL不支持匿名 Union。否则,Union 与 C 类似。

Union 不能包含 fix-up 类型(指针、文件描述符、绑定对象等)。它们不需要特殊的字段或关联类型,只需通过memcpy()或等效的方式复制。union 可能不会直接包含(或包含通过其他数据结构)任何需要设置绑定偏移量的东西(例如:handle 或者 binder-interface 的引用)。例如:

union UnionType {
uint32_t a;
//  vec<uint32_t> r;  // Error: can't contain a vec<T>
uint8_t b;1
};
fun8(UnionType info); // Legal

Unions也可以在 structs 内部声明。例如:

struct MyStruct {
    union MyUnion {
      uint32_t a;
      uint8_t b;
    }; // declares type but not member

    union MyUnion2 {
      uint32_t a;
      uint8_t b;
    } data; // declares type but not member
  }

Versioning

HIDL要求在HIDL中编写的每个接口都要进行版本控制。在HAL接口发布之后,它被冻结,只能对新版本进行进一步的更改。虽然给定发布的接口可能不会被修改,但它可以被另一个接口扩展。

HIDL code structure

HIDL代码是在用户定义的类型、接口和包中组织的:

  • 用户定义类型(UDTs)。HIDL提供了对一组原始数据类型的访问,这些数据类型可以通过 struct、union 和 enum 组合更复杂的类型。UDTs 被传递给接口的方法,并且可以在包的级别(通用于所有接口)或单独接口。
  • 接口。作为HIDL的基本构建模块,接口包括UDT和方法声明。接口也可以从另一个接口继承。
  • 包。组织相关的HIDL接口和它们操作的数据类型。一个包由一个名称和一个版本标识,并包含以下内容: type.hal 数据类型定义文件,零或多个接口,每个都在他们自己的 .hal 文件中。

数据类型定义文件 types.hal 只包含目标语言中的UDTs(所有包级的 UDT 都保存在一个文件中)。可用于包中的所有接口。

Versioning philosophy

一个HIDL包(比如 android.hardware.nfc),在发布了一个给定版本(如1.0)之后,是不能被改变的。对包中的接口或对其UDTs的任何更改的修改只能在另一个包中进行。

在HIDL中,版本控制应用于包级别,而不是在接口级别,而包中的所有接口和UDTs都具有相同的版本。包版本遵循 semantic versioning,没有 patch level 和 build-metadata 组件。在一个给定的包中,一个小版本的bump意味着这个包的新版本是向后兼容的,与旧的包兼容,而一个主要版本的bump意味着这个包的新版本不是向后兼容旧的包。

从概念上讲,包可以通过以下几种方式与另一个包关联:

  • 不关联
  • 包级别向后兼容扩展。这发生在一个包的新的minor-version uprevs(下一次递增的修订)中;新包的名称和主要版本与旧的包相同,但是版本比较高。从功能上讲,这个新包是旧包的超集,意思是:1.新包中包含父包的顶级接口,尽管接口可能有新方法、新接口级UDTs(下面描述的接口级扩展)和 types.hal 中的新UDT。2.新接口也可以添加到新包中。3.父包的所有数据类型都存在于新包中,并且可以由旧包中的(可能重新实现的)方法处理。4.新的数据类型也可以被添加到现有接口的新方法或新接口中。
  • 接口级的向后兼容扩展。新的包还可以通过包含逻辑上独立的接口来扩展原始包,这些接口只提供额外的功能,而不是核心的功能。为此目的,不妨采取以下措施:1.新包中的接口需要使用旧包的数据类型。2.新包中的接口可以扩展一个或多个旧包的接口。
  • 原物向后兼容。这是软件包的一个主要版本uprev,两者之间不需要任何关联。在某种程度上,它可以通过包的旧版本的组合和旧包接口子集的继承来表示。

Structuring interfaces

对于一个结构良好的接口,添加不属于原始设计的新类型的功能需要对HIDL接口进行修改。相反,如果您可以或期望在引入新功能的接口的两边进行更改,而不改变接口本身,那么接口就不是结构化的。

Treble支持独立编译的设备上的供应商组件 vendor.img 和系统组件 system.img。所有 vendor.img 和 system.img 必须明确和彻底地定义,以便他们能够继续工作多年。这包括许多API方面,但是一个主要的方面是IPC机制,HIDL用于 system.img/vendor.img 的进程间通信。

Requirements

通过HIDL传递的所有数据必须显式地定义。为了确保实现和客户可以在单独或独立开发的情况下继续一起工作,数据必须符合以下要求:

  • 可以在HIDL中直接使用语义名和语义来描述(使用structs enums等)。
  • 可以用ISO/IEC 7816这样的公共标准来描述。
  • 可以用硬件标准或硬件的物理布局来描述。
  • 如果必要,可以是不透明的数据(如公钥、id等)。

如果使用不透明数据,则必须只在HIDL接口的一侧读取。例如,如果 vendor.img 代码为 system.img 提供了一个组件,一个字符串消息或 vec< uint8_t>数据,该数据不能被 system.img 解析。它只能返回给 vendor.img 解释。当从 vendor.img 传递一个值到 system.img 上的供应商代码或者另一个设备,数据的格式以及如何解释它必须被准确描述,并且仍然是接口的一部分。

Guidelines

您应该能够仅使用. HAL文件(即您不需要查看Android源代码或公共标准)来编写HAL的实现或客户端。我们建议指定具体需要的行为。诸如“可能执行A或B的实现”之类的语句鼓励与开发的客户端相互交织的实现。

HIDL code layout

HIDL包括核心和供应商包。

核心的HIDL接口是由谷歌指定的。它们所属于的包从android.hardware 开始,并由子系统命名,可能具有嵌套级别的命名。例如,NFC包被命名为 android.hardware.nfc ,相机包是android.hardware.camera 。一般而言,一个核心包名称为android.hardware.[name1].[name2]…. 。HIDL包除了名称之外还有一个版本。例如,软件包 android.hardware.camera 可能在3.4版;这很重要,因为包的版本影响它在源树中的位置。

所有核心包都放在构建系统 hardware/interfaces/ 中。版本 m.n 的包 android.hardware.[name1].[name2]… n 在 hardware/interfaces/name1/name2/…/m.n/ 下; 3.4 版本的 android.hardware.camera 包在hardware/interfaces/camera/3.4/ 下。硬编码的映射存在于包前缀 android.hardware.和hardware/interfaces/ 之间。

非核心(供应商)包是由SoC供应商或ODM 提供。非核心包的前缀是 vendor.(VENDOR).hardware 。(VENDOR) 指的是SoC供应商或OEM/ODM。这映射到源树中的路径 vendor/(VENDOR)/interface(此映射也是硬编码的)。

Fully-qualified user-defined-type names

在HIDL中,每个UDT都有一个完全限定的名称,包括UDT名称、定义UDT的包名称和包版本。只有在声明类型的实例而不是定义类型本身时才使用完全限定名。例如,假设包 android.hardware.nfc,版本1.0定义了一个名为NfcData的struct。在声明的地点(不论在 types.hal 中或在一个接口中),简单声明如下:

struct NfcData {
    vec<uint8_t> data;
};

在声明这种类型的实例(无论是在数据结构中还是作为方法参数)时,使用完全限定的类型名称:

android.hardware.nfc@1.0::NfcData

一般语法是PACKAGE@VERSION::UDT,其中:

  • PACKAGE 是一个点分的HIDL包名
  • VERSION 是点分的 major.minor
  • UDT 是HIDL UDT点分名字。由于HIDL支持嵌套的UDT, HIDL接口可以包含UDT(一种嵌套的声明),所以使用点来访问名称。

例如,在1.0版本 android.hardware.example 的 common types文件中定义了以下嵌套声明:

// types.hal
package android.hardware.example@1.0;
struct Foo {
    struct Bar {
        // …
    };
    Bar cheers;
};

Bar的完全限定名称为 android.hardware.example@1.0::Foo.Bar。如果,除了在上面的包中,嵌套的声明在一个名为IQuux的接口中:

// IQuux.hal
package android.hardware.example@1.0;
interface IQuux {
    struct Foo {
        struct Bar {
            // …
        };
        Bar cheers;
    };
    doSomething(Foo f) generates (Foo.Bar fb);
};

Bar的完全限定名称为android.hardware.example@1.0::IQuux.Foo.Bar。

在这两种情况下,Bar都只能在Foo的声明范围内被称为Bar。在包或接口级别,您必须通过 Foo: Foo.Bar 引用 Bar。就像在上面 doSomething 中所说的那样。或者,您可以更详细地声明该方法:

// IQuux.hal
doSomething(android.hardware.example@1.0::IQuux.Foo f) generates (android.hardware.example@1.0::IQuux.Foo.Bar fb);

Fully-qualified enumeration values

如果UDT是enum类型,那么枚举类型的每个值都有一个完全限定的名称,该名称以枚举类型的完全限定名开始,然后是冒号,然后是枚举值的名称。例如,假设包 android.hardware.nfc,版本1.0定义了枚举类型NfcStatus:

enum NfcStatus {
    STATUS_OK,
    STATUS_FAILED
};

对于 STATUS_OK ,完全限定名是:

android.hardware.nfc@1.0::NfcStatus:STATUS_OK

通用语法是 PACKAGE@VERSION::UDT:VALUE 。

Auto-inference rules

完全限定的UDT名称不需要指定。UDT名称可以安全地省略以下内容:

  • 包,例如 @1.0::IFoo.Type
  • 包和版本,例如 IFoo.Type

HIDL试图使用 auto-interference 规则来补全名称(较低的规则号意味着更高的优先级)。

规则1

如果没有提供包和版本,则尝试进行本地名称查找。例子:

interface Nfc {
    typedef string NfcErrorMessage;
    send(NfcData d) generates (@1.0::NfcStatus s, NfcErrorMessage m);
};

本地查找 NfcErrorMessage,找到上面的 typedef,本地查找 NfcData,但它不是本地定义的 规则2和规则3使用 @1.0::NfcStatus 提供一个版本,规则1以不使用。

规则2

如果规则1失败,组件缺少完全限定名称,组件用当前包信息自动填充。然后HIDL编译器产看当前文件(和所有 import)来自动补全完全限定名,使用上面的例子,假设 ExtendedNfcData 是在相同包中定义的,并与 NfcData 有相同的版本,如下:

struct ExtendedNfcData {
    NfcData base;
    // … additional members
};

HIDL编译器从当前包中填写包名和版本名称,以生成完全合格的UDT名称 android.hardware.nfc@1.0::NfcData。由于该名称存在于当前包中(假设它是正确导入的),因此它被用于声明。

当前包中的名称仅在以下情况下才导入:

  • 它是通过import语句显式导入的。
  • 它是在当前包 types.hal定义的。

如果NfcData只符合版本号,则遵循相同的流程:

struct ExtendedNfcData {
    // autofill the current package name (android.hardware.nfc)
    @1.0::NfcData base;
    // … additional members
};

规则3

如果规则2未能生成匹配(在当前包中没有定义UDT),那么HIDL编译器将扫描所有导入包中的匹配。使用上面的示例,假设 ExtendedNfcData 在包 android.hardware.nfc 的版本1.1中声明, 1.1 版本导入了 1.0 版本,定义指定UDT名称:

struct ExtendedNfcData {
    NfcData base;
    // … additional members
};

编译器查找任何名为NfcData的UDT,并在 android.hardware.nfc 中找到一个,产生了完全合格的android.hardware.nfc@1.0::NfcData。如果在给定的局部限定的UDT中找到多个匹配项,那么HIDL编译器会抛出一个错误。

Example

使用规则2,在当前包中定义的导入类型优于从另一个包导入的类型:

// hardware/interfaces/foo/1.0/types.hal
package android.hardware.foo@1.0;
struct S {};

// hardware/interfaces/foo/1.0/IFooCallback.hal
package android.hardware.foo@1.0;
interface IFooCallback {};

// hardware/interfaces/bar/1.0/types.hal
package android.hardware.bar@1.0;
typedef string S;

// hardware/interfaces/bar/1.0/IFooCallback.hal
package android.hardware.bar@1.0;
interface IFooCallback {};

// hardware/interfaces/bar/1.0/IBar.hal
package android.hardware.bar@1.0;
import android.hardware.foo@1.0;
interface IBar {
    baz1(S s); // android.hardware.bar@1.0::S
    baz2(IFooCallback s); // android.hardware.foo@1.0::IFooCallback
};
  • S 补全成 android.hardware.bar@1.0::S,在 bar/1.0/types.hal 中发现(因为 types.hal 是自动导入的)。
  • IFooCallback 补全成 android.hardware.bar@1.0::IFooCallback 使用规则2,但他不能被发现,因为 bar/1.0/IFooCallback.hal 不是自动导入的,因此规则3解决它,得到 android.hardware.foo@1.0::IFooCallback ,它是导入的通过 import android.hardware.foo@1.0。

types.hal

每个HIDL包都包含一个 types.hal 文件包含在参与该包的所有接口中共享的UDT。HIDL类型总是公开的;无论是否在 types.hal 中或接口声明中声明 UDT,这些类型可以在定义它们的范围之外访问。types.hal 不是用来描述包的公共API的,而是用于在包内的所有接口使用的UDT。由于HIDL的性质,所有的UDTs都是接口的一部分。

types.hal 由 UDT 和import语句组成。因为 types.hal 可用于包的每个接口(它是一个隐式导入),这些导入语句的定义是包级别的。types.hal 也可能包含导入的 UDT 和接口。

例如,IFoo.hal:

package android.hardware.foo@1.0;
// whole package import
import android.hardware.bar@1.0;
// types only import
import android.hardware.baz@1.0::types;
// partial imports
import android.hardware.qux@1.0::IQux.Quux;
// partial imports
import android.hardware.quuz@1.0::Quuz;

下面的被导入:

  • android.hidl.base@1.0::IBase (implicitly)
  • android.hardware.foo@1.0::types (implicitly)
  • Everything in android.hardware.bar@1.0 (including all interfaces and its types.hal)
  • types.hal from android.hardware.baz@1.0::types (interfaces in android.hardware.baz@1.0 are not imported)
  • IQux.hal and types.hal from android.hardware.qux@1.0
  • Quuz from android.hardware.quuz@1.0 (assuming Quuz is defined in types.hal, the entire types.hal file is parsed, but types other than Quuz are not imported).

Interface-level versioning

在HIDL中,接口可以使用扩展关键字从其他接口继承。对于扩展另一个接口的接口,它必须通过import语句访问它。扩展接口的名称(基本接口)遵循上面解释的类型名限定规则。接口只能从一个接口继承;HIDL不支持多重继承。

下面的uprev版本示例使用以下包:

// types.hal
package android.hardware.example@1.0
struct Foo {
    struct Bar {
        vec<uint32_t> val;
    };
};

// IQuux.hal
package android.hardware.example@1.0
interface IQuux {
    fromFooToBar(Foo f) generates (Foo.Bar b);
}

Uprev rules

定义一个包 package@major.minor。A或B必须是 true:

RuleRequire
Rule A从最小版本开始:多有过去的版本 package@major.0, package@major.1, …, package@major.(minor-1) 必须被定义
Rule B 1前一个版本被定义:package@major.(minor-1) 必须被定义
Rule B 2至少继承一个具有相同名称的接口:有一个接口 package@major.minor::IFoo 扩展自 package@major.(minor-1)::IFoo (如果过去的包有一个接口)
Rule B 3不继承不同名称的接口:不能存在 package@major.minor::IBar 扩展 package@major.(minor-1)::IBaz,IBar 和 IBaz 是两个不同的名字,

由于规则 A:

  • 一个包可以从任意小的版本号开始(例如,android.hardware.biometrics.fingerprint starts at @2.1)
  • 要求 “android.hardware.foo@1.0 is not defined” 不被定义意味着文件夹 hardware/interfaces/foo/1.0 不应该存在。

但是,规则 A并不影响具有相同包名不同主板本的包(例如,android.hardware.camera.device 同时有@1.0和@3.2版本定义;@3.2不需要与@1.0交互。因此,@3.2::IExtFoo可以扩展@1.0::IFoo。

提供的包名是不同的,package@major.minor::IBar 可能扩展自一个不同名称的接口(例如,android.hardware.bar@1.0::IBar 可以 扩展 android.hardware.baz@2.2::IBaz)。如果一个接口没有用 extend 关键字显式的声明一个父类型,他将扩展自 android.hidl.base@1.0::IBase 。

B2 和 B3 必须同时满足,例如,即使 android.hardware.foo@1.1::IFoo 扩展 android.hardware.foo@1.0::IFoo 通过了规则 B2,如果 android.hardware.foo@1.1::IExtBar 扩展 android.hardware.foo@1.0::IBar,这也不是一个有效的 uprev。

Upreving interfaces

Generally uprev means to change the dependency version to a newer one that has been made availalbe.

为了更新 android.hardware.example@1.0 到 @1.1:

// types.hal
package android.hardware.example@1.1;
import android.hardware.example@1.0;

// IQuux.hal
package android.hardware.example@1.1
interface IQuux extends @1.0::IQuux {
    fromBarToFoo(Foo.Bar b) generates (Foo f);
}

这是一个包级别的 types.hal 导入版本 1.0 的android.hardware.example。当没有新的 UDT 添加进版本 1.1 的包,版本1.0的 UDT 仍然被需要,因此在 types.hal 进行包级别的导入。(相同的影响可以实现通过 IQuux.hal 中接口级别的导入)

在扩展@1.0::IQuux 的声明中,我们指定被继承的 IQuux 版本(需要消除歧义,因为 IQuux 既用来声明一个接口,也用来从一个接口继承)。由于声明只是在声明的地方继承所有包和版本属性的名称,所以消除歧义必须位于基接口的名称中;我们也可以使用完全限定的UDT,但这是多余的。

新接口 IQuux 不会重新声明 fromFooToBar() 方法,它从 @1.0::IQuux 继承;它简单地列出了添加的新方法 fromBarToFoo().。在HIDL中,在子接口中可能不会再次声明继承的方法,因此IQuux接口不能显式地声明fromFooToBar()方法。

Uprev conventions

有时,接口名称必须重命名扩展接口。我们建议枚举扩展、结构和联合与被扩展具有相同的名称,除非它们有足够的不同以保证一个新名称。例子:

// in parent hal file
enum Brightness : uint32_t { NONE, WHITE };

// in child hal file extending the existing set with additional similar values
enum Brightness : @1.0::Brightness { AUTOMATIC };

// extending the existing set with values that require a new, more descriptive name:
enum Color : @1.0::Brightness { HW_GREEN, RAINBOW };

除非方法需要一个新名称,否则它应该与它的扩展的名字类似。例如,@1.1::IFoo 中的方法 foo_1_1 可能是 @1.0::IFoo 中 foo方法的替换。

Package-level versioning

HIDL版本控制发生在包级别;在发布包之后,它是不可变的(它的接口和 UDT 无法更改)。包可以通过多种方式相互关联,所有这些都可以通过结合接口级继承和通过组合构建UDTs来表达。

然而,一种类型的关系是严格定义的,必须执行:包级向后兼容的继承。在这种情况下,父包是继承的包,子包是扩展父包的包。包级别向后兼容的继承规则如下所示。

  1. 子包中的所有顶级接口都是从父包接口继承的。
  2. 新的接口也可以添加新的包(在其他包中与其他接口的关系没有限制)。
  3. 新的数据类型也可以被添加使用,通过现有接口的新方法或新接口。

这些规则可以使用HIDL接口级继承和UDT组合实现,但是需要 meta-level 的知识来了解这些关系构成向后兼容的包扩展。这些知识推断如下:

对于版本 major.minor 的包 package,如果一个 package 存在于 major.(minor-1) ,这样 package@major.minor 是一个小的uprev,必须遵循向后兼容的规则。

Code Style Guide

HIDL代码风格类似于Android框架中的c++代码,有4个空间缩进和大小写混合文件名。包声明、导入和文档字符串与Java中类似,稍有修改。

下面的例子 IFoo.hal 和 types.hal 展示了 HIDL 代码风格(IFooClientCallback.hal, IBar.hal, and IBaz.hal 被省略了)。

// hardware/interfaces/foo/1.0/IFoo.hal
/*
 * (License Notice)
 */

package android.hardware.foo@1.0;

import android.hardware.bar@1.0::IBar;

import IBaz;
import IFooClientCallback;

/**
 * IFoo is an interface that…
 */
interface IFoo {

    /**
     * This is a multiline docstring.
     * @return result 0 if successful, nonzero otherwise.
     */
     foo() generates (FooStatus result);

    /**
     * Restart controller by power cycle.
     * @param bar callback interface that…
     * @return result 0 if successful, nonzero otherwise.
     */
    powerCycle(IBar bar) generates (FooStatus result);

    /** Single line docstring. */
    baz();


    /**
     * The bar function.
     * @param clientCallback callback after function is called
     * @param baz related baz object
     * @param data input data blob
     */
    bar(IFooClientCallback clientCallback,
        IBaz baz,
        FooData data);

};
// hardware/interfaces/foo/1.0/types.hal
/*
 * (License Notice)
 */

package android.hardware.foo@1.0;

/** Replied status. */
enum Status : int32_t {
    OK,
    ERR_ARG, // invalid arguments
    ERR_UNKNOWN = -1, // note, no transport related errors
};

struct ArgData {
    int32_t[20]  someArray;
    vec<uint8_t> data;
};

Naming conventions

函数名、变量名和文件名应该是描述性的;避免过分缩写。把缩略语当作单词(例如,使用INfc而不是INFC)。

Directory structure and file naming

该目录结构应如下所示:
这里写图片描述

其中:

  • ROOT-DIRECTORY 是:1. 核心HIDL包在hardware/interfaces 2. 厂商包在 vendor/VENDOR/interfaces
  • MODULE 应是一个小写的单词,描述子系统(例如:nfc)。如果超过一个单词,使用嵌套 SUBMODULE,可以超过以及嵌套。
  • VERSION 应该提取相同的版本作为版本描述
  • I INTERFACE_X 应该位接口名,使用 UpperCamelCase/PascalCase 作为接口名描述。

例子:
这里写图片描述

Package names

包名必须使用以下全限定名(FQN)格式(称为 PACKAGE-NAME):

PACKAGE.MODULE[.SUBMODULE[.SUBMODULE[…]]]@VERSION

Imports

Imports 有下面三种形式:

  • 整包引入:import PACKAGE-NAME;
  • 部分引入:import PACKAGE-NAME::UDT; (or, if the imported type is in the same package,import UDT;
  • 只引入类型:import PACKAGE-NAME::types;

Grouping and ordering imports

在包声明之后(导入之前)使用空行。每个导入应该占据一行,不应该缩进。按以下顺序导入组:

  1. 其他 android.hardware 包
  2. 其他 vendor.VENDOR 包,每个厂商一组,厂商按名字字母顺序排列
  3. 导入相同包的其他接口

在组之间使用空行。在每个组内,按字母顺序对导入排序。例子:

import android.hardware.nfc@1.0::INfc;
import android.hardware.nfc@1.0::INfcClientCallback;

// Importing the whole module.
import vendor.barvendor.bar@3.1;

import vendor.foovendor.foo@2.2::IFooBar;
import vendor.foovendor.foo@2.2::IFooFoo;

import IBar;
import IFoo;

Interface names

接口名称必须以 I 开头,后跟一个 UpperCamelCase/PascalCase 名称。名为 IFoo 的接口必须在文件 IFoo.hal 中定义。这个文件只能包含IFoo接口的定义(接口 INAME 应该在 INAME.hal 中)。

Functions

对于函数名、参数和返回变量名,请使用 lowerCamelCase。例子:

open(INfcClientCallback clientCallback) generates (int32_t retVal);
oneway pingAlive(IFooCallback cb);

Struct/union field names

对于struct/union字段名,请使用 lowerCamelCase。例子:

struct FooReply {
    vec<uint8_t> replyData;
}

Type names
struct/union 类型、枚举类型和typedef。对于这些名称,请使用 UpperCamelCase /PascalCase。例子:

enum NfcStatus : int32_t {
    /*...*/
};
struct NfcData {
    /*...*/
};

Enum values

枚举值应该是 UPPER_CASE_WITH_UNDERSCORES 。当传递enum值作为函数参数并将它们作为函数返回时,使用实际的enum类型(而不是底层的整数类型)。例子:

enum NfcStatus : int32_t {
    HAL_NFC_STATUS_OK               = 0,
    HAL_NFC_STATUS_FAILED           = 1,
    HAL_NFC_STATUS_ERR_TRANSPORT    = 2,
    HAL_NFC_STATUS_ERR_CMD_TIMEOUT  = 3,
    HAL_NFC_STATUS_REFUSED          = 4
};

对于枚举值的完全限定名,在枚举类型名称和枚举值名称之间使用冒号:

PACKAGE-NAME::UDT[.UDT[.UDT[…]]:ENUM_VALUE_NAME

在一个完全限定的名称中不能有空格。只在必要时使用全限定名,省略不必要的部分。例子:

android.hardware.foo@1.0::IFoo.IFooInternal.FooEnum:ENUM_OK

Comments

对于单行注释,这两个//和/** /都很好。

// This is a single line comment
/* This is also single line comment */
/* * This is documentation comment */

  • 使用 // 主要为了:
    1.行后的注释
    2.不用于生成文档的注释
    3.ToDos
  • 使用 /* * */ 主要用于生成函数文档“文档字符串”,例子
/** Replied status */
enum FooStatus {
    OK            = 0, // no error
    ERR_TRANSPORT = 1, // transport level error
    ERR_ARG       = 2  // invalid args
}
  • 多行注释应该开始新的行 /* 开始,在每行开始用 ,结尾使用 */。
/**
 * My multi-line
 * comment
 */
  • Licensing notice 和 changelogs 应该开始新行,在每一行的开头使用 * ,并在最后使用 */ 。例子:
/*
 * Copyright (C) 2017 The Android Open Source Project
 * ...
 */

/*
 * Changelog:
 * ...
 */

File comments

使用适当的 licensing notice 开始每个文件。对于核心HALs,应该是 development/docs/copyright-templates/c.txt 中的AOSP Apache license。记住要更新年份和使用/* */样式的多行注释,如上所述。

您可以选择在许可证通知之后放置一个空行,然后是一个 changelog /版本控制信息。使用/* */样式多行注释如上所述,将空行放置在changelog之后,然后执行包声明。

TODO comments

TODOs应该包含 TODO字符串 后面加上一个冒号。例子:

// TODO: remove this code before foo is checked in.

只有在开发期间才允许使用TODO注释;它们不应该存在于已发布的接口中。

Interface/Function comments (docstrings)

使用/* * */用于多行和单行文档字符串。不要使用//用于文档字符串。

接口的文档字符串应该描述接口的一般机制、设计原理、目的等。函数的docstring应该是特定于函数的(包级别的文档在包目录中的 README 文件中)。

/**
 * IFooController is the controller for foos.
 */
interface IFooController {
    /**
     * Opens the controller.
     * @return status HAL_FOO_OK if successful.
     */
    open() generates (FooStatus status);

    /** Close the controller. */
    close();
};

您必须为每个参数/返回值添加@params和@return:

  • 必须为每个参数添加@param。接下来应该是参数的名称,然后是docstring。
  • 必须为每个返回值添加@return。后面应该是返回值的名称,然后是docstring。

例子:

/**
 * Explain what foo does.
 * @param arg1 explain what arg1 is
 * @param arg2 explain what arg2 is
 * @return ret1 explain what ret1 is
 * @return ret2 explain what ret2 is
 */
foo(T arg1, T arg2) generates (S ret1, S ret2);

Formatting

一般格式规则包括:

  • 行的长度。每行文本数最多为80。
  • 空格。行尾没有空格;空行不能包含空格。
  • 空格和制表。只使用空格。
  • 缩进的大小。块缩进使用4个空格,行缩进8个空格。
  • 大括号。除了注释,大括号开始与前的代码一行,结束与分号单独占一行。
interface INfc {
    close();
};

Package declaration

包声明应该在文件的顶部,在 license notice 之后,应该占据整个行,并且不应该缩进。使用以下格式声明包:

package PACKAGE-NAME;

例子

package android.hardware.nfc@1.0;

Function declarations
函数名、参数、生成和返回值应该在相同的行上。例子

interface IFoo {
    /** ... */
    easyMethod(int32_t data) generates (int32_t result);
};

如果它们不匹配,尝试在相同的缩进级别上放置参数和返回值,并区分 generate,以帮助读者快速查看参数和返回值。例子:

interface IFoo {
    suchALongMethodThatCannotFitInOneLine(int32_t theFirstVeryLongParameter,
                                          int32_t anotherVeryLongParameter);
    anEvenLongerMethodThatCannotFitInOneLine(int32_t theFirstLongParameter,
                                             int32_t anotherVeryLongParameter)
                                  generates (int32_t theFirstReturnValue,
                                             int32_t anotherReturnValue);
    superSuperSuperSuperSuperSuperSuperLongMethodThatYouWillHateToType(
            int32_t theFirstVeryLongParameter, // 8 spaces
            int32_t anotherVeryLongParameter
        ) generates (
            int32_t theFirstReturnValue,
            int32_t anotherReturnValue
        );
    // method name is even shorter than 'generates'
    foobar(AReallyReallyLongType aReallyReallyLongParameter,
           AReallyReallyLongType anotherReallyReallyLongParameter)
        generates (ASuperLongType aSuperLongReturnValue, // 4 spaces
                   ASuperLongType anotherSuperLongReturnValue);
}

其他细节:

  • 一个开括号总是与函数名在同一行。
  • 函数名和开括号之间没有空格。
  • 括号和参数之间没有空格,除非它们之间有换行。
  • 如果 generates 与前一个闭括号在同一行上,则前面使用空格。如果generates 与下一个开括号在同一条线上,则跟随空格。
  • 对齐所有参数和返回值(如果可能的话)。
  • 默认缩进是4个空格。
  • 参数换行与上一行的第一个参数对齐,否则它们有8个空间缩进。

Annotations

使用下列格式的注释:

@annotate(keyword = value, keyword = {value, value, value})

按字母顺序对注释进行排序,并在等号的周围使用空格。例子:

@callflow(key = value)
@entry
@exit

确保注释占据整行。例子:

// Good
@entry
@exit

// Bad
@entry @exit

如果注释不能在同一行上,缩进8个空格。例子:

@annotate(
        keyword = value,
        keyword = {
                value,
                value
        },
        keyword = value)

如果整个值数组不能在同一行中,则在打开的括号 { 和数组内的每个逗号之后,放置换行符。在最后一个值后立即放置右括号。如果只有一个值,就不要使用括号。

如果整个值数组都可以在相同的行中,那么在打开大括号之后不要使用空格,在结束括号之前,在每个逗号后面使用一个空格。例子:

// Good
@callflow(key = {"val", "val"})

// Bad
@callflow(key = { "val","val" })

在注释和函数声明之间不能有空行。例子:

// Good
@entry
foo();

// Bad
@entry

foo();

Enum declarations

对enum声明使用以下规则:

  • 如果枚举声明与另一个包共享,则将声明放入 types.hal 中。而不是嵌入到接口中。
  • 在冒号之前和之后使用空格,基本类型之后,打开的括号之前使用空格。
  • 最后的枚举值可能有也可能没有额外的逗号。

Struct declarations

对于结构声明,请使用以下规则:

  • 如果struct声明与另一个包共享,则将声明放入类型中。而不是嵌入到接口中。
  • 在开括号之前,结构类型名称之后使空格。
  • 对齐字段名称(可选)。例子:
struct MyStruct {
    vec<uint8_t>   data;
    int32_t        someInt;
}

Array declarations

不要在下列放空格:

  • 元素类型和开方括号之间。
  • 开方括号和数组之间。
  • 数组和闭方括号之间。
  • 如果存在多个维度,则闭方括号和下一个开方括号之间。

例子:

// Good
int32_t[5] array;

// Good
int32_t[5][6] multiDimArray;

// Bad
int32_t [ 5 ] [ 6 ] array;

Vectors

不要在下列放空格:

  • vec 和 < 之间
  • < 和 元素之间
  • 元素和 > 之间

例子:

// Good
vec<int32_t> array;

// Good
vec<vec<int32_t>> array;

// Good
vec< vec<int32_t> > array;

// Bad
vec < int32_t > array;

// Bad
vec < vec < int32_t > > array;
Logo

更多推荐