HIDL 框架向后兼容性验证

HIDL HAL 可保证 Android 核心系统(也称为 system.img 或框架)向后兼容。虽然供应商测试套件 (VTS) 测试可确保 HAL 按预期运行(例如,针对所有 1.2 实现运行 1.1 HAL 测试),但仍需要进行框架测试,以确保提供受支持的 HAL(1.0、1.1 或 1.2)时该框架适用于该 HAL。

要详细了解 HAL 接口定义语言 (HIDL),请参阅 HIDLHIDL 版本控制HIDL HAL 弃用

关于 HAL 升级

HAL 升级分为两类:主要和次要。大多数系统仅包含一个 HAL 实现,但支持多个实现。例如:

android.hardware.teleport@1.0 # initial interface
android.hardware.teleport@1.1 # minor version upgrade
android.hardware.teleport@1.2 # another minor version upgrade
...
android.hardware.teleport@2.0 # major version upgrade
...

系统分区通常包含一个框架守护进程(如 teleportd),用于管理与特定 HAL 实现组进行的通信。作为一种替代方法,系统可能会包含一个用于实现便捷客户端行为的系统库(如 android.hardware.configstore-utils)。在上面的示例中,无论设备上安装了哪个版本的 HAL,teleportd 都必须能够正常运行。

Google 维护的版本

如果存在主要版本升级(1.0、2.0、3.0 等),则至少必须有一台 Google 维护的设备来维护各主要版本的实现,直到该版本弃用为止。如果 Google 维护的所有设备均未搭载特定主要版本,则 Google 会继续维护该主要版本的旧实现。

这种维护会增加一点额外的开销,因为创建新实现(如 2.0)时,旧实现(如 1.2)将保留且默认处于不使用的状态。

测试次要版本升级

如要测试框架中次要版本的向后兼容性,则需要一种自动生成次要版本实现的方法。鉴于 Google 维护的版本存在一定限制,hidl-gen 只会(且只能)生成采用 1.(x+n) 实现并提供 1.x 实现的适配器;它无法根据 2.0 实现生成 1.0 实现(以主要版本的定义为准)。

例如,要针对 1.2 实现运行 1.1 测试,则必须能够模拟具有 1.1 实现的情况。1.2 接口可自动用作 1.1 实现,但行为上存在一些细微差别(例如,框架会手动检查所属的具体版本或对自身调用 castFrom)。

基本做法如下所示:

  1. 在 Android 移动设备上安装 x.(y+n) 接口。
  2. 安装并启用目标为 x.y 的适配器。
  3. 测试设备,验证它能否按预期运行旧版次要版本。

这些适配器完全隐藏了如下事实:实现实际上由 1.2 接口提供支持,并且仅提供 1.1 接口(适配器采用 1.2 接口并让其看起来像 1.1 接口)。

工作流程示例

在此示例中,Android 设备运行 android.hardware.foo@1.1::IFoo/default。要确保客户端在 android.hardware.foo@1.0::IFoo/default 上正常运行,请执行以下操作:

  1. 在终端中,运行以下命令:
    $ PACKAGE=android.hidl.allocator@1.0-adapter
    $ INTERFACE=IAllocator
    $ INSTANCE=ashmem
    $ THREAD_COUNT=1 # can see current thread use on `lshal -i -e`
    $ m -j $PACKAGE
    $ /data/nativetest64/$PACKAGE/$PACKAGE $INTERFACE $INSTANCE $THREAD_COUNT
    Trying to adapt down android.hidl.allocator@1.0-adapter/default
    Press any key to disassociate adapter.
  2. 使用 adb shell stop(或 start)重启客户端,或者仅终止相应进程。
  3. 测试完成后,取消关联适配器。
  4. 通过重启设备或重启客户端来恢复系统状态。

其他目标

对于编译系统中使用 hidl_interface 指定的每个接口,hidl-gen 会自动为其适配器添加额外的编译目标。对于软件包 a.b.c@x.y,还需添加额外的 C++ 目标 a.b.c@x.y-adapter

a.b.c@x.y 的适配器将一些实现 a.b.c@x.(y+n)::ISomething/instance-name 用作输入,并且必须注册 a.b.c@x.y::ISomething/instance-name,还必须取消注册 y+n 实现。

假设有如下示例接口:

// IFoo.hal
package a.b.c@1.0;
interface IFoo {
    doFoo(int32_t a) generates (int64_t b);
    doSubInterface() generates (IFoo a);
};

a.b.c@1.0-adapter 提供的代码与以下示例类似:

// autogenerated code
// in namespace a::b::c::V1_0::IFoo
struct MockFoo {
    // takes some subclass of V1_0. May be V1_1, V1_2, etc...
    MockFoo(V1_0::IFoo impl) mImpl(impl) {}

    Return<int64_t> doFoo(int32_t a) {
        return this->mImpl->doFoo(a);
    }

    Return<V1_0::ICallback> doSubInterface() {
        // getMockForBinder returns MockCallback instance
        // that corresponds to a particular binder object
        // It can't return a new object every time or
        // clients using interfacesSame will have
        // divergent behavior when using the mock.
        auto _hidl_out = this->mImpl->doSubInterface();
        return getMockForBinder(_hidl_out);
    }
};

数据值会精确地转发到自动生成的模拟类以及从中转出,子接口除外(它们会返回)。这些接口必须封装在相应的最新回调对象中。