1. 程式人生 > >Android HIDL第一個HelloWorld demo

Android HIDL第一個HelloWorld demo

原址

寫在前面

程式設計師有個癖好,無論是學習什麼新知識,都喜歡以HelloWorld作為一個簡單的例子來開頭,咱們也不例外。

OK,咱這裡都是乾貨,廢話就不多說啦,學習HIDL呢咱們還是需要一些準備工作和門檻的。

準備工作:

  • Android BSP編譯環境

  • Android裝置的BSP程式碼

  • Android裝置,用來跑測試程式碼

我這邊使用的是公司的裝置,打個小廣告哈,咱們是世界500強做Android工業手機的,這裡使用最近的專案使用的裝置,基於Qualcomm 驍龍660晶片,基於這個平臺來做開發。

當然了,如果手頭上沒有裝置的話,你也可以使用Andnroid模擬器做開發,Android模擬器的映象可以使用官方的AOSP程式碼來編譯,但是注意的是如果使用模擬器,kernel要去下載goldfish的程式碼,這個我這裡就不贅述了,可以google瞭解一下。

Naruto

我們需要給這個簡單的例子起一個牛逼的名字,我這裡叫Naruto,不要問我為什麼,哥是一個鐵打的火影迷,哈哈,就這麼定了,就叫Naruto了,那麼那麼我們就來說一段故事吧:

咱們可是要寫一個Android的HAL,大家不要把初衷搞混了,我們看看AOSP有哪些HAL:

  • Camera

  • Audio

  • Sensor

  • 等等

ape_fwk_hal.png

這些啊都是Android裝置上的硬體,因為Google理論上只關心Android的框架層和上層軟體,但是上層軟體依賴於底層的硬體實現,但是每家手機廠商,或者說是CPU廠商底層硬體的實現都是不一樣的,所以這個HAL層基本都是手機廠商或者CPU廠商去實現的,Google只是作為一個框架的指導,和Framework層API的介面定義,這些介面的實現都得由HAL去完成。

那麼我們的Naruto就肩負了這個重任嘍,控制底層硬體嘛,底層硬體都是由Linux kernel驅動控制的,提供檔案讀寫就可以簡單控制驅動啦,咱們這邊就搞虛擬驅動好了,省略了kernel driver的實現,有機會我們還可以在別的文章中去聊聊驅動,哈哈,畢竟哥啥都會。(吹牛逼不用上稅)

等等,我們這個是HelloWorld,好吧,Naruto,你就提供一個HelloWorld的介面吧,大材小用了。

HIDL 介面檔案定義

進入程式碼,我們假設Naruto作為標準AOSP的HAL,我們就把程式碼揉進標準HAL層去,進入程式碼目錄建立HIDL目錄:

mkdir -p hardware/interfaces/naruto/1.0/default 

接著建立介面描述檔案INaruto.hal,放在剛才建立的目錄中

package [email protected];

interface INaruto {
    helloWorld(string name) generates (string result);
};

沒錯這是一個Google定義的語言格式,C++和Java的結合體,我相信咱們搞Android BSP來發的,什麼語言不會呢,對不:

  • 彙編:bootloader和kernel中可能會用到

  • C語言:這你丫不會,你玩毛的Linux Kernel啊

  • C++:這你丫不會,你就別搞Android底層開發了,HAL和中間庫

  • Java:這麼再不會就自殺吧,framework和app的程式碼都是Java的

  • Python:這個不會麼也沒事,編譯相關的

  • Shell:這個不可能不會

  • Makefile:肯定會的,不會跳樓吧

這裡我們定義了一個INaruto介面檔案,簡單的添加了一個helloWorld介面,傳入是一個string,返回一個string,後面我們會來實現這個介面。

生成HAL 相關檔案

既然Google在Android 8.1要我們把HAL層換一次血,那麼他肯定會有一些列相關的工具來方便我們開發嘍,不然誰搞啊,對不對。

所以呢,Google還是幫我們提供了一些工具來生成HAL層相關的程式碼框架和程式碼例項,這樣子我們只需要關心實現部分,而不需要寫一堆無用程式碼,浪費時間在搞Makefile和一些低階錯誤上。

使用hidl-gen工具

# [email protected]
# LOC=hardware/interfaces/naruto/1.0/default/
# make hidl-gen -j64
# hidl-gen -o $LOC -Lc++-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE
# hidl-gen -o $LOC -Landroidbp-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE

然後使用指令碼來更新Makefile,自動生成Android,mk, Android.bp

# ./hardware/interfaces/update-makefiles.sh

現在,我們來新增兩個空檔案:

touch hardware/interfaces/naruto/1.0/default/[email protected]
touch hardware/interfaces/naruto/1.0/default/service.cpp

現在我們的程式碼目錄: hardware/interface/naruto:

├── 1.0
│   ├── Android.bp
│   ├── Android.mk
│   ├── default
│   │   ├── Android.bp
│   │   ├── [email protected]e.rc
│   │   ├── Naruto.cpp
│   │   ├── Naruto.h
│   │   └── service.cpp
│   └── INaruto.hal
└── Android.bp

是不是so easy,我們寫程式碼就寫了一個INaruto.hal,其餘程式碼都是自動生成的,特別是Naruto.cpp和Naruto.h這兩個檔案是實現介面的關鍵檔案。

實現HAL實現端的共享庫

來來來,vim走起來,開啟Naruto.h和Naruto.cpp檔案,開始要寫程式碼了,

開啟Naruto.h檔案,

struct Naruto : public INaruto {
    // Methods from INaruto follow.
    Return<void> helloWorld(const hidl_string& name, helloWorld_cb _hidl_cb) override;
    // Methods from ::android::hidl::base::V1_0::IBase follow.
};

// FIXME: most likely delete, this is only for passthrough implementations
// extern "C" INaruto* HIDL_FETCH_INaruto(const char* name);

我們知道,HIDL的實現有兩種方式,一種是Binderized模式,另一種是Passthrough模式,我們看到上面有兩行註釋掉的程式碼,看來這個程式碼是關鍵,來選擇實現方式是Binderized還是Passthrough。

我們這裡使用Passthrough模式來演示,其實大家後面嘗試這兩種方式後會發現其實這兩種本質是一樣的,目前大部分廠商使用的都是Passthrough來延續以前的很多程式碼,但是慢慢的都會被改掉的,所以我們來開啟這個註釋。

Naruto.h

# ifndef ANDROID_HARDWARE_NARUTO_V1_0_NARUTO_H
# define ANDROID_HARDWARE_NARUTO_V1_0_NARUTO_H
# include <android/hardware/naruto/1.0/INaruto.h>
# include <hidl/MQDescriptor.h>
# include <hidl/Status.h>

namespace android {
namespace hardware {
namespace naruto {
namespace V1_0 {
namespace implementation {

using ::android::hardware::hidl_array;
using ::android::hardware::hidl_memory;
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::sp;

struct Naruto : public INaruto {
    // Methods from INaruto follow.
    Return<void> helloWorld(const hidl_string& name, helloWorld_cb _hidl_cb) override;
    // Methods from ::android::hidl::base::V1_0::IBase follow.
};

// FIXME: most likely delete, this is only for passthrough implementations
extern "C" INaruto* HIDL_FETCH_INaruto(const char* name);

}  // namespace implementation
}  // namespace V1_0
}  // namespace naruto
}  // namespace hardware
}  // namespace android

# endif  // ANDROID_HARDWARE_NARUTO_V1_0_NARUTO_H

Naruto.cpp

# include "Naruto.h"

namespace android {
namespace hardware {
namespace naruto {
namespace V1_0 {
namespace implementation {

// Methods from INaruto follow.
Return<void> Naruto::helloWorld(const hidl_string& name, helloWorld_cb _hidl_cb) {
    // TODO implement
    char buf[100];
    ::memset(buf, 0x00, 100);
    ::snprintf(buf, 100, "Hello World, %s", name.c_str());
    hidl_string result(buf);

    _hidl_cb(result);
    return Void();
}

// Methods from ::android::hidl::base::V1_0::IBase follow.

INaruto* HIDL_FETCH_INaruto(const char* /* name */) {
    return new Naruto();
}

}  // namespace implementation
}  // namespace V1_0
}  // namespace naruto
}  // namespace hardware
}  // namespace android
  1. 我們打開了HIDL_FETCH的註釋,讓我們的HIDL使用Passthrough方式去實現

  2. 新增helloWorld函式的實現,簡單的做了字串拼接(學過C/C++)的同學應該都看得懂

然後可以檢視一下Android.bp檔案看一下編譯生成個啥

cc_library_shared {
    name: "[email protected]",
    relative_install_path: "hw",
    proprietary: true,
    srcs: [
        "Naruto.cpp",
    ],
    shared_libs: [
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "[email protected]",
    ],
}

最終會生成一個[email protected], 生成在/vendor/lib64/hw/下,我們用mmm編譯生成看看

$ mmm hardware/interfaces/naruto/1.0/default/

PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=8.1.0
TARGET_PRODUCT=hon660
TARGET_BUILD_VARIANT=userdebug
TARGET_BUILD_TYPE=release
TARGET_ARCH=arm64
TARGET_ARCH_VARIANT=armv8-a
TARGET_CPU_VARIANT=generic
TARGET_2ND_ARCH=arm
TARGET_2ND_ARCH_VARIANT=armv7-a-neon
TARGET_2ND_CPU_VARIANT=cortex-a53
HOST_ARCH=x86_64
HOST_2ND_ARCH=x86
HOST_OS=linux
HOST_OS_EXTRA=Linux-3.16.0-48-generic-x86_64-with-Ubuntu-14.04-trusty
HOST_CROSS_OS=windows
HOST_CROSS_ARCH=x86
HOST_CROSS_2ND_ARCH=x86_64
HOST_BUILD_TYPE=release
BUILD_ID=OPM1.171019.011

# OUT_DIR=out

[2/2] bootstrap out/soong/.minibootstrap/build.ninja.in
[1/1] out/soong/.bootstrap/bin/minibp out/soong/.bootstrap/build.ninja
[2/3] glob hardware/interfaces/*/Android.bp
[1/1] out/soong/.bootstrap/bin/soong_build out/soong/build.ninja
No need to regenerate ninja file
[100% 3/3] out/soong/.bootstrap/bin/soong_build out/soong/build.ninja
[100% 18/18] build 'out/target/product/hon660/obj/SHARED_LIBRARIES/[email protected]_intermediates/[email protected]'

#### build completed successfully (02:35 (mm:ss))

沒問題 對吧,好了,我們後面還有很多事情要做呢。

呼叫流程

上面呢我們完成了實現端的程式碼和編譯,我們這節來看一下整個HIDL的呼叫流程,因為裡面涉及到好幾個庫,有好多同學都被這些庫給搞混了,我們來看看這些庫的順序吧。

HIDL軟體包中自動生成的檔案會連結到與軟體包同名的單個共享庫。該共享庫還會匯出單個頭檔案INruto.h,用於在binder客戶端和服務端的介面檔案,下面的圖詮釋了我們的INaruto.hal編譯後生成的檔案走向,從官網拷貝過來的,大家不要在乎檔名哈:

treble_cpp_compiler_generated_files.png

  • **IFoo.h** - 描述 C++ 類中的純 IFoo 介面;它包含 IFoo.hal 檔案中的 IFoo 介面中所定義的方法和型別,必要時會轉換為 C++ 型別。不包含與用於實現此介面的 RPC 機制(例如 HwBinder)相關的詳細資訊。類的名稱空間包含軟體包名稱和版本號,例如 ::android::hardware::samples::IFoo::V1_0。客戶端和伺服器都包含此標頭:客戶端用它來呼叫方法,伺服器用它來實現這些方法。

  • **IHwFoo.h** - 標頭檔案,其中包含用於對介面中使用的資料型別進行序列化的函式的宣告。開發者不得直接包含其標頭(它不包含任何類)。

  • **BpFoo.h** - 從 IFoo 繼承的類,可描述介面的 HwBinder 代理(客戶端)實現。開發者不得直接引用此類。

  • **BnFoo.h** - 儲存對 IFoo 實現的引用的類,可描述介面的 HwBinder 存根(伺服器端)實現。開發者不得直接引用此類。

  • **FooAll.cpp** - 包含 HwBinder 代理和 HwBinder 存根的實現的類。當客戶端呼叫介面方法時,代理會自動從客戶端封送引數,並將事務傳送到繫結核心驅動程式,該核心驅動程式會將事務傳送到另一端的存根(該存根隨後會呼叫實際的伺服器實現)。

這些檔案的結構類似於由 aidl-cpp 生成的檔案(有關詳細資訊,請參見 HIDL 概覽中的“直通模式”)。獨立於 HIDL 使用的 RPC 機制的唯一一個自動生成的檔案是 IFoo.h,其他所有檔案都與 HIDL 使用的 HwBinder RPC 機制相關聯。因此,客戶端和伺服器實現不得直接引用除 IFoo 之外的任何內容。為了滿足這項要求,請只包含 IFoo.h 並連結到生成的共享庫。

我們這個例項會用到以下幾個模組:

  • [email protected]: Naruto模組實現端的程式碼編譯生成,binder server端

  • [email protected]: Naruto模組呼叫端的程式碼,binder client端

  • naruto_hal_service: 通過直通式註冊binder service,暴露介面給client呼叫

  • [email protected]: Android native 程序入口

Naruto HAL flow.png

大概流程就是這個樣子。

啟動binder server端程序

還記得我們之前建立的兩個檔案嗎,我們還沒有去實現呢,先來看一下rc檔案

service naruto_hal_service /vendor/bin/hw/[email protected]
    class hal
    user system
    group system

很簡單,就是在裝置啟動的時候執行/vendor/bin/hw/[email protected]程式:

# define LOG_TAG "[email protected]"

# include <android/hardware/naruto/1.0/INaruto.h>

# include <hidl/LegacySupport.h>

using android::hardware::naruto::V1_0::INaruto;
using android::hardware::defaultPassthroughServiceImplementation;

int main() {
    return defaultPassthroughServiceImplementation<INaruto>();
}

這個service是註冊了INaruto介面檔案裡面的介面,作為binder server端,很簡單就一句話,因為我們使用了passthrough的模式,Android幫我們封裝了這個函式,不需要我們自己去addService啦。

cc_binary {
    name: "[email protected]",
    defaults: ["hidl_defaults"],
    proprietary: true,
    relative_install_path: "hw",
    srcs: ["service.cpp"],
    init_rc: ["[email protected]"],
    shared_libs: [
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "liblog",
        "[email protected]",
        "[email protected]",
    ],
}

編譯後可以在, vendor/bin/hw/下找到對應的檔案。

OK,我們server端的程序和實現端共享庫已經完成了。

但是這個時候你如果燒錄映象,會發現這個程序會啟動失敗,原因是因為我們沒有給這個程序配sepolicy,所以正確的做法是要給他加上selinux的許可權,我們這裡就不去做了,因為我們可以用root許可權去手動起這個service。

好了,接下來要看看client的程式碼怎麼寫了。

HIDL Client測試程式碼

我寫程式碼喜歡一步一步來,每一步都搞個測試程式碼來測試,一來是驗證每一步的功能,而來呢是為後面測試使用。

我有個同事,寫程式碼賊快,寫完了之後就不知道咋除錯了,這種方式不好,不好,大家不要效仿。

寫程式碼不是一件難事,寫好程式碼是一件不容易的事情,好的程式碼都是通過大量測試來改善的,沒有誰可以一次性的寫好程式碼,所以大家在設計階段一定要把測試介面留出來,不然的話後面返工去re-design的話,會很沒面子,沒辦法,做我們這一行的,天天都在趕進度,你TMD跟老闆說要返工做re-design,老闆不剁了你不可。

好了,貼上我們的測試程式碼:

# include <android/hardware/naruto/1.0/INaruto.h>

# include <hidl/Status.h>

# include <hidl/LegacySupport.h>

# include <utils/misc.h>

# include <hidl/HidlSupport.h>

# include <stdio.h>

using android::hardware::naruto::V1_0::INaruto;
using android::sp;
using android::hardware::hidl_string;

int main()
{
    int ret;

    android::sp<INaruto> service = INaruto::getService();
    if(service == nullptr) {
        printf("Failed to get service\n");
        return -1;
    }

    service->helloWorld("JayZhang", [&](hidl_string result) {
                printf("%s\n", result.c_str());
        });

    return 0;
}

程式碼是相當的簡單啊,似不似啊,例項化binder service,通過INaruto::getService(),獲取到binder server端接介面代理類,然後就可以呼叫他的方法了,我們這裡呼叫helloWorld介面,然後通過callback獲取結果。

還是為了那些無知的程式設計師貼上Makefile吧

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_PROPRIETARY_MODULE := true
LOCAL_MODULE := naruto_test
LOCAL_SRC_FILES := \
    client.cpp \

LOCAL_SHARED_LIBRARIES := \
   liblog \
   libhidlbase \
   libutils \
   [email protected] \

include $(BUILD_EXECUTABLE)

記得在manifest檔案裡新增vendor介面的定義,不然在client端是沒法拿到service的,在相應的manifest.xml裡面加入:

<hal format="hidl">
    <name>android.hardware.naruto</name>
    <transport>hwbinder</transport>
    <version>1.0</version>
    <interface>
        <name>INaruto</name>
        <instance>default</instance>
    </interface>
</hal>

然後我們來測試一下程式碼吧:

手動執行service:

1533565212703.png

執行測試程式碼:

1533565241175.png

看到沒有,我們的測試程式碼傳入"JayZhang"字串,結果輸出"Hello World, JayZhang", 符合我們的預期結果。

所以本篇就結束嘍,吃瓜群眾還不趕快碼程式碼,好記性不如爛筆頭啊,自己不寫一遍怎麼記得住。

這知識簡單的如本HIDL的使用,不要著急,後面會有別的知識點,畢竟這只是一個簡單的HelloWorld。