HarmonyOS NEXT應用開發之預載入so並讀取RawFile檔案

生活就是这么怪發表於2024-04-17

介紹

本示例主要介紹在TaskPool子執行緒中使用 dlopen 預載入 so 庫並使用控制代碼呼叫庫函式的方法,以及在Native中使用 pread 系統函式讀取Rawfile檔案的部分文字內容,並新增 HiLog 日誌。

效果圖預覽

image

使用說明

  1. rawfile路徑下存在一個有內容的文字檔案rawfile.txt。
  2. 輸入開始讀取位置、需要讀取的長度,點選“開始讀取”,即可透過呼叫Native側暴露的getRawFileContent介面把讀取到的內容顯示在介面上。

具體程式碼可參考MainPage.ets

實現思路

在TaskPool子執行緒中使用dlopen預載入so庫和使用控制代碼呼叫so庫函式的方式

  1. 將需要載入的.so檔案放到工程中,在CMakeLists中使用target_link_directories命令將包含這些庫檔案的目錄新增到預載入庫的連結目錄。
target_link_directories(preloadso PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${OHOS_ARCH}/
  1. 使用target_link_libraries命令將需要預載入的so庫連結到專案中。
target_link_libraries(preloadso PUBLIC libhilog_ndk.z.so libace_napi.z.so global_handlers libnativerawfile.so)
  1. 定義一個全域性物件global_handlers用於存放載入so庫所得控制代碼,其他需要使用global_handlers的cpp檔案需要引入定義全域性物件的標頭檔案。
std::unordered_map<std::string, void *> global_handlers;
  1. 在 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;
}
  1. 暴露Preload介面給ArkTS層使用,使其能夠透過preload呼叫Native層的Preload介面。
napi_property_descriptor desc[] = {{"preload", nullptr, Preload, nullptr, nullptr, nullptr, napi_default, nullptr}};
  1. 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());
  }
}
  1. 在Ability的onCreate生命週期函式中,呼叫preloadSOByTaskPool開啟子執行緒,完成so庫的預載入。
    onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
        // 在TaskPool子執行緒預載入so
        preloadSOByTaskPool();
    }
  1. 後續可以透過控制代碼使用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日誌的實現主要步驟如下

  1. 在CMakeLists中透過target_link_libraries匯入日誌模組libhilog_ndk.z.so。
target_link_libraries(nativerawfile PUBLIC libace_napi.z.so libhilog_ndk.z.so librawfile.z.so)
  1. 在需要列印hilog日誌的cpp檔案開頭引入標頭檔案 #include "hilog/log.h"。
#include "hilog/log.h"
  1. 在需要列印日誌的地方透過OH_LOG_Print列印日誌。日誌級別有LOG_INFO、LOG_ERROR等
OH_LOG_Print(LOG_APP, LOG_INFO, GLOBAL_RESMGR, TAG, "GetRawFileContent Begin");

Native讀取Rawfile中文字檔案部分內容主要步驟如下:

  1. 在前端透過呼叫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'))
  1. 在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中間層介面

模組依賴

  1. 本例項依賴common模組來實現公共元件FunctionDescription

參考資料

公共元件FunctionDescription

相關文章