当前位置:   article > 正文

harmony 鸿蒙安全和高效的使用N-API开发Native模块_鸿蒙native开发(1)

harmony 鸿蒙安全和高效的使用N-API开发Native模块_鸿蒙native开发(1)

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

NULL,
NULL,
NULL,
napi_enumerable|napi_static,
NULL};
  • 1
  • 2
  • 3
  • 4
  • 5

NODE_API_CALL(env, napi_create_object(env, &return_value));

status = napi_define_class(NULL,
“TrackedFunction”,
NAPI_AUTO_LENGTH,
TestDefineClass,
NULL,
1,
&property_descriptor,
&result);
SaveConstructor(env, result);

}




---



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

// 2、由开发者自己管理 constructor 对象的生命周期
napi_status SaveConstructor(napi_env env, napi_value constructor) {
return napi_create_reference(env, constructor, 1, &g_constructor);
};

napi_status GetConstructor(napi_env env) {
napi_value constructor;
return napi_get_reference_value(env, g_constructor, &constructor);
};


##### 使用案例2:napi\_wrap


开发者使用 napi\_wrap 接口,可以将 native 对象和 js 对象绑定,当 js 对象被 GC 回收时,需要通过回调函数对 native 对象的资源进行清理。napi\_wrap 接口本质上也是创建了一个 napi\_ref,开发者可以根据业务需要,选择由系统来管理创建的 napi\_ref,或是自行释放创建的 napi\_ref。



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

// 用法1:napi_wrap不需要接收创建的napi_ref,最后一个参数传递nullptr,创建的napi_ref由系统管理,不需要用户手动释放
napi_wrap(env, jsobject, nativeObject, cb, nullptr, nullptr);

// 用法2:napi_wrap需要接收创建的napi_ref,最后一个参数不为nullptr,返回的napi_ref需要用户手动释放,否则会内存泄漏
napi_ref result;
napi_wrap(env, jsobject, nativeObject, cb, nullptr, &result);
// 当jsobject和result后续不再使用时,及时调用napi_remove_wrap释放result
napi_value result1;
napi_remove_wrap(env, jsobject, result1)


### 跨语言调用开销


#### 接口调用


跨语言调用是指在一个程序中使用多种编程语言编写的代码,并且这些代码可以相互调用和交互,ArkTS 调用 C++ 就是一种跨语言调用的方式。使用 N-API 进行函数调用会引入一定的开销,因为需要进行上下文切换、参数传递、函数调用和返回值处理等,这些过程都涉及到一些性能开销。目前,通过 N-API 接口实现 ArkTS 调用 C++ 的场景大致分为三类:ArkTS 直接调用 C++ 接口、ArkTS 监听 C++ 接口以及 ArkTS 接收 C++ 回调。频繁的跨语言接口调用可能会影响业务性能,因此需要开发者合理的设计接口调用频率。


#### 数值转换


使用 N-API 进行 ArkTS 与 C++ 之间的数据转换,有如下建议: \* 减少数据转换次数:频繁的数据转换可能会导致性能下降,可以通过批量处理数据或者使用更高效的数据结构来优化性能; \* 避免不必要的数据复制:在进行数据转换时,可以使用 N-API 提供的接口来直接访问原始数据,而不是创建新的数据副本; \* 使用缓存:如果某些数据在多次转换中都会被使用到,可以考虑使用缓存来避免重复的数据转换。缓存可以减少不必要的计算,提高性能。


### 异步操作


对于IO、CPU密集型任务需要异步处理, 否则会造成主线程的阻塞。N-API 支持异步能力,允许应用程序在执行某个耗时任务时不会被阻塞,而是继续执行其他任务。当异步操作完成时,应用程序会收到通知,并可以处理异步操作的结果。


#### 异步示例


开发者可以通过如下示例将耗时任务用异步方式实现,大概逻辑包括以下三步: \* 用 napi\_create\_promise 接口创建 promise,将创建一个 deferred 对象并与 promise 一起返回,deferred 对象会绑定到已创建的 promise; \* 执行耗时任务,并将执行结果传递给 promise; \* 使用 napi\_resolve\_deferred 或 napi\_reject\_deffered 接口来 resolve 或 reject 创建的 promise,并释放 deferred 对象。



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

// 在executeCB、completeCB之间传递数据
struct AddonData {
napi_async_work asyncWork = nullptr;
napi_deferred deferred = nullptr;
napi_ref callback = nullptr;

double args[2] = {0};
double result = 0;
  • 1
  • 2

};

// 2、执行耗时任务,并将执行结果传递给 promise;
static void addExecuteCB(napi_env env, void *data) {
AddonData *addonData = (AddonData *)data;
addonData->result = addonData->args[0] + addonData->args[1];
};

// 3、使用 napi_resolve_deferred 或 napi_reject_deffered 接口来 resolve 或 reject 创建的 promise,并释放 deferred 对象;
static void addPromiseCompleteCB(napi_env env, napi_status status, void *data) {
AddonData *addonData = (AddonData *)data;
napi_value result = nullptr;
napi_create_double(env, addonData->result, &result);
napi_resolve_deferred(env, addonData->deferred, result);

if (addonData->callback != nullptr) {
    napi\_delete\_reference(env, addonData->callback);
}

// 删除异步 work
napi\_delete\_async\_work(env, addonData->asyncWork);
delete addonData;
addonData = nullptr;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

};

// 1、用 napi_create_promise 接口创建 promise,将创建一个 deferred 对象并与 promise 一起返回,deferred
// 对象会绑定到已创建的 promise;
static napi_value addPromise(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2];
napi_value thisArg = nullptr;
napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr);

napi_valuetype valuetype0;
napi\_typeof(env, args[0], &valuetype0);
napi_valuetype valuetype1;
napi\_typeof(env, args[1], &valuetype1);
if (valuetype0 != napi_number||valuetype1 != napi_number) {
    napi\_throw\_type\_error(env, nullptr, "Wrong arguments. 2 numbers expected.");
    return NULL;
}

napi_value promise = nullptr;
napi_deferred deferred = nullptr;
napi\_create\_promise(env, &deferred, &promise);

// 异步工作项上下文用户数据,传递到异步工作项的execute、complete之间传递数据
auto addonData = new AddonData{
    .asyncWork = nullptr,
    .deferred = deferred,
};

napi\_get\_value\_double(env, args[0], &addonData->args[0]);
napi\_get\_value\_double(env, args[1], &addonData->args[1]);

// 创建async work,创建成功后通过最后一个参数(addonData->asyncWork)返回async work的handle
napi_value resourceName = nullptr;
napi\_create\_string\_utf8(env, "addAsyncCallback", NAPI_AUTO_LENGTH, &resourceName);
napi\_create\_async\_work(env, nullptr, resourceName, addExecuteCB, addPromiseCompleteCB, (void \*)addonData,
                       &addonData->asyncWork);

// 将刚创建的async work加到队列,由底层去调度执行
napi\_queue\_async\_work(env, addonData->asyncWork);

return promise;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

}


在异步操作完成后,回调函数将被调用,并将结果传递给 Promise 对象。在 JavaScript 中,可以使用 Promise 对象的 then() 方法来处理异步操作的结果。



  • 1
  • 2
  • 3
  • 4
  • 5

import hilog from ‘@ohos.hilog’;
import testNapi from ‘libentry.so’

@Entry
@Component
struct TestAdd {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text(“hello world”)
.onClick(() => {
let num1 = 2;
let num2 = 3;
testNapi.addPromise(num1, num2).then((result) => {
hilog.info(0x0000, ‘testTag’, ‘%{public}d’, result);
})
})
}
.width(‘100%’)
.height(‘100%’)
}
}


#### 指定异步任务调度优先级


Function Flow 编程模型(Function Flow Runtime,FFRT)是一种基于任务和数据驱动的并发编程模型,允许开发者通过任务及其依赖关系描述的方式进行应用开发。方舟 ArkTS 运行时提供了扩展 qos 信息的接口,支持传入 qos,并调用 FFRT,根据系统资源使用情况降低功耗、提升性能。


* 接口示例:napi\_status napi\_queue\_async\_work\_with\_qos(napi\_env env, napi\_async\_work work, napi\_qos\_t qos)()


	+ [in] env:调用API的环境;
	+ [in] napi\_async\_work: 异步任务;
	+ [in] napi\_qos\_t: qos 等级;
* qos 等级定义:



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
typedef enum {
napi_qos_background = 0,
napi_qos_utility = 1,
napi_qos_default = 2,
napi_qos_user_initiated = 3,
} napi_qos_t;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

* N-API 层封装了对外的接口,对接 libuv 层 uv\_queue\_work\_with\_qos(uv\_loop\_t\* loop, uv\_work\_t\* req, uv\_work\_cb work\_cb, uv\_after\_work\_cb after\_work\_cb, uv\_qos\_t qos) 函数。
* 相较于已有接口 napi\_queue\_async\_work,增加了 qos 等级,用于控制任务调度的优先级。使用示例: “`cpp static void PromiseOnExec(napi\_env env, void \*data) { OH\_LOG\_INFO(LOG\_APP, “PromiseOnExec”); }



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

static void PromiseOnComplete(napi_env env, napi_status status, void *data) {
int number = *((int *)data); OH_LOG_INFO(LOG_APP, “PromiseOnComplete number = %{public}d”, number);
}

static napi_value Test(napi_env env, napi_callback_info info) {
napi_value resourceName = nullptr;
napi_create_string_utf8(env, “TestExample”, NAPI_AUTO_LENGTH, &resourceName);
napi_async_work async_work; int *data = new int(10); napi_create_async_work(env, nullptr, resourceName, PromiseOnExec, PromiseOnComplete, data, &async_work);
napi_queue_async_work_with_qos(env, async_work, napi_qos_default); return nullptr;
}


### 线程安全


如果应用需要进行大量的计算或者 IO 操作,使用并发机制可以充分利用多核 CPU 的优势,提高应用的处理效率。例如,图像处理、视频编码、数据分析等应用可以使用并发机制来提高处理速度。


虽然 N-API 本身不支持多线程并发操作,但是可以在多线程环境下进行一些数据交互,且需要格外注意线程安全。在多线程环境下,开发者可以使用 napi\_create\_threadsafe\_function 函数创建一个线程安全函数,然后在任意线程中调用。


\*\*应用场景:\*\*当 native 侧有其他线程,并且需要根据这些线程的完成结果调用 JavaScript 函数时,这些线程必须与 native 侧的主线程进行通信,才能在主线程中调用 JavaScript 函数。线程安全函数便提供了一种简化方法,避免了线程间通讯,同时可以回到主线程调用 JavaScript 函数。


#### 使用方法


##### ArkTS 侧传入回调函数



  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

struct Index {
@State message: string = ‘Hello World’

build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
testNapi.threadSafeTest((value) => {
hilog.info(0x0000, ‘testTag’, 'js callback value = ’ + value);
})
})
}
.width(‘100%’)
}
.height(‘100%’)
}
}


##### native 侧主线程中创建线程安全函数



  • 1
  • 2
  • 3
  • 4
  • 5

static void CallJs(napi_env env, napi_value js_cb, void *context, void *data) {

std::thread::id this_id = std::this_thread::get\_id();
OH\_LOG\_INFO(LOG_APP, "thread CallJs %{public}d.\n", this_id);
napi_status status;

status = napi\_get\_reference\_value(env, cbObj, &js_cb);

napi_valuetype valueType = napi_undefined;
napi\_typeof(env, js_cb, &valueType);
OH\_LOG\_INFO(LOG_APP, "CallJs js\_cb is napi\_function: %{public}d", valueType == napi_function);

OH\_LOG\_INFO(LOG_APP, "CallJs 0");
if (env != NULL) {
    napi_value undefined, js_the_prime;
    status = napi\_create\_int32(env, 666, &js_the_prime);
    OH\_LOG\_INFO(LOG_APP, "CallJs 1: %{public}d", status == napi_ok);
    status = napi\_get\_undefined(env, &undefined);
    OH\_LOG\_INFO(LOG_APP, "CallJs 2: %{public}d", status == napi_ok);

    napi_value ret;

    status = napi\_call\_function(env, undefined, js_cb, 1, &js_the_prime, &ret);
    OH\_LOG\_INFO(LOG_APP, "CallJs 3: %{public}d", status == napi_ok);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

}

napi_threadsafe_function tsfn;

static napi_value ThreadSafeTest(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value js_cb, work_name;
napi_status status;

status = napi\_get\_cb\_info(env, info, &argc, &js_cb, NULL, NULL);
OH\_LOG\_INFO(LOG_APP, "ThreadSafeTest 0: %{public}d", status == napi_ok);

status = napi\_create\_reference(env, js_cb, 1, &cbObj);
OH\_LOG\_INFO(LOG_APP, "napi\_create\_reference of js\_cb to cbObj: %{public}d", status == napi_ok);

status =
    napi\_create\_string\_utf8(env, "Node-API Thread-safe Call from Async Work Item", NAPI_AUTO_LENGTH, &work_name);
OH\_LOG\_INFO(LOG_APP, "ThreadSafeTest 1: %{public}d", status == napi_ok);

std::thread::id this_id = std::this_thread::get\_id();
OH\_LOG\_INFO(LOG_APP, "thread ThreadSafeTest %{public}d.\n", this_id);

napi_valuetype valueType = napi_undefined;
napi\_typeof(env, js_cb, &valueType);
OH\_LOG\_INFO(LOG_APP, "ThreadSafeTest js\_cb is napi\_function: %{public}d", valueType == napi_function);

status = napi\_create\_threadsafe\_function(env, js_cb, NULL, work_name, 0, 1, NULL, NULL, NULL, CallJs, &tsfn);
OH\_LOG\_INFO(LOG_APP, "ThreadSafeTest 2: %{public}d", status == napi_ok);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

}


##### 其他线程中调用线程安全函数



  • 1
  • 2
  • 3
  • 4
  • 5

std::thread t( {
std::thread::id this_id = std::this_thread::get_id();
OH_LOG_INFO(LOG_APP, “thread0 %{public}d.\n”, this_id);
napi_status status;
status = napi_acquire_threadsafe_function(tsfn);
OH_LOG_INFO(LOG_APP, “thread1 : %{public}d”, status == napi_ok);
status = napi_call_threadsafe_function(tsfn, NULL, napi_tsfn_blocking);
OH_LOG_INFO(LOG_APP, “thread2 : %{public}d”, status == napi_ok);
});
t.detach();


#### 线程函数使用注意事项


在多线程环境下,需要避免使用共享的数据结构和全局变量,以免竞争和冲突。同时,需要确保线程之间的同步和互斥,以避免数据不一致的情况发生。除此之外,仍需注意:


* 对线程安全函数的调用是异步进行的,对 JavaScript 回调的调用将被放置在任务队列中;
* 创建 napi\_threadsafe\_function 时,可以提供 napi\_finalize 回调。当线程安全函数即将被销毁时,将在主线程上调用此 napi\_finalize 回调;
* 在调用 napi\_create\_threadsafe\_function 时给定了上下文,可以从任何调用 napi\_get\_threadafe\_function\_context 的线程中获取。


**为了能让大家更好的学习鸿蒙 (OpenHarmony) 开发技术,这边特意整理了《鸿蒙 (OpenHarmony)开发学习手册》(共计890页),希望对大家有所帮助:[`https://qr21.cn/FV7h05`]( )**


#### 《鸿蒙 (OpenHarmony)开发学习手册》:[`https://qr21.cn/FV7h05`]( )


**入门必看:[`https://qr21.cn/FV7h05`]( )**


1. 应用开发导读(ArkTS)
2. ……


![](https://img-blog.csdnimg.cn/img_convert/4991b6612f7848b8226a785e9bf2fbe9.webp?x-oss-process=image/format,png)


**HarmonyOS 概念:[`https://qr21.cn/FV7h05`]( )**


1. 系统定义
2. 技术架构
3. 技术特性
4. 系统安全


![](https://img-blog.csdnimg.cn/img_convert/53c5d7c674e3b2bf227cbf917fccef69.webp?x-oss-process=image/format,png)


**如何快速入门:[`https://qr21.cn/FV7h05`]( )**


1. 基本概念
2. 构建第一个ArkTS应用
3. 构建第一个JS应用
4. ……


![](https://img-blog.csdnimg.cn/img_convert/52d468782ba786e32203d4ea7c7afc2b.webp?x-oss-process=image/format,png)


**开发基础知识:[`https://qr21.cn/FV7h05`]( )**


1. 应用基础知识
2. 配置文件
3. 应用数据管理
4. 应用安全管理
5. 应用隐私保护
6. 三方应用调用管控机制
7. 资源分类与访问
8. 学习ArkTS语言
9. ……


![](https://img-blog.csdnimg.cn/img_convert/2e9f9af56c68d6b543f76207bccf7bab.webp?x-oss-process=image/format,png)


**基于ArkTS 开发:[`https://qr21.cn/FV7h05`]( )**


1. Ability开发
2. UI开发
3. 公共事件与通知
4. 窗口管理
5. 媒体
6. 安全
7. 网络与链接
8. 电话服务
9. 数据管理
10. 后台任务(Background Task)管理
11. 设备管理
12. 设备使用信息统计
13. DFX
14. 国际化开发


![img](https://img-blog.csdnimg.cn/img_convert/f6c98897713c53af8e379cbe2d16ad55.png)
![img](https://img-blog.csdnimg.cn/img_convert/afcfd598c653cba63bb6f27389148965.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

 UI开发
3. 公共事件与通知
4. 窗口管理
5. 媒体
6. 安全
7. 网络与链接
8. 电话服务
9. 数据管理
10. 后台任务(Background Task)管理
11. 设备管理
12. 设备使用信息统计
13. DFX
14. 国际化开发


[外链图片转存中...(img-JNExQWx3-1715810881049)]
[外链图片转存中...(img-6tOnZ1dk-1715810881049)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/624073
推荐阅读
相关标签
  

闽ICP备14008679号