介紹
本示例主要介紹在TaskPool子執行緒中使用 dlopen 預載入 so 庫並使用控制代碼呼叫庫函式的方法,以及在Native中使用 pread 系統函式讀取Rawfile檔案的部分文字內容,並新增 HiLog 日誌。
效果圖預覽
使用說明
- rawfile路徑下存在一個有內容的文字檔案rawfile.txt。
- 輸入開始讀取位置、需要讀取的長度,點選“開始讀取”,即可透過呼叫Native側暴露的getRawFileContent介面把讀取到的內容顯示在介面上。
具體程式碼可參考MainPage.ets。
實現思路
在TaskPool子執行緒中使用dlopen預載入so庫和使用控制代碼呼叫so庫函式的方式
- 將需要載入的.so檔案放到工程中,在CMakeLists中使用target_link_directories命令將包含這些庫檔案的目錄新增到預載入庫的連結目錄。
target_link_directories(preloadso PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${OHOS_ARCH}/
- 使用target_link_libraries命令將需要預載入的so庫連結到專案中。
target_link_libraries(preloadso PUBLIC libhilog_ndk.z.so libace_napi.z.so global_handlers libnativerawfile.so)
- 定義一個全域性物件global_handlers用於存放載入so庫所得控制代碼,其他需要使用global_handlers的cpp檔案需要引入定義全域性物件的標頭檔案。
std::unordered_map<std::string, void *> global_handlers;
- 在 Native 層的 Preload 介面中,遍歷傳入的 .so 路徑陣列,使用 dlopen 函式載入庫,並將控制代碼儲存到 global_handlers 中。
// 獲取傳入的so庫路徑陣列的長度
uint32_t arrayLength;
napi_get_array_length(env, args[0], &arrayLength);
for (uint32_t i = 0; i < arrayLength; i++) {
napi_get_element(env, args[0], i, &pathString); // 獲取陣列的第 i 個元素
napi_status status = napi_get_value_string_utf8(env, pathString, path, sizeof(path), &pathLength);
if (status != napi_ok) {
// 處理獲取路徑失敗的情況
continue;
}
// TODO:知識點:使用dlopen動態載入so庫,返回so庫的控制代碼
void *handler = dlopen(path, RTLD_LAZY);
if (handler == nullptr) {
// TODO:知識點:dlerror丟擲載入庫失敗的錯誤
dlerror();
continue; // 載入下一個
}
// 將控制代碼儲存到全域性物件global_handlers中
global_handlers[std::string(path)] = handler;
}
- 暴露Preload介面給ArkTS層使用,使其能夠透過preload呼叫Native層的Preload介面。
napi_property_descriptor desc[] = {{"preload", nullptr, Preload, nullptr, nullptr, nullptr, napi_default, nullptr}};
- ArkTS層使用TaskPool建立子執行緒,透過preload介面呼叫Native側的Preload介面,實現在TaskPool子執行緒中載入.so庫,匯出preloadSOByTaskPool函式。
@Concurrent
function preloadSO(): string[] {
return napi.preload(Constants.LIBRARY_PATH_ARRAY);
}
export function preloadSOByTaskPool(): void {
// TODO: 知識點:使用new taskpool.Task()建立任務項,傳入任務執行函式和所需引數
let task: taskpool.Task = new taskpool.Task(preloadSO);
try {
// TODO:知識點:使用taskpool.execute將待執行的函式放入TaskPool內部任務佇列等待執行
taskpool.execute(task, taskpool.Priority.HIGH).then((res: string[]) => {
// so庫預載入完成的處理
logger.info(TAG, '%{public}s', 'PreloadSOByTaskPool:' + JSON.stringify(res));
})
} catch (err) {
logger.error(TAG, "PreloadSOByTaskPool: execute failed, " + (err as BusinessError).toString());
}
}
- 在Ability的onCreate生命週期函式中,呼叫preloadSOByTaskPool開啟子執行緒,完成so庫的預載入。
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 在TaskPool子執行緒預載入so
preloadSOByTaskPool();
}
- 後續可以透過控制代碼使用so庫中的函式。
- 在Native層引入標頭檔案global_handlers.h。
#include "global\_handlers.h"
- 編寫napi介面,用於實現ArkTS層和so庫之間的互動
static napi_value GetTotalRawFileContent(napi_env env, napi_callback_info info){}
static napi_value GetRawFileContent(napi_env env, napi_callback_info info) {}
- 在napi介面中從全域性物件global_handlers中取出對應so庫的控制代碼。
// 從全域性物件中獲取指定so庫的控制代碼
void *handler = global_handlers["libnativerawfile.so"];
- 控制代碼不為空時,使用dlsym查詢和呼叫so庫中的符號。
// TODO:知識點:使用dlsym查詢和呼叫so庫中的符號
GetTotalRawFileContentWrapperFunc getTotalRawFileContentWrapper =
reinterpret_cast<GetTotalRawFileContentWrapperFunc>(dlsym(handler, "GetTotalRawFileContentWrapper"));
if (getTotalRawFileContentWrapper) {
// 呼叫 GetRawFileContentWrapper 函式
napi_value result = getTotalRawFileContentWrapper(env, info);
OH_LOG_Print(LOG_APP, LOG_INFO, GLOBAL_RESMGR, TAG, " GetRawFileContentWrapper finish");
return result;
} else {
// 處理無法獲取函式指標的情況
OH_LOG_Print(LOG_APP, LOG_ERROR, GLOBAL_RESMGR, TAG, " GetTotalRawFileContentWrapper fn failed");
return nullptr;
}
- 在ArkTS層呼叫編寫的napi介面,就可以使用so庫匯出的函式
this.rawfileContent = nativeRawfileApi.getRawFileContent(getContext().resourceManager, 'rawfile.txt', 2, 5);
Native中加入hilog日誌的實現主要步驟如下
- 在CMakeLists中透過target_link_libraries匯入日誌模組libhilog_ndk.z.so。
target_link_libraries(nativerawfile PUBLIC libace_napi.z.so libhilog_ndk.z.so librawfile.z.so)
- 在需要列印hilog日誌的cpp檔案開頭引入標頭檔案 #include "hilog/log.h"。
#include "hilog/log.h"
- 在需要列印日誌的地方透過OH_LOG_Print列印日誌。日誌級別有LOG_INFO、LOG_ERROR等
OH_LOG_Print(LOG_APP, LOG_INFO, GLOBAL_RESMGR, TAG, "GetRawFileContent Begin");
Native讀取Rawfile中文字檔案部分內容主要步驟如下:
- 在前端透過呼叫Native中的getRawFileContent介面讀取檔案部分內容。傳入的引數為檔名、開始讀取位置、讀取檔案長度。
Button($r('app.string.ReadButton'))
.onClick(()=> {
this.rawfileContent = nativeRawfileApi.getRawFileContent(getContext().resourceManager, 'rawfile.txt', this.ReadStartPos, this.readLength);
}).margin($r('app.string.rawfile_margin'))
- 在Native側native_rawfile.cpp的getRawFileContent介面中透過Rawfile的API介面以及pread函式讀取Rawfile檔案部分內容。
// TODO 知識點:透過pread讀取檔案部分內容。
if ((ret = pread(descriptor.fd, buf, lenContent, descriptor.start + startPos)) == -1) {
OH_LOG_Print(LOG_APP, LOG_ERROR, GLOBAL_RESMGR, TAG, "GetRawFileContent pread error!");
} else {
buf[lenContent] = '\0';
OH_LOG_Print(LOG_APP, LOG_INFO, GLOBAL_RESMGR, TAG, "GetRawFileContent: %{public}ld: %{public}ld: %{public}s\n",
descriptor.start, len, buf);
}
高效能知識點
不涉及
工程結構&模組型別
nativerawfile // har型別
|---libs\
| |---arm64-v8a\libnativeRawFile.so // arm64-v8a型別so庫
| |---armeabi-v7a\libnativeRawFile.so // armeabi-v7a型別so庫
| |---x86_64\libnativeRawFile.so // x86_64型別so庫
|---src\main\ets\components\mainpage\
| |---MainPage.ets // 檢視層-Rawfile場景主頁面
|---src\main\ets\utils\
| |---Constants.ets // 常量資料
| |---TaskPool.ets // TaskPool子執行緒載入so庫
|---src\main\cpp\
| |---include\global_handlers.h // native層-全域性控制代碼標頭檔案
| |---global_handlers.cpp // native層-定義全域性控制代碼物件
| |---preloadso.cpp // native層-載入libnativeRawFile.so業務邏輯
| |---nativeRawFile.cpp // native層-讀取Rawfile檔案部分內容業務邏輯,libnativeRawFile.so原始碼
| |---native_rawfile_api.cpp // native層-libnativeRawFile.so和ArkTS中間層介面
模組依賴
- 本例項依賴common模組來實現公共元件FunctionDescription。
參考資料
公共元件FunctionDescription