一、前言
正如 Android HIDL 概述 一文中簡單的對 HIDL 的演進和新架構下 Framework 與 Hal 層之間的通訊做了介紹。但是筆者的目的是想完整的實現從上層 APP 到 hal 之間通訊過程,由此可以更加深刻的理解這種機制。
二、Binderized Mode (繫結式)簡介
從上文介紹,我們知道 繫結模式 是 google 為了向前相容而定義的一種型別,且 Android 8.0 及後續版本的裝置都必須只支援這種模式。這種模式下 Framework 與 Hal 分別位於不同的程序中,其實從具體實現來講這種模式也更應該被稱為 Binder 化的直通式。本文將透過這種方式實現一個 HIDL 服務。
三、環境/工具準備
- Ubuntu 20.04 TLS
- Android 原始碼:Android 9.0,編譯燒錄詳見 Android原始碼編譯燒錄
- hidl-gen 工具:Android 系統自帶,需要配置一下環境變數
四、HIDL 實現
本文目的是實現一個具有 加減乘除 運算的 HIDL 服務,命名為 銀河一號(GalaxyOne)。HIDL用起來非常簡單,在系統原始碼中的 hardware/interfaces 目錄下有很多的 HIDL,我們仿照其他 HIDL 來建立自己的目錄:hardware/interfaces/galaxy_one/1.0
4.1 建立 IGalaxyOne.hal 檔案
hardware/interfaces/galaxy_one/1.0/IGalaxyOne.hal
這裡定義了四種基本的運算:加、減、乘、除,這是上層呼叫 HAL 的入口,內容如下:
package android.hardware.galaxy_one@1.0;
interface IGalaxyOne{
//加法
add(uint32_t a,uint32_t b) generates (uint32_t result);
//減法
sub(uint32_t a,uint32_t b) generates (uint32_t result);
//乘法
mul(uint32_t a,uint32_t b) generates (uint32_t result);
//除法
div(uint32_t a,uint32_t b) generates (uint32_t result);
};
4.2 hidl-gen 生成 HIDL 框架
在使用 hidl-gen 之前需要先做兩件事:
1、hidl-gen 由 Android 提供,使用之前需要先配置一下系統路徑,如我這裡所做的:
# vim ~/.bashrc
export PATH=/home/zsk/AOSP/out/soong/host/linux-x86/bin:$PATH
2、Ubuntu 新的終端視窗必須先設定一些 Android 環境變數:
source build/envsetup.sh
lunch aosp_sailfish-userdebug // lunch mode 根據需求修改
make hidl-gen
配置完成之後在 原始碼根目錄 下執行如下命令:
PACKAGE=android.hardware.galaxy_one@1.0
LOC=hardware/interfaces/galaxy_one/1.0/default/
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
命令執行成功之後會發現在 hardware/interfaces/galaxy_one/1.0 目錄下多了一個 default 目錄,進入之後發現有如下檔案:
之後執行 update-makefiles.sh 指令碼來為 HIDL 生成對應的 Android.bp 檔案,此指令碼位於 hardware/interfaces 目錄下,同樣可在原始碼根目錄下執行:
./hardware/interfaces/update-makefiles.sh
接下來我們需要新增兩個空檔案:
touch hardware/interfaces/galaxy_one/1.0/default/android.hardware.galaxy_one@1.0-service.rc
touch hardware/interfaces/galaxy_one/1.0/default/service.cpp
完成之後,整個工程結構如下所示:
4.3 呼叫流程
上述過程已經將 HIDL 服務所需要的全本檔案配置完成,雖然其中很多檔案是空的,或者沒有具體實現,我們現在先放在一邊,先來對整體的呼叫流程及各個檔案的功效略作說明。
- Application:指上層應用
- JNI:指 framework 層,getService 獲取 hal 層 service
- android.hardware.galaxy_one@1.0.so:由 IGalaxyOne.hal 生成的介面庫,由 hardware/interfaces/galaxy_one/1.0/Android.bp 透過 IGalaxyOne.hal 生成,這樣只要這個介面庫不變,那麼 framework 的更新和 hal 層就隔絕開了
- android.hardware.galaxy_one@1.0-service.rc:裝置開機時透過 rc 檔案啟動此服務
- galaxy_hal_service:service的名,可透過國 start galaxy_hal_service 啟動服務
- android.hardware.galaxy_one@1.0-impl.so:實現庫,上層應用的最終呼叫
關於 Application、JNI 這兩層內容會在稍後用兩個篇幅去分析,此處暫不理會。現在我們就著這個呼叫過程將需要的內容補充完成。
4.3.1 介面庫生成
android.hardware.galaxy_one@1.0.so,由 hardware/interfaces/galaxy_one/1.0/Android.bp 透過 IGalaxyOne.hal 生成,Android.bp 檔案是在上面一些列命令執行之後生成,而介面庫是當我們最終執行編譯模組時生成,可以說這個過程不需要我們手動參與,Android.bp 內容如下:
// This file is autogenerated by hidl-gen -Landroidbp.
hidl_interface {
name: "android.hardware.galaxy_one@1.0", //此處設定介面庫的名字
root: "android.hardware",
vndk: {
enabled: true,
},
srcs: [
"IGalaxyOne.hal",
],
interfaces: [
"android.hidl.base@1.0",
],
gen_java: true,
}
4.3.2 實現庫生成
android.hardware.galaxy_one@1.0-impl.so,由 hardware/interfaces/galaxy_one/1.0/default/Android.bp 透過 GalaxyOne.cpp 生成,注意這個 Android.bp 檔案是位於 default 目錄下,同樣的在最後模組編譯時生成,原始內容如下:
cc_library_shared {
name: "android.hardware.galaxy_one@1.0-impl",
relative_install_path: "hw",
proprietary: true,
srcs: [
"GalaxyOne.cpp",
],
shared_libs: [ //這裡可以新增我們需要的庫
"liblog",
"libhidlbase",
"libhidltransport",
"libhwbinder",
"libutils",
"android.hardware.galaxy_one@1.0",
],
}
4.3.3 GalaxyOne.cpp 實現
在 4.3.2 中,實現庫是由 GalaxyOne.cpp 編譯而成,現在我們來將此檔案補充完成:
GalaxyOne.h:
Binder化直通式,同樣需要將 HIDL_FETCH_XXX 開啟,至於原因我們在後面會提及
#ifndef ANDROID_HARDWARE_GALAXY_ONE_V1_0_GALAXYONE_H
#define ANDROID_HARDWARE_GALAXY_ONE_V1_0_GALAXYONE_H
#include <android/hardware/galaxy_one/1.0/IGalaxyOne.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>
#include <log/log.h>
namespace android {
namespace hardware {
namespace galaxy_one {
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 GalaxyOne : public IGalaxyOne {
// Methods from ::android::hardware::galaxy_one::V1_0::IGalaxyOne follow.
Return<uint32_t> add(uint32_t a, uint32_t b) override;
Return<uint32_t> sub(uint32_t a, uint32_t b) override;
Return<uint32_t> mul(uint32_t a, uint32_t b) override;
Return<uint32_t> div(uint32_t a, uint32_t b) override;
// Methods from ::android::hidl::base::V1_0::IBase follow.
};
// FIXME: most likely delete, this is only for passthrough implementations
extern "C" IGalaxyOne* HIDL_FETCH_IGalaxyOne(const char* name);
} // namespace implementation
} // namespace V1_0
} // namespace galaxy_one
} // namespace hardware
} // namespace android
#endif // ANDROID_HARDWARE_GALAXY_ONE_V1_0_GALAXYONE_H
GalaxyOne.cpp:
#include "GalaxyOne.h"
namespace android {
namespace hardware {
namespace galaxy_one {
namespace V1_0 {
namespace implementation {
// Methods from ::android::hardware::galaxy_one::V1_0::IGalaxyOne follow.
Return<uint32_t> GalaxyOne::add(uint32_t a, uint32_t b) {
uint32_t result = a + b;
ALOGE("GalaxyOne::add a = %d,b = %d,result = %d",a,b,result);
return result;
}
Return<uint32_t> GalaxyOne::sub(uint32_t a, uint32_t b) {
uint32_t result = a - b;
ALOGE("GalaxyOne::sub a = %d,b = %d,result = %d",a,b,result);
return result;
}
Return<uint32_t> GalaxyOne::mul(uint32_t a, uint32_t b) {
uint32_t result = a * b;
ALOGE("GalaxyOne::mul a = %d,b = %d,result = %d",a,b,result);
return result;
}
Return<uint32_t> GalaxyOne::div(uint32_t a, uint32_t b) {
uint32_t result = a / b;
ALOGE("GalaxyOne::div a = %d,b = %d,result = %d",a,b,result);
return result;
}
// Methods from ::android::hidl::base::V1_0::IBase follow.
IGalaxyOne* HIDL_FETCH_IGalaxyOne(const char* /* name */) {
ALOG("galaxy_one service init success....");
return new GalaxyOne();
}
} // namespace implementation
} // namespace V1_0
} // namespace galaxy_one
} // namespace hardware
} // namespace android
4.3.3 模組編譯
現在除了需要的 rc 檔案沒有補充、galaxy-hal-service 服務沒有生成外其餘均已配置好了,現在進行編譯生成對應的庫。進入根目錄下執行如下命令:(注意是在剛剛執行過的 source build/envsetup.sh 和 lunch 的視窗下編譯,若是新視窗則需要重新執行這兩條命令)
mmm hardware/interfaces/galaxy_one/1.0
此時應該可以在 out/tartget/product/XXX/vendor/lib64/hw 和 out/tartget/product/XXX/system/lib64/hw 目錄下找到 android.hardware.galaxy_one@1.0.so 和 android.hardware.galaxy_one@1.0-impl.so 兩個動態庫
4.3.4 serice 生成
上面過程將需要的動態庫生成完畢,接下來我們需要生成對應的 service 可執行檔案,這個過程一共分為三步:
1、在 /default 下的 Android.bp 檔案中新增如下內容
cc_binary {
name: "android.hardware.galaxy_one@1.0-service",
defaults: ["hidl_defaults"],
relative_install_path: "hw",
vendor: true,
srcs: [
"service.cpp"
],
init_rc: ["android.hardware.galaxy_one@1.0-service.rc"],
shared_libs: [
"liblog",
"libhidlbase",
"libhidltransport",
"libhwbinder",
"libutils",
"android.hardware.galaxy_one@1.0",
],
}
2、補充 service.cpp 內容
內容很簡單,defaultPassthroughServiceImplementation 幫我們自動註冊服務:
#define LOG_TAG "android.hardware.galaxy_one@1.0-service"
#include <android/hardware/galaxy_one/1.0/IGalaxyOne.h>
#include <hidl/LegacySupport.h>
#include "GalaxyOne.h"
// Generated HIDL files
using android::hardware::galaxy_one::V1_0::IGalaxyOne;
using android::hardware::galaxy_one::V1_0::implementation::GalaxyOne;
using android::hardware::defaultPassthroughServiceImplementation;
using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
int main() {
return defaultPassthroughServiceImplementation<IGalaxyOne>();
}
3、補充 rc 檔案
注意這裡的 galaxy-hal-service 相當於這個服務的別名,系統就是根據這個檔案在啟動的同時也將這個 service 啟動,因此在下面我們手動啟動測試時沒有什麼作用,不過這裡先補充完整。
service galaxy-hal-service /vendor/bin/hw/android.hardware.galaxy_one@1.0-service
class hal
user system
group system
同樣執行 mmm hardware/interfaces/galaxy_one/1.0 命令,完成之後就會得到如下二進位制可執行檔案:
out/target/product/sailfish/vendor/bin/hw/android.hardware.galaxy_one@1.0-service
4.4 client 端
經過一系列過程之後,我們得到了三個產物
1、android.hardware.galaxy_one@1.0.so
2、android.hardware.galaxy_one@1.0-impl.so
3、android.hardware.galaxy_one@1.0-service
現在需要模擬一個客戶端來測試呼叫,因此在 default 目錄下新建 test 目錄,並新建 client.cpp、Android.bp 檔案,具體結構如下:
client.cpp 內容如下:
#include <android/hardware/galaxy_one/1.0/IGalaxyOne.h>
#include <hidl/Status.h>
#include <log/log.h>
using android::sp;
using android::hardware::galaxy_one::V1_0::IGalaxyOne;
using android::hardware::Return;
int main(){
android::sp<IGalaxyOne> service = IGalaxyOne::getService();
if (service == nullptr) {
ALOGD("faile to get galaxy_one service......");
return -1;
}
ALOGE("success to get galaxy_one service.....");
uint32_t addResult = service->add(3,4);
ALOGE("galaxy_one service add: result = %d",(int)addResult);
uint32_t subResult = service->sub(8,3);
ALOGE("galaxy_one service sub: result = %d",(int)subResult);
uint32_t mulResult = service->mul(3,8);
ALOGE("galaxy_one service mul: result = %d",(int)mulResult);
uint32_t divResult = service->div(8,2);
ALOGE("galaxy_one service div: result = %d",(int)divResult);
return 0;
}
Android.bp 內容如下:
cc_binary {
name: "galaxy_test", //表示生成的 client 名稱
srcs: [
"client.cpp"
],
shared_libs: [
"liblog",
"android.hardware.galaxy_one@1.0",
"libhidlbase",
"libhidltransport",
"libhwbinder",
"libutils",
],
}
mmm hardware/interfaces/galaxy_one/1.0 命令編譯之後,可以在 out/target/product/XXX/system/bin 目錄下找到 galaxy_test 。
五、驗證服務
5.1 push 裝置
現在我們一共得到 4 個產物,使用 adb 命令將其 push 到手機對應目錄下:
1、android.hardware.galaxy_one@1.0-impl.so ===> /vendor/lib64/hw
2、android.hardware.galaxy_one@1.0.so ===> vendor/lib64
3、android.hardware.galaxy_one@1.0-service ===> /vendor/bin/hw
4、galaxy_test ===> /system/bin
5、rc ===》 /vendor/etc/init
6、xml ===》/vendor/etc/vintf/manifest/
5.2 修改裝置 manifest.xml
HIDL 想要被 framework 獲取使用還需要在 manifest.xml 中註冊,此在手機 vendor/etc/vintf 下,我們將這個檔案 pull 出來新增如下程式碼之後再 push 原來位置:
<hal format="hidl">
<name>android.hardware.galaxy_one</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>IGalaxyOne</name>
<instance>default</instance>
</interface>
<fqname>@1.0::IGalaxyOne/default</fqname>
</hal>
5.3 執行 service
這裡我們手動啟動,用於測試
./vendor/bin/hw/android.hardware.galaxy_one@1.0-service
5.4 執行 client
./system/bin/galaxy_test
執行成功之後可檢視日誌,有如下內容則表示服務建立成功:
apk呼叫
在out下查詢jar
apk呼叫:
IGalaxyOne service = null; try { service = IGalaxyOne.getService(false); Log.e("longjiang",service.add(3,5)+""); } catch (RemoteException e) { throw new RuntimeException(e); }