介紹 本示例主要介紹在TaskPool子線程中使用 dlopen 預載入 so 庫並使用句柄調用庫函數的方法,以及在Native中使用 pread 系統函數讀取Rawfile文件的部分文本內容,並添加 HiLog 日誌。 效果圖預覽 使用說明 rawfile路徑下存在一個有內容的文本文件rawfil ...
介紹
本示例主要介紹在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。