鴻蒙搜狐新聞如何在Native調用ArkTS方法

01

前言

鴻蒙作為一款新興的智能操作系統,現在適配鴻蒙系統的應用越來越多,同時會面臨三端兼容問題,如同一產品功能,需要維護iOS、Android、鴻蒙三端代碼。

拿文件上傳、下載功能場景舉例,同時要適配iOS、Android、鴻蒙三端,目前比較好的方案是文件上傳、下載功能下沉到C,三端native代碼可以復用,但是在鴻蒙上存在如下一個問題:

Native文件上傳、下載進度怎么從Native線程異步回調給ArkTs線程對應JS回調方法?

答案:通過Node-API提供的線程安全函數,可以把文件上傳、下載進度從native線程異步回調ArkTs線程JS回調方法。

Native、ArkTs線程異步回調

02

Node-API是什么

Node-API(以前稱為 N-API)是用于構建native功能的API,它獨立于底層的JavaScript引擎(例如 V8引擎),并作為 Node.js 本身的一部分進行維護。

Node-API結構層次圖
Node-API結構層次圖

Node.js是JavaScript運行時環境,用來支持JS代碼的執行,HarmonyOS Native API對Node-API的接口進行了封裝和重寫,支持Node-API標準款的部分接口,以此提供ArkTS/JS與C/C++模塊之間交互能力。

Node-API必須遵循一個原則: Node-API的調用必須和JS的調用線程一致。

1)Node-API接口只能在JS線程使用,JS調用Native的線程才能調用Node-API接口,Native子線程無法調用;

2)napi env是無法跨線程使用,如果使用全局變量將napi env保存下來,再在其他線程使用會崩潰。

上面提到的文件上傳場景,文件上傳是在native子線程,非JS線程,如果使用Node-API,必然會崩潰,但Node-API提供線程安全函數,通過線程安全函數可以在native線程里回調JS線程。

03

線程安全函數如何實現跨線程通信

3.1 線程安全函數

線程安全函數ThreadSafeFunction(簡稱TFS)為了解決跨線程的函數調用問題,可以將普通的JS函數封裝成線程安全函數,包裝之后的函數是允許在線程之間傳遞,線程安全函數的定義如下:

NAPI_EXTERN napi_status
napi_create_threadsafe_function(napi_env env,napi_value func,napi_value async_resource,napi_value async_resource_name,size_t max_queue_size,size_t initial_thread_count,void* thread_finalize_data,napi_finalize thread_finalize_cb,void* context,napi_threadsafe_function_call_js call_js_cb,napi_threadsafe_function* result);
參數解釋

env

調用API的環境

func

JS回調函數

async_resource

與將傳遞給可能的async_hooks init鉤子的異步工作關聯的可選對象

async_resource_name

一個JS字符串,用于為 async_hooks API提供的資源類型提供標識符

max_queue_size

事件隊列的最大大小,0為無限制

initial_thread_count

初始線程數,包括將使用此函數的主線程

thread_finalize_data

要傳遞給 thread_finalize_cb的可選數據

thread_finalize_cb

napi_threadsafe_function被銷毀時調用的可選函數

context

napi_threadsafe_function上下文

call_js_cb

可選回調,調用JS函數以響應不同線程上的調用,此回調將在主線程上調用

線程安全函數native交互圖
線程安全函數native交互圖

使用TFS一般分為如下幾步:

step1:native側創建線程安全函數,綁定ArkTs的API的callback和線程安全回調函數call\_js\_cb;

step2:native子線程執行異步任務,并在子線程中調用napi_call_threadsafe_function,將call_js_cb拋到事件循環中進行調度;

step3:在call_js_cb中通過調用napi_call_function將native異步任務的結果回調給ArkTs線程,進行相關的UI刷新操作。

3.2 EventLoop事件循環

Libuv是由C語言編寫的,基于事件驅動的異步I/O庫,利用編譯好的libuv庫文件,可以寫一個簡單事件驅動的例子:

#include "stdio.h"
#include "uv.h"int?main() {uv_loop_t *loop = uv_default_loop(); ?printf("hello libuv");uv_run(loop, UV_RUN_DEFAULT);
}
node事件循環.drawio.png
node事件循環.drawio.png

線程安全函數是怎么實現主線程、線程之間通信?

libuv通過循環不斷取出watcher隊列中的事件,uv__iot_t結構體保存了文件描述符,其對應一個事件和事件回調,通過uv__iot_t結構體來初始化 epoll_event,再使用epoll_wait來等待文件描述符上I/O事件,事件觸發之后調用對應的回調函數,通過epoll實現線程間的通信。

API解釋

uv_run(uv_loop_t* loop, uv_run_mode mode)

運行事件循環,有如下幾種mode:
UV_RUN_DEFAULT:運行事件循環直到沒有更多的活動的和被引用到的句柄或請求
UV_RUN_ONCE:輪詢I/O一次?
UV_RUN_NOWAIT:輪詢I/O一次但不會阻塞,如若沒有待處理的回調函數時

uv_loop_alive(const uv_loop_t* loop)

如果有被引用的活動句柄、活動請求或者循環里的關閉句柄時返回非零值,否則返回零值,表示事件不再活動

uv__io_t

IO觀察者,是一個結構體,描述了上層事件和事件回調信息

uv__io_poll(loop, timeout)

把新增需要被監聽的fd放到poll中,其內部有一個epoll_wait方法

uv_async_start、uv__io_start

注冊IO觀察者到事件loop里,并注冊需要監聽的事件

uv__async_send、uv_async_send

觸發事件執行,并執行事件的回調

04

鴻蒙搜狐新聞文件上傳場景Native側調用ArkTS側的系統能力實現

下面以鴻蒙搜狐新聞時間線視頻、圖片發布場景舉例,如何在native子線程上傳視頻、圖片,并把上傳進度、上傳錯誤、上傳完成狀態通過線程安全函數回調給ArkTS-UI線程,進行UI上傳進度更新等刷新UI操作。

4.1 native文件上傳調用ArkTs JS方法整體設計

鴻蒙搜狐新聞文件上傳、下載下沉到native,網絡請求使用了第三方庫curl,簽名計算算法使用了openssl庫,將curl、openssl編譯成so或者靜態a文件并實現文件上傳、下載功能。

鴻蒙搜狐新聞Native文件上傳通過線程安全函數調用ArkTs JS方法設計流程圖如下:

4.2 native文件上傳調用ArkTs JS方法整體設計詳細實現

native調用ArkTs JS方法效果圖

創建文件上傳線程安全函數是在ArkTs線程執行,調用文件上傳進度線程安全函數是在Native子線程,執行文件上傳進度回調JS方法是在ArkTs線程執行。

API方法實現

從上往下方法調用鏈如下:

uploadFile方法定義:

JS回調方法解釋

onProgressFun

文件上傳進度回調

onCompleteFun

文件上傳完成回調

onErrorFun

文件上傳錯誤回調

/*** 文件上傳** @param uploadUrl ?上傳url* @param filePath ? 本地文件路徑* @param onProgressFun 上傳進度回調 0~100* @param onCompleteFun 上傳完成回調* @param onErrorFun ?錯誤回調,errorCode:錯誤code,errorDesc:錯誤描述*/
export?const uploadFile: (uploadUrl: string, filePath: string,onProgressFun: (progress: number) => void, onCompleteFun: () => void, onErrorFun:(errorCode: number, errorDesc: string) =>void) => void;

鴻蒙Native上傳文件方法定義:

static napi_value NAPI_Global_uploadFile(napi_env env, napi_callback_info info) {size_t argc = 5;napi_value args[5] = {nullptr, nullptr, nullptr, nullptr, nullptr};... // 省略參數解析代碼實現 ? ?MultiFileManager::getInstance()->uploadFileWrapper(uploadUrl, filePath, env, args[2], args[3], args[4]);return?nullptr;
}/*** 上傳文件子線程執行** @param uploadUrl* @param filePath*/
void MultiFileManager::uploadFileWrapper(std::string uploadUrl, std::string filePath, napi_env env, napi_value onProgressFun,napi_value onCompleteFun, napi_value onErrorFun){UploadBridge::createThreadUploadCallBack(env,onProgressFun, onCompleteFun, onErrorFun);pool->enqueue([this](std::string uploadUrl, std::string filePath) { uploadFile(uploadUrl, filePath); },uploadUrl, filePath); ? ? ? ? ? ? ? ? ? ? ? ??
}

在上傳任務放到native子線程執行之前,先在ArkTs線程里創建線程安全函數,綁定ArkTs的上傳方法onProgressFun回調和線程安全回調函數callJsUploadOnProgress。

創建上傳文件進度線程安全函數定義:

createThreadUploadCallBack方法說明:

  • native線程執行文件上傳進度callUploadOnProgress方法后,napi_call_threadsafe_function會調用文件上傳進度線程安全函數,即底層會調用uv_async_send將JS文件上傳進度回調方法callJsUploadOnProgress拋到EventLoop事件循環中執行

  • 事件調度在ArkTs線程執行callJsUploadOnProgress方法,并回調JS的onProgressFun方法,ArkTs UI拿到上傳文件進度后更新UI

/*** 創建線程安全函數*?* @param env* @param onProgressFun* @param onCompleteFun* @param onErrorFun*/
void UploadBridge::createThreadUploadCallBack(napi_env env, napi_value onProgressFun, napi_value onCompleteFun, napi_value onErrorFun){uploadProgress = -1;napi_value workName;napi_create_string_utf8(env,?"workItem", NAPI_AUTO_LENGTH, &workName);// 創建線程安全函數napi_create_threadsafe_function(env, onProgressFun, NULL, workName, 0, 1, NULL, NULL, NULL, callJsUploadOnProgress, &onProgressTsFn);......
}

ArkTs線程執行JS上傳文件進度方法定義:

/*** ?JS文件上傳進度回調方法*?* @param env* @param js_cb* @param context* @param data*/
void UploadBridge::callJsUploadOnProgress(napi_env env, napi_value jsCallBack, void *context, void *data) {napi_value argv;napi_create_int32(env, uploadProgress, &argv);napi_value result = nullptr;napi_call_function(env, nullptr, jsCallBack, 1, &argv, &result);......
}

native線程調用文件上傳進度安全函數方法定義:

/*** 上傳文件** @param fileMeta*/
void MultiFileRequest::uploadFile(UploadFileMeta *fileMeta) {CURLcode ret;CURL *curlHandle;std::string response;curl_global_init(CURL_GLOBAL_ALL);curlHandle = curl_easy_init();if?(curlHandle == NULL) { // 初始化失敗UploadBridge::callUploadOnError(CURL_INIT_ERROR, getErrorDesc(CURL_INIT_ERROR));return;}curlHandle = appendTestProxy(curlHandle);......fileMeta->uploadInfo.file = uploadFile;fileMeta->uploadInfo.uploadedSize = fileMeta->uploadedSize;;fileMeta->uploadInfo.totalSize = fileMeta->totalSize;curl_easy_setopt(curlHandle, CURLOPT_UPLOAD, 1L);curl_easy_setopt(curlHandle, CURLOPT_PUT, 1l);curl_easy_setopt(curlHandle, CURLOPT_HTTPHEADER, headers);curl_easy_setopt(curlHandle, CURLOPT_URL, fileMeta->url.c_str());// 傳入文件上傳進度回調方法curl_easy_setopt(curlHandle, CURLOPT_READFUNCTION, MultiFileRequest::uploadReadData);......curl_slist_free_all(headers);fclose(uploadFile);curl_easy_cleanup(curlHandle);curl_global_cleanup();
}/*** 讀取上傳文件二進制數據**/
size_t MultiFileRequest::uploadReadData(void *ptr, size_t size, size_t nMem, void *userp) {UploadInfo *uploadInfo = static_cast<UploadInfo *>(userp);unsigned long nread;size_t retcode = fread(ptr, size, nMem, uploadInfo->file);if?(retcode > 0) {nread = (unsigned long)retcode;uploadInfo->uploadedSize += nread;}if?(uploadInfo->totalSize > 0) {// 文件上傳過程中讀文件可能會出錯,會從文件的某個offset重新上傳,導致已上傳文件大小大于文件總大小的情況,避免出現進度大于100%的情況if?(uploadInfo->uploadedSize <= uploadInfo->totalSize) {?UploadBridge::callUploadOnProgress((float)uploadInfo->uploadedSize / uploadInfo->totalSize * 100);}?else?{Utils::logD ("retry uploadFile");}}return?retcode;
}/*** antive文件上傳進度回調方法*?* @param progress*/
void UploadBridge::callUploadOnProgress(int progress) {Utils::logD("上傳文件進度是:"?+ std::to_string(progress));if?(uploadProgress != progress) {uploadProgress = progress;napi_acquire_threadsafe_function(onProgressTsFn);napi_call_threadsafe_function(onProgressTsFn, NULL, napi_tsfn_nonblocking);}
}

05

最后

在Native側調用ArkTS側的系統能力,除了使用線程安全函數外,還可以直接使用libuv,但需要額外編譯libuv源碼,如果需要了解更多libuv的知識,可以參見(https://github.com/libuv/libuv)。libuv結構圖



本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/915461.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/915461.shtml
英文地址,請注明出處:http://en.pswp.cn/news/915461.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java行為型模式---中介者模式

中介者模式基礎概念中介者模式&#xff08;Mediator Pattern&#xff09;是一種行為型設計模式&#xff0c;其核心思想是通過一個中介對象來封裝一系列對象之間的交互&#xff0c;使各對象不需要顯式地相互引用&#xff0c;從而降低耦合度&#xff0c;并可以獨立地改變它們之間…

Python爬蟲實戰:研究Korean庫相關技術

一、引言 1.1 研究背景與意義 隨著韓流文化在全球的傳播,韓語網頁內容急劇增加。韓國在科技、娛樂等領域的信息具有重要研究價值。然而,韓語獨特的黏著語特性(如助詞體系、詞尾變化)給信息處理帶來挑戰。傳統爬蟲缺乏對韓語語言特點的針對性處理,本研究旨在開發一套完整…

表單校驗--數組各項獨立校驗

寫需求時遇到一個這樣的問題&#xff0c;就是校樣項是多個的&#xff0c;但是其字段名稱相同這時我們可以這樣校驗&#xff0c;注意字段之間的關聯性<div v-for"(item,index) in formData.hospitalDoctorList" :key"item.key || index"><el-form-…

基于SpringBoot和leaflet-timeline-slider的歷史敘事GIS展示-以哪吒2的海外國家上映安排為例

目錄 前言 一、哪吒2的海外之路 1、海外征戰歷程 2、上映國家空間查詢 二、后端接口的實現 1、模型層的實現 2、上映時間與國家 3、控制層的實現 三、基于leaflet-timeline-slider的前端實現 1、時間軸控件的引入及定義 2、時間軸綁定事件 3、成果展示 四、總結 前言…

tar 解壓:Cannot change ownership to uid 1000, gid 1000: Operation not permitted

tar 解壓 tar.gz 壓縮包報錯&#xff1a; # tar xzf $INPUT_FOLDER/archive.tar.gz -C /mnt/test-nas/[..] tar: xx.jpg: Cannot change ownership to uid 1000, gid 1000: Operation not permitted原因是用普通用戶執行的解壓縮腳本&#xff0c;用root用戶執行tar解壓縮&…

騰訊客戶端開發面試真題分析

以下是針對騰訊客戶端開發工程師面試問題的分類與高頻問題分析&#xff08;基于??105道問題&#xff0c;總出現次數118次??&#xff09;。按技術領域整合為??7大類別??&#xff0c;按占比排序并精選高頻問題標注優先級&#xff08;1-5&#x1f31f;&#xff09;&#x…

線上問題排查之【CPU飆高100%】

目錄 案例 發現問題 排查問題 步驟一 步驟二 步驟三 案例 import java.util.concurrent.TimeUnit;/*** 簡單寫一個CPU飆高的案例*/ public class CpuLoadUp {// 這里定義了一個標識private volatile static int flag 0;public static void main(String[] args) {// 執行…

c語言 進階 動態內存管理

動態內存管理1. 為什么存在動態內存分配2. 動態內存函數的介紹?2.1 malloc 和 freemalloc 函數free 函數2.2內存泄漏2.3 calloc2.4 realloc3. 常見的動態內存錯誤3.1 對NULL指針的解引用操作3.2 對動態開辟空間的越界訪問3.3 對非動態開辟內存使用free釋放3.4 使用free釋放一塊…

Redis的五大基本數據類型

一、Redis基本知識與Redis鍵&#xff08;key&#xff09;常用操作命令。redis的默認端口6379。mysql默認端口號3306。 默認16個數據庫&#xff0c;類似數組的下標從0開始&#xff0c;初始默認使用0號庫。可以使用select index來切換數據庫&#xff0c;如&#xff1a;select 1&a…

達夢數據庫JSON_TABLE使用說明

在達夢數據庫&#xff08;DM Database&#xff09;中&#xff0c;將 JSON 數據轉換為表格形式可以使用內置的 JSON_TABLE 函數。以下是詳細步驟和示例&#xff1a;1. 核心函數&#xff1a;JSON_TABLE JSON_TABLE 用于將 JSON 數據解析為關系表結構&#xff0c;支持從 JSON 對象…

A316-1926-V1 USB多路高清音頻解碼器模組技術解析

隨著數字音頻技術的不斷發展&#xff0c;高品質音頻解決方案的需求日益增長。本文將介紹一款基于XMOS技術的高性能USB音頻解碼器模組——A316-1926-V1&#xff0c;這是一款專為高清音頻應用設計的專業模組。核心技術與特性A316-1926-V1是一款集成了多項先進技術的USB多路高清音…

.NET 8 中的 KeyedService

.NET 8 中的 KeyedService&#xff1a;新特性解析與使用示例 一、引言 在 .NET 8 的 Preview 7 版本中&#xff0c;引入了 KeyedService 支持。這一特性為開發者提供了按名稱&#xff08;name&#xff09;獲取服務的便利&#xff0c;在某些場景下&#xff0c;開發者無需再自行…

Paimon對比基于消息隊列(如Kafka)的傳統實時數倉方案的優勢

弊端&#xff1a;數據重復 -> 優勢&#xff1a;Paimon 主鍵表原生去重原方案弊端 (Kafka)問題: 消息隊列&#xff08;Kafka&#xff09;是僅支持追加&#xff08;Append-Only&#xff09;的日志流。當 Flink 作業發生故障恢復&#xff08;Failover&#xff09;或業務邏輯迭代…

Linux Shell 命令 + 項目場景

shell 命令1. 基礎文件操作命令1.1 ls - 列出目錄內容1.2 find - 文件搜索2. 版本控制命令2.1 git - 版本控制系統2.2 高級 Git 操作3. 文本搜索命令3.1 grep - 文本搜索3.2 高級搜索技巧4. Android 構建系統命令4.1 source - 加載環境變量4.2 lunch - 選擇構建目標4.3 m - And…

A316-Mini-V1:超小尺寸USB高清音頻解碼器模組技術探析

引言 隨著便攜式音頻設備的普及&#xff0c;對小型化、高性能音頻解決方案的需求日益增長。本文將介紹一款極致小型化的高性能USB音頻解碼器模組——A316-Mini-V1&#xff0c;這是一款基于XMOS XU316芯片的微型音頻處理模組。產品概述 A316-Mini-V1是一款專為小尺寸產品設計的M…

低代碼平臺買saas好還是私有化好

選擇低代碼平臺采用SaaS還是私有化部署&#xff0c;應根據企業具體情況考慮安全性、成本控制、維護難度、擴展需求等因素。 其中&#xff0c;安全性是決定企業選擇的重要因素之一。私有化部署意味著企業能夠完全掌控數據和系統的安全管理&#xff0c;更適合對數據安全要求極高的…

基于SkyWalking的微服務APM監控實戰指南

基于SkyWalking的微服務APM監控實戰指南 1. 業務場景描述 隨著微服務在生產環境中大規模應用&#xff0c;系統鏈路復雜、實例彈性伸縮、灰度發布等特點都給性能監控和問題診斷帶來了新的挑戰。傳統的單機或輕量級監控方案已無法滿足微服務環境下的全鏈路、分布式追蹤和實時告警…

Python 進階(五): Excel 基本操作

目錄 1. 概述2. 寫入 2.1 使用 xlwt2.2 使用 XlsxWriter 3. 讀取4. 修改 1. 概述 在現實中&#xff0c;很多工作都需要與數據打交道&#xff0c;Excel 作為常用的數據處理工具&#xff0c;一直備受人們的青睞&#xff0c;而大部分人都是手動操作 Excel&#xff0c;如果數據量…

32、鴻蒙Harmony Next開發:使用動畫-動畫概述

???屬性動畫轉場動畫粒子動畫組件動畫動畫曲線動畫銜接動畫效果幀動畫&#xff08;ohos.animator&#xff09; UI&#xff08;用戶界面&#xff09;中包含開發者與設備進行交互時所看到的各種組件&#xff08;如時間、壁紙等&#xff09;。屬性作為接口&#xff0c;用于控制…

【STM32】485接口原理

485 通信實驗 這篇文章是對 RS485通信 的原理、硬件連接、接口芯片&#xff08;SP3485&#xff09;、總線結構等都有詳盡的說明。我們在此處進行清晰有條理的講解整理&#xff0c;便于學習和實驗操作。 在了解485接口通信原理之前&#xff0c;我們先復習一下串口&#xff1a;串…