4、HIDL与AIDL基础:HIDL接口定义语言、AIDL与HIDL的区别、在MTK8676上创建第一个HIDL服务

好,我们进入第四章。这一章要聊的是HIDL和AIDL。

说实话,我刚接触Android HAL层开发那会儿,也被这两个东西搞晕过。它们都是接口定义语言,长得也像,但用途完全不同。在MTK8676这个平台上,你经常会同时遇到它们。搞不清楚,后面写代码就会踩坑。

今天我就带你彻底理清楚。咱们从HIDL开始讲起。

4.1 HIDL接口定义语言

HIDL,全称是HAL Interface Definition Language。它是Android 8.0之后引入的。目的是把HAL模块和Android框架彻底解耦。

你想想看,以前的HAL是直接编译进system_server进程的。一个HAL崩溃,整个系统就挂了。HIDL就是为了解决这个问题。它让HAL跑在独立的进程中,通过IPC通信。

核心思想:HIDL定义的是服务端和客户端之间的契约。服务端实现接口,客户端调用接口。两者通过Binder通信。

在MTK8676上,HIDL文件通常放在 hardware/interfaces/ 目录下。它的后缀是 .hal。语法上,它有点像C++和Java的混合体。

看一个简单的例子:

package vendor.mediatek.hardware.example@1.0;

interface IExample {
    hello(string name) generates (string result);
    getVersion() generates (int32_t major, int32_t minor);
};

这里我解释一下:

  • package 定义了包名,类似Java的命名空间。MTK的包名通常以 vendor.mediatek 开头。
  • @1.0 是版本号。HIDL非常强调版本管理。接口一旦发布,就不能改。只能通过增加新版本兼容。
  • interface 定义接口名。方法后面用 generates 关键字指定返回值。
  • 注意,HIDL的方法可以返回多个值。比如 getVersion 返回了两个int32。

个人经验:我建议你在定义HIDL接口时,尽量把方法拆细。一个方法只做一件事。别搞那种又长又复杂的接口。我在MTK项目上见过一个接口有20多个方法,维护起来简直噩梦。

4.2 AIDL与HIDL的区别

好,讲完HIDL,咱们说说AIDL。AIDL是Android Interface Definition Language。它比HIDL出现得早。Android系统内部大量使用AIDL。

它们到底有什么区别?我直接给你一张表,一目了然:

对比项 HIDL AIDL
引入时间 Android 8.0 Android 1.0
主要用途 HAL层接口定义 应用层与系统服务通信
语言 独立.hal文件 .aidl文件(Java风格)
版本管理 强制版本号,接口不可变 无强制版本管理
进程模型 HAL运行在独立进程 服务运行在system_server
稳定性 高,接口冻结后不可改 相对灵活,可随时修改
典型场景 传感器、摄像头、音频HAL ActivityManager、WindowManager

说白了,HIDL是给硬件厂商用的。AIDL是给应用框架用的。在MTK8676上,你写HAL层代码,主要用HIDL。但有时候也会用到AIDL,比如和系统服务交互。

避坑指南:我曾经在MTK平台上犯过一个错误。我把一个HIDL接口定义成了AIDL风格,结果编译死活过不去。记住,HIDL的语法和AIDL不一样。HIDL用 generates 而不是 return。HIDL不支持Java的泛型。这些细节不注意,编译报错会让你找半天。

4.3 在MTK8676上创建第一个HIDL服务

理论讲完了,咱们动手。在MTK8676上创建一个HIDL服务,其实不复杂。我带你走一遍完整流程。

第一步:定义HIDL接口

hardware/interfaces/ 下创建目录结构:

mkdir -p vendor/mediatek/hardware/hello/1.0/default

然后创建 IHello.hal 文件:

package vendor.mediatek.hardware.hello@1.0;

interface IHello {
    sayHello(string name) generates (string message);
};

这个接口很简单。就一个方法,传入名字,返回问候语。

第二步:生成代码

MTK的编译系统会自动处理HIDL文件的代码生成。你只需要在 Android.bp 中声明:

hidl_interface {
    name: "vendor.mediatek.hardware.hello@1.0",
    root: "vendor.mediatek.hardware",
    srcs: ["IHello.hal"],
    interfaces: [
        "android.hidl.base@1.0",
    ],
    gen_java: true,
    gen_java_constants: true,
};

运行 make hidl-gen 或者全编译,系统会自动生成C++和Java的桩代码。

第三步:实现服务端

default/ 目录下创建 Hello.cpp

#include <vendor/mediatek/hardware/hello/1.0/IHello.h>
#include <hidl/LegacySupport.h>

using vendor::mediatek::hardware::hello::V1_0::IHello;
using android::hardware::Return;
using android::hardware::Void;

struct HelloImpl : public IHello {
    Return<android::String16> sayHello(const android::String16& name) override {
        std::string msg = "Hello, " + std::string(name.c_str()) + "!";
        return android::String16(msg.c_str());
    }
};

int main() {
    return android::hardware::defaultPassthroughServiceImplementation<HelloImpl>("hello");
}

这里要注意,HIDL服务默认是passthrough模式。它会直接加载到调用进程。如果你想让它跑在独立进程,需要注册为binderized服务。

第四步:注册服务

Android.bp 中添加可执行文件编译规则:

cc_binary {
    name: "android.hardware.hello@1.0-service",
    relative_install_path: "hw",
    srcs: ["Hello.cpp"],
    shared_libs: [
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "vendor.mediatek.hardware.hello@1.0",
    ],
    init_rc: "android.hardware.hello@1.0-service.rc",
};

同时创建 .rc 文件,让服务在系统启动时自动运行:

service hello-hal /vendor/bin/hw/android.hardware.hello@1.0-service
    class hal
    user system
    group system

第五步:客户端调用

在任意进程中,你可以这样调用:

using vendor::mediatek::hardware::hello::V1_0::IHello;

sp<IHello> service = IHello::getService();
if (service != nullptr) {
    service->sayHello("World", [](const android::String16& result) {
        ALOGD("Result: %s", std::string(result.c_str()).c_str());
    });
}

个人习惯:我每次写完HIDL服务,都会先写一个简单的客户端测试程序。确保服务能正常启动和响应。别等到集成到系统里才发现问题。那时候排查起来就麻烦了。

嗯,到这里,你已经掌握了在MTK8676上创建HIDL服务的基本流程。从接口定义到代码生成,再到服务实现和注册,每一步我都带你走了一遍。

下一章,我们会深入HIDL的进阶用法。包括回调、同步异步、以及性能优化。这些在实际项目中非常关键。