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
-
我們打開了HIDL_FETCH的註釋,讓我們的HIDL使用Passthrough方式去實現
-
新增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。