Android Hidl笔记

HAL 类型

在 Android 8.0 及更高版本中,较低级别的层已重新编写以采用更加模块化的新架构。搭载 Android 8.0 或更高版本的设备必须支持使用 HIDL 语言编写的 HAL,下面列出了一些例外情况。这些 HAL 可以是绑定式 HAL 也可以是直通式 HAL。Android R 也支持使用 AIDL 编写的 HAL。所有 AIDL HAL 均为绑定式。

  • 绑定式 HAL。以 HAL 接口定义语言 (HIDL) 或 Android 接口定义语言 (AIDL) 表示的 HAL。这些 HAL 取代了早期 Android 版本中使用的传统 HAL 和旧版 HAL。在绑定式 HAL 中,Android 框架和 HAL 之间通过 Binder 进程间通信 (IPC) 调用进行通信。所有在推出时即搭载了 Android 8.0 或更高版本的设备都必须只支持绑定式 HAL。
  • 直通式 HAL。以 HIDL 封装的传统 HAL 或旧版 HAL。这些 HAL 封装了现有的 HAL,可在绑定模式和Same-Process(直通)模式下使用。升级到 Android 8.0 的设备可以使用直通式 HAL。如果有一个 HAL 接口 a.b.c.d@M.N::IFoo,系统会创建两个软件包:
  • a.b.c.d@M.N::IFoo-impl。该软件包包含 HAL 的实现,并提供函数 IFoo* HIDL_FETCH_IFoo(const char* name)。在旧版设备上,此软件包经过 dlopen 处理,且实现使用 HIDL_FETCH_IFoo 进行了实例化。您可以使用 hidl-gen、-Lc+±impl 和 -Landroidbp-impl 生成基础代码。
  • a.b.c.d@M.N::IFoo-service。打开直通式 HAL,并将其自身注册为 Binder 化服务,从而使同一 HAL 实现能够同时以直通模式和 Binder 化模式使用。
    如果有一个类型 IFoo,您可以调用 sp IFoo::getService(string name, bool getStub),以获取对 IFoo 实例的访问权限。
    如果 getStub 为 True,则 getService 会尝试仅在直通模式下打开 HAL。如果 getStub 为 False,则 getService 会尝试查找 Binder 化服务;如果未找到,则它会尝试查找直通式服务。除了在 defaultPassthroughServiceImplementation 中,其余情况一律不得使用 getStub 参数。(搭载 Android O 的设备是完全 Binder 化的设备,因此不得在直通模式下打开服务。)

HAL 接口定义语言(简称 HIDL,发音为“hide-l”)是用于指定 HAL 和其用户之间的接口的一种接口描述语言 (IDL),HIDL 旨在用于进程间通信 (IPC)。进程之间的通信采用 Binder 机制。对于必须与进程相关联的代码库,还可以使用直通模式(在 Java 中不受支持)。
HIDL 会尝试尽可能减少复制操作的次数。HIDL 定义的数据以 C++ 标准布局数据结构传递至 C++ 代码,无需解压,可直接使用。此外,HIDL 还提供共享内存接口;由于 RPC 本身有点慢,因此 HIDL 支持两种无需使用 RPC 调用的数据传输方法:共享内存和快速消息队列 (FMQ)。

HAL 实现

HIDL 接口软件包位于 hardware/interfaces 或 vendor/ 目录下(个别情况除外)。顶层 hardware/interfaces 会直接映射到 android.hardware 软件包命名空间;版本是软件包(而不是接口)命名空间下的子目录。

hidl-gen 编译器会将 .hal 文件编译成一组 .h 和 .cpp 文件。这些自动生成的文件可用来构建客户端/服务器实现所关联的共享库。用于构建此共享库的 Android.bp 文件由 hardware/interfaces/update-makefiles.sh 脚本自动生成。每次将新软件包添加到 hardware/interfaces 或在现有软件包中添加/移除 .hal 文件时,您都必须重新运行该脚本,以确保生成的共享库是最新的。

例如,IFoo.hal 示例文件应该位于 hardware/interfaces/samples/1.0 下。IFoo.hal 示例文件会在 samples 软件包中创建一个 IFoo 接口:

package android.hardware.samples@1.0;
interface IFoo {
struct Foo {
int64_t someValue;
handle myHandle;
};
someMethod() generates (vec<uint32_t>);
anotherMethod(Foo foo) generates (int32_t ret);
};

HIDL 软件包中自动生成的文件会关联到与该软件包同名的单个共享库(例如 android.hardware.samples@1.0)。该共享库还会导出单个头文件 IFoo.h,它可以包含在客户端和服务器中。在 Binder 化模式下,使用 hidl-gen 编译器以 IFoo.hal 接口文件作为输入会自动生成以下文件:
在这里插入图片描述

  • IFoo.h - 描述 C++ 类中的纯 IFoo 接口;它包含 IFoo.hal 文件中的 IFoo 接口中定义的方法和类型(必要时会转换为 C++ 类型)。不包含与用于实现此接口的 RPC 机制(例如 HwBinder)相关的详细信息。该类的命名空间包含软件包名称和版本号,例如 ::android::hardware::samples::IFoo::V1_0。客户端和服务器都包含此头文件:客户端用它来调用方法,服务器用它来实现这些方法。
  • IHwFoo.h - 一个头文件,包含用于对接口中使用的数据类型进行序列化的函数的声明。开发者不得直接包含此头文件(它不包含任何类)。
  • BpHwFoo.h - 一个类,从 IFoo 继承而来,并描述接口的 HwBinder 代理(客户端)实现。开发者不得直接引用此类。
  • BnHwFoo.h - 一个类,保持对 IFoo 实现的引用,并描述接口的 HwBinder 存根(服务器端)实现。开发者不得直接引用此类。
  • FooAll.cpp - 一个类,包含 HwBinder 代理和 HwBinder 存根的实现。当客户端调用接口方法时,代理会自动列集来自客户端的参数,并将事务发送到 Binder 内核驱动程序,该内核驱动程序会将事务传送到另一端的存根(该存根随后会调用实际的服务器实现)。

使用软件包中的任何接口的客户端或服务器必须在下面的其中一 (1) 个位置包含该软件包的共享库:

  • 在 Android.mk 中:
    LOCAL_SHARED_LIBRARIES += android.hardware.samples@1.0
  • 在 Android.bp 中:
    shared_libs: [
    /* … */
    “android.hardware.samples@1.0”,
    ],

您可能需要包含的其他库

  • libhidlbase 包含标准 HIDL 数据类型。从 Android 10 开始,此库还包含先前在libhidltransport 和 libhwbinder 中的所有符号。
  • libhidltransport 通过不同的 RPC/IPC 机制处理 HIDL 调用的传输。Android 10 弃用了此库。
  • libhwbinder Binder 专用符号。Android 10 弃用了此库。
  • libfmq 快速消息队列 IPC。

命名空间:
HIDL 函数和类型(如 Return 和 Void())在命名空间 ::android::hardware 中进行声明。软件包的 C++ 命名空间由软件包名称和版本号确定。例如,hardware/interfaces 下版本为 1.2 的软件包 mypackage 具有以下特质:

C++ 命名空间为 ::android::hardware::mypackage::V1_2 该软件包中 IMyInterface 的完全限定名称为
::android::hardware::mypackage::V1_2::IMyInterface(IMyInterface 是一个标识符,而不是命名空间的一部分)。
在该软件包的 types.hal 文件中定义的类型标识为:::android::hardware::mypackage::V1_2::MyPackageTyp

异步回调:
很多现有的 HAL 实现会与异步硬件通信,这意味着它们需要以异步方式通知客户端已发生的新事件。HIDL 接口可以用作异步回调,因为 HIDL 接口函数可以将 HIDL 接口对象用作参数。

接口文件 IFooCallback.hal 示例

package android.hardware.samples@1.0;
    interface IFooCallback {
        sendEvent(uint32_t event_id);
        sendData(vec<uint8_t> data);
    }

IFoo 中采用 IFooCallback 参数的新方法示例:

    package android.hardware.samples@1.0;
    interface IFoo {
        struct Foo {
           int64_t someValue;
           handle myHandle;
        };

        someMethod(Foo foo) generates (int32_t ret);
        anotherMethod() generates (vec<uint32_t>);
        registerCallback(IFooCallback callback);
    };

使用 IFoo 接口的客户端是 IFooCallback 接口的服务;它会提供 IFooCallback 的实现:

class FooCallback : public IFooCallback {
        Return<void> sendEvent(uint32_t event_id) {
            // process the event from the HAL
        }
        Return<void> sendData(const hidl_vec<uint8_t>& data) {
            // process data from the HAL
        }
    };

HIDL 语法

HIDL 基元与 C++ 数据类型之间的对应关系

HIDL 类型C++ 类型头文件/库
enumenum class
uint8_t…uint64_tuint8_t…uint64_t<stdint.h>
int8_t…int64_tint8_t…int64_t<stdint.h>
floatfloat
doubledouble
vechidl_veclibhidlbase
T[S1][S2]…[SN]T[S1][S2]…[SN]
stringhidl_stringlibhidlbase
handlehidl_handlelibhidlbase
safe_union(custom) struct
structstruct
unionunion
fmq_syncMQDescriptorSynclibhidlbase
fmq_unsyncMQDescriptorUnsynclibhidlbase

HIDL 形式的枚举会变为 C++ 形式的枚举。例如:

 enum Mode : uint8_t { WRITE = 1 << 0, READ = 1 << 1 };
    enum SpecialMode : Mode { NONE = 0, COMPARE = 1 << 2 };

…会变为:

enum class Mode : uint8_t { WRITE = 1, READ = 2 };
    enum class SpecialMode : uint8_t { WRITE = 1, READ = 2, NONE = 0, COMPARE = 4 };

字符串:

hidl_string 类(libhidlbase 的一部分)可用于通过 HIDL 接口传递字符串,它在 /system/libhidl/base/include/hidl/HidlSupport.h 中进行定义。该类中的第一个存储位置是指向其字符缓冲区的指针。

hidl_string 知道如何使用 operator=、隐式类型转换和 .c_str() 函数与 std::string and char*(C 样式的字符串)来回进行转换。HIDL 字符串结构体具有适当的复制构造函数和赋值运算符,可用于:

  • 从 std::string 或 C 字符串加载 HIDL 字符串。
  • 从 HIDL 字符串创建新的 std::string。

此外,HIDL 字符串还有转换构造函数,因此 C 字符串 (char *) 和 C++ 字符串 (std::string) 可用于采用 HIDL 字符串的方法。

结构体:

HIDL 形式的 struct 只能包含固定大小的数据类型,不能包含任何函数。HIDL 结构体定义会直接映射到 C++ 形式的标准布局 struct,从而确保 struct 具有一致的内存布局。一个结构体可以包含多个指向单独的可变长度缓冲区的 HIDL 类型(包括 handle、string 和 vec)。

内存:

HIDL memory 类型会映射到 libhidlbase 中的 hidl_memory 类,该类表示未映射的共享内存。这是要在 HIDL 中共享内存而必须在进程之间传递的对象。要使用共享内存,需满足以下条件:

  1. 获取 IAllocator 的实例(当前只有“ashmem”实例可用),并使用该实例分配共享内存。
  2. IAllocator::allocate() 返回 hidl_memory 对象,该对象可通过 HIDL RPC 传递,并能使用 libhidlmemory 的 mapMemory 函数映射到某个进程。
  3. mapMemory 返回对可用于访问内存的 sp 对象的引用。(IMemory 和 IAllocator 在 android.hidl.memory@1.0 中进行定义。)

IAllocator 的实例可用于分配内存:

#include <android/hidl/allocator/1.0/IAllocator.h>
    #include <android/hidl/memory/1.0/IMemory.h>
    #include <hidlmemory/mapping.h>
    using ::android::hidl::allocator::V1_0::IAllocator;
    using ::android::hidl::memory::V1_0::IMemory;
    using ::android::hardware::hidl_memory;
    ....
      sp<IAllocator> ashmemAllocator = IAllocator::getService("ashmem");
      ashmemAllocator->allocate(2048, [&](bool success, const hidl_memory& mem) {
            if (!success) { /* error */ }
            // now you can use the hidl_memory object 'mem' or pass it around
      }));

对内存的实际更改必须通过 IMemory 对象完成(要么在创建 mem 的一端完成,要么在通过 HIDL RPC 接收它的一端完成)。

// Same includes as above

    sp<IMemory> memory = mapMemory(mem);
    void* data = memory->getPointer();
    memory->update();
    // update memory however you wish after calling update and before calling commit
    data[0] = 42;
    memory->commit();
    // …
    memory->update(); // the same memory can be updated multiple times
    // …
    memory->commit();

函数:

对于具有返回值的每个 HIDL 函数(具有 generates 语句),该函数的 C++ 参数列表中都有一个附加参数:使用 HIDL 函数的返回值调用的回调函数。有一种例外情况:如果 generates 子句包含直接映射到 C++ 基元的单个参数,则会使用回调省略机制(回调会被移除,而返回值则会通过正常的 return 语句从函数返回)。
generates 语句可以产生三种类型的函数签名:

  • 如果只有一个属于 C++ 基元的返回值,generates 返回值会由 Return 对象中函数的值返回。
  • 如果情况更复杂,generates 返回值则会通过随函数调用本身一起提供的回调参数返回,而函数则返回 Return。
  • 如果没有 generates语句,函数会返回 Return<void

由于客户端和服务器端函数具有相同的签名,因此服务器端函数必须返回 Return 类型(即使其实现并不会指出传输错误)。Return 对象使用 Return(myTValue) 进行构建(也可以通过 mTValue 隐式构建,例如在 return 语句中),而 Return 对象则使用 Void() 进行构建。
Return 对象与其 T 值相互之间可以进行隐式转换。您可以在 Return 对象中检查是否存在传输错误,只需调用其 isOk() 方法即可。这项检查不是必需的;不过,如果发生了一个错误,而您未在 Return 对象销毁前对该错误进行检查,或尝试进行了 T 值转换,则客户端进程将会终止并记录一个错误。如果 isOk() 表明存在由开发者代码中的逻辑错误(例如将 nullptr 作为同步回调进行传递)导致的传输错误或失败调用,则可以对 Return 对象调用 description() 以返回适合日志记录的字符串。在这种情况下,您无法确定因调用失败而在服务器上执行的代码可能有多少。另外,您还可以使用 isDeadObject() 方法。此方法表明,之所以会显示 !isOk(),是因为远程对象已崩溃或已不存在。isDeadObject() 一律表示 !isOk()。
另外,您还可以使用 Return<*>::withDefault 方法。此方法会在返回值为 !isOk() 的情况下提供一个值。此方法还会自动将返回对象标记为正常,以免客户端进程遭到终止。

HAL Demo

  1. 服务器实现:
    实现 IFoo 接口的服务器必须包含自动生成的 IFoo 头文件:
#include <android/hardware/samples/1.0/IFoo.h>   

该标头由 IFoo 接口的共享库自动导出以进行链接。IFoo.hal 示例:

// IFoo.hal
    interface IFoo {
        someMethod() generates (vec<uint32_t>);
        ...
    }

IFoo 接口的服务器实现的示例框架:

// From the IFoo.h header
    using android::hardware::samples::V1_0::IFoo;

    class FooImpl : public IFoo {
        Return<void> someMethod(foo my_foo, someMethod_cb _cb) {
            vec<uint32_t> return_data;
            // Compute return_data
            _cb(return_data);
            return Void();
        }
        ...
    };

要使服务器接口的实现可供客户端使用,您可以:

向 hwservicemanager 注册接口实现(详情见下文)
或者
将接口实现作为接口方法的参数进行传递(详情见异步回调部分)。

注册接口实现时,hwservicemanager 进程会按名称和版本号跟踪设备上正在运行的已注册 HIDL 接口。服务器可以按名称注册 HIDL 接口实现,而客户端则可以按名称和版本号请求服务实现。该进程可提供 HIDL 接口android.hidl.manager@1.0::IServiceManager。
每个自动生成的 HIDL 接口头文件(例如 IFoo.h)都有一个 registerAsService() 方法,可用于向 hwservicemanager 注册接口实现。唯一一个必需的参数是接口实现的名称,因为稍后客户端将使用此名称从 hwservicemanager 检索接口:

::android::sp<IFoo> myFoo = new FooImpl();
::android::sp<IFoo> mySecondFoo = new FooAnotherImpl();
status_t status = myFoo->registerAsService();
status_t anotherStatus = mySecondFoo->registerAsService("another_foo");

hwservicemanager 会将 [package@version::interface, instance_name] 组合视为唯一,以使不同的接口(或同一接口的不同版本)能够采用完全相同的实例名称无冲突地注册。如果您调用的 registerAsService() 具有完全相同的软件包版本、接口和实例名称,则 hwservicemanager 将丢弃对先前注册的服务的引用,并使用新的服务。

  1. 客户端实现
    和服务器一样,客户端也必须 #include 其引用的每个接口
 #include <android/hardware/samples/1.0/IFoo.h>

客户端可以通过两种方式获取接口:

通过 I::getService(通过 hwservicemanager)
通过接口方法

每个自动生成的接口头文件都有一个静态 getService 方法,可用于从 hwservicemanager 检索服务实例:

  // getService will return nullptr if the service can't be found
    sp<IFoo> myFoo = IFoo::getService();
    sp<IFoo> myAlternateFoo = IFoo::getService("another_foo");

现在,客户端有一个 IFoo 接口,并可以向其(将其当作本地类实现)调用方法。实际上,实现可以在同一个进程中运行,也可以在不同的进程中运行,甚至还可以在另一个设备上运行(通过 HAL 远程处理)。由于客户端在 1.0 版软件包中包含的 IFoo 对象上调用 getService,因此仅当服务器实现与 1.0 客户端兼容时,hwservicemanager 才会返回该实现。
此外,您也可以使用 castFrom 方法,在不同的接口之间进行类型转换。该方法会通过以下方式发挥作用:对远程接口进行 IPC 调用,以确保底层类型与正在请求的类型相同。如果请求的类型不可用,则返回 nullptr。

  sp<V1_0::IFoo> foo1_0 = V1_0::IFoo::getService();
  sp<V1_1::IFoo> foo1_1 = V1_1::IFoo::castFrom(foo1_0);
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我来给你提供一些关于Android Studio的笔记。 1. Android Studio的介绍: - Android Studio是由Google推出的官方Android开发集成开发环境(IDE),用于开发Android应用程序。 - 它基于IntelliJ IDEA开发,提供了丰富的功能和工具,方便开发者进行Android应用程序的开发、调试和测试。 2. Android Studio的安装: - 在官方网站(https://developer.android.com/studio)上下载最新版本的Android Studio。 - 执行安装程序,并按照安装向导的指示进行操作。 - 安装完成后,启动Android Studio并进行必要的配置。 3. Android Studio的主要功能: - 提供了强大的代码编辑器,支持代码补全、语法高亮、代码重构等功能。 - 可以通过布局编辑器(Layout Editor)创建和编辑Android应用程序的用户界面。 - 集成了Android SDK工具,可以方便地管理安卓设备、模拟器以及APK包等。 - 提供了丰富的调试工具,如代码调试器、内存分析器、性能分析器等。 - 支持版本控制系统(如Git)的集成,方便团队协作开发。 4. Android Studio常用操作: - 创建一个新项目:选择"File" -> "New" -> "New Project",按照向导填写项目信息,即可创建一个新的Android项目。 - 打开现有项目:选择"File" -> "Open" -> 选择项目文件夹,即可打开一个已存在的Android项目。 - 运行应用程序:选择"Run" -> "Run 'app'",选择目标设备后即可在设备上运行应用程序。 - 调试应用程序:在代码中设置断点,选择"Debug" -> "Debug 'app'",即可启动调试模式并在代码中进行调试。 - 通过布局编辑器创建UI界面:在res目录下的layout文件夹中选择一个布局文件,然后使用布局编辑器进行UI界面的设计和编辑。 这些是Android Studio的一些基本信息和常用操作。希望对你有帮助!如果你还有其他问题,可以继续问我。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值