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;串…