Native API 在 HarmonyOS 应用工程中的使用指导

2023-12-13 04:03:40

HarmonyOS 的应用必须用 js 来桥接 native。需要使用ace_napi仓中提供的 napi 接口来处理 js 交互。napi 提供的接口名与三方 Node.js 一致,目前支持部分接口,符号表见 ace_napi 仓中的 libnapi.ndk.json 文件。

开发流程

在 DevEco?Studio 的模板工程中包含使用 Native?API 的默认工程,使用 File->New->Create?Project 创建 Native?C++模板工程。创建后在 main 目录下会包含 cpp 目录,可以使用 ace_napi 仓下提供的 napi 接口进行开发。

js 侧通过 import 引入 native 侧包含处理 js 逻辑的 so,如:import?hello?from?'libhello.so',意为使用 libhello.so 的能力,native 侧通过 napi 接口创建的 js 对象会给到应用 js 侧的 hello 对象。

开发建议

注册建议

●?nm_register_func 对应的函数需要加上 static,防止与其他 so 里的符号冲突。

●?模块注册的入口,即使用__attribute__((constructor))修饰的函数的函数名需要确保不与其他模块重复。

so 命名规则

so 命名必须符合以下规则:

●?每个模块对应一个 so。

●?如模块名为 hello,则 so 的名字为 libhello.so,napi_module 中 nm_modname 字段应为 hello,大小写与模块名保持一致,应用使用时写作:import?hello?from?'libhello.so'。

js 对象线程限制

ark 引擎会对 js 对象线程使用进行保护,使用不当会引起应用 crash,因此需要遵循如下原则:

●?napi 接口只能在 js 线程使用。

●?env 与线程绑定,不能跨线程使用。native 侧 js 对象只能在创建时的线程使用,即与线程所持有的 env 绑定。

头文件引入限制

在使用 napi 的对象和方法时需要引用"napi/native_api.h"。否则在只引入三方库头文件时,会出现接口无法找到的编译报错。

napi_create_async_work 接口说明

napi_create_async_work 里有两个回调:

●?execute:用于异步处理业务逻辑。因为不在 js 线程中,所以不允许调用 napi 的接口。业务逻辑的返回值可以返回到 complete 回调中处理。

●?complete:可以调用 napi 的接口,将 execute 中的返回值封装成 js 对象返回。此回调在 js 线程中执行。

napi_status napi_create_async_work(napi_env env,                                   napi_value async_resource,                                   napi_value async_resource_name,                                   napi_async_execute_callback execute,                                   napi_async_complete_callback complete,                                   void* data,                                   napi_async_work* result)

storage?模块——同步异步接口封装

模块简介

本示例通过实现?storage?模块展示了同步和异步方法的封装。storage?模块实现了数据的保存、获取、删除、清除功能。

接口声明

import { AsyncCallback } from?'./basic';
declare namespace storage {
  function get(key: string, callback: AsyncCallback<string>): void;
  function get(key: string, defaultValue: string, callback: AsyncCallback<string>): void;
  function get(key: string, defaultValue?: string): Promise<string>;
  function set(key: string, value: string, callback: AsyncCallback<string>): void;
  function remove(key: string, callback: AsyncCallback<void>): void;
  function clear(callback: AsyncCallback<void>): void;
  function getSync(key: string, defaultValue?: string): string;
  function setSync(key: string, value: string): void;
  function removeSync(key: string): void;
  function clearClear(): void;
}
export default storage;

具体实现

完整代码参见仓下路径:OpenHarmony/arkui_napi仓库 sample/native_module_storage/

1、模块注册

如下,注册了 4 个同步接口(getSync、setSync、removeSync、clearSync)、4 个异步接口(get、set、remove、clear)。

/***********************************************
 * Module export and register
 ***********************************************/
static napi_value StorageExport(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        DECLARE_NAPI_FUNCTION("get", JSStorageGet),
        DECLARE_NAPI_FUNCTION("set", JSStorageSet),
        DECLARE_NAPI_FUNCTION("remove", JSStorageDelete),
        DECLARE_NAPI_FUNCTION("clear", JSStorageClear),

        DECLARE_NAPI_FUNCTION("getSync", JSStorageGetSync),
        DECLARE_NAPI_FUNCTION("setSync", JSStorageSetSync),
        DECLARE_NAPI_FUNCTION("deleteSync", JSStorageDeleteSync),
        DECLARE_NAPI_FUNCTION("clearSync", JSStorageClearSync),
    };
    NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
    return exports;
}

// storage module
static napi_module storage_module = {.nm_version = 1,
                                     .nm_flags = 0,
                                     .nm_filename = nullptr,
                                     .nm_register_func = StorageExport,
                                     .nm_modname = "storage",
                                     .nm_priv = ((void*)0),
                                     .reserved = {0}};

// storage module register
extern "C" __attribute__((constructor)) void StorageRegister()
{
    napi_module_register(&storage_module);
}

2、getSync?函数实现

如上注册时所写,getSync?对应的函数是?JSStorageGetSync?。从?gKeyValueStorage?中获取数据后,创建一个字符串对象并返回。

static napi_value JSStorageGetSync(napi_env env, napi_callback_info info)
{
    GET_PARAMS(env, info, 2);
    NAPI_ASSERT(env, argc >= 1, "requires?1?parameter");
    char key[32] = {0};
    size_t keyLen = 0;
    char value[128] = {0};
    size_t valueLen = 0;

    //?参数解析
    for (size_t i = 0; i < argc; i++) {
        napi_valuetype valueType;
        napi_typeof(env, argv[i], &valueType);

        if (i == 0 && valueType == napi_string) {
            napi_get_value_string_utf8(env, argv[i], key, 31, &keyLen);
        } else if (i == 1 && valueType == napi_string) {
            napi_get_value_string_utf8(env, argv[i], value, 127, &valueLen);
            break;
        } else {
            NAPI_ASSERT(env, false, "type?mismatch");
        }
    }

    //?获取数据的业务逻辑,这里简单地从一个全局变量中获取
    auto itr = gKeyValueStorage.find(key);
    napi_value result = nullptr;
    if (itr != gKeyValueStorage.end()) {
        //?获取到数据后创建一个string类型的JS对象
        napi_create_string_utf8(env, itr->second.c_str(), itr->second.length(), &result);
    } else if (valueLen > 0) {
        //?没有获取到数据使用默认值创建JS对象
        napi_create_string_utf8(env, value, valueLen, &result);
    } else {
        NAPI_ASSERT(env, false, "key?does?not?exist");
    }
    //?返回结果
    return result;
}

3、get?函数实现

如上注册时所写,get 对应的函数式 JSStorageGet。

static?napi_value?JSStorageGet(napi_env?env,?napi_callback_info?info)
{
????GET_PARAMS(env,?info,?3);
????NAPI_ASSERT(env,?argc?>=?1,?"requires?1?parameter");

????//?StorageAsyncContext是自己定义的一个类,用于保存执行过程中的数据
????StorageAsyncContext*?asyncContext?=?new?StorageAsyncContext();

????asyncContext->env?=?env;

????//?获取参数
????for?(size_t?i?=?0;?i?<?argc;?i++)?{
????????napi_valuetype?valueType;
????????napi_typeof(env,?argv[i],?&valueType);

????????if?(i?==?0?&&?valueType?==?napi_string)?{
????????????napi_get_value_string_utf8(env,?argv[i],?asyncContext->key,?31,?&asyncContext->keyLen);
????????}?else?if?(i?==?1?&&?valueType?==?napi_string)?{
????????????napi_get_value_string_utf8(env,?argv[i],?asyncContext->value,?127,?&asyncContext->valueLen);
????????}?else?if?(i?==?1?&&?valueType?==?napi_function)?{
????????????napi_create_reference(env,?argv[i],?1,?&asyncContext->callbackRef);
????????????break;
????????}?else?if?(i?==?2?&&?valueType?==?napi_function)?{
????????????napi_create_reference(env,?argv[i],?1,?&asyncContext->callbackRef);
????????}?else?{
????????????NAPI_ASSERT(env,?false,?"type?mismatch");
????????}
????}

????napi_value?result?=?nullptr;

????//?根据参数判断开发者使用的是promise还是callback
????if?(asyncContext->callbackRef?==?nullptr)?{
????????//?创建promise
????????napi_create_promise(env,?&asyncContext->deferred,?&result);
????}?else?{
????????napi_get_undefined(env,?&result);
????}

????napi_value?resource?=?nullptr;
????napi_create_string_utf8(env,?"JSStorageGet",?NAPI_AUTO_LENGTH,?&resource);

????napi_create_async_work(
????????env,?nullptr,?resource,
????????//?回调1:此回调由napi异步执行,里面就是需要异步执行的业务逻辑。由于是异步线程执行,所以不要在此通过napi接口操作JS对象。
????????[](napi_env?env,?void*?data)?{
????????????StorageAsyncContext*?asyncContext?=?(StorageAsyncContext*)data;
????????????auto?itr?=?gKeyValueStorage.find(asyncContext->key);
????????????if?(itr?!=?gKeyValueStorage.end())?{
????????????????strncpy_s(asyncContext->value,?127,?itr->second.c_str(),?itr->second.length());
????????????????asyncContext->status?=?0;
????????????}?else?{
????????????????asyncContext->status?=?1;
????????????}
????????},
????????//?回调2:此回调在上述异步回调执行完后执行,此时回到了JS线程来回调开发者传入的回调
????????[](napi_env?env,?napi_status?status,?void*?data)?{
????????????StorageAsyncContext*?asyncContext?=?(StorageAsyncContext*)data;
????????????napi_value?result[2]?=?{0};
????????????if?(!asyncContext->status)?{
????????????????napi_get_undefined(env,?&result[0]);
????????????????napi_create_string_utf8(env,?asyncContext->value,?strlen(asyncContext->value),?&result[1]);
????????????}?else?{
????????????????napi_value?message?=?nullptr;
????????????????napi_create_string_utf8(env,?"key?does?not?exist",?NAPI_AUTO_LENGTH,?&message);
????????????????napi_create_error(env,?nullptr,?message,?&result[0]);
????????????????napi_get_undefined(env,?&result[1]);
????????????}
????????????if?(asyncContext->deferred)?{
????????????????//?如果走的是promise,那么判断回调1的结果
????????????????if?(!asyncContext->status)?{
????????????????????//?回调1执行成功(status为1)时触发,也就是触发promise里then里面的回调
????????????????????napi_resolve_deferred(env,?asyncContext->deferred,?result[1]);
????????????????}?else?{
????????????????????//?回调1执行失败(status为0)时触发,也就是触发promise里catch里面的回调
????????????????????napi_reject_deferred(env,?asyncContext->deferred,?result[0]);
????????????????}
????????????}?else?{
????????????????//?如果走的是callback,则通过napi_call_function调用callback回调返回结果
????????????????napi_value?callback?=?nullptr;
????????????????napi_value?returnVal;
????????????????napi_get_reference_value(env,?asyncContext->callbackRef,?&callback);
????????????????napi_call_function(env,?nullptr,?callback,?2,?result,?&returnVal);
????????????????napi_delete_reference(env,?asyncContext->callbackRef);
????????????}
????????????napi_delete_async_work(env,?asyncContext->work);
????????????delete?asyncContext;
????????},
????????(void*)asyncContext,?&asyncContext->work);
????napi_queue_async_work(env,?asyncContext->work);

????return?result;
}

4、js 示例代码

import?storage?from?'libstorage.so';
export?default?{  testGetSync() {      const?name?=?storage.getSync('name');????console.log('name?is?'?+?name);  },  testGet() {????storage.get('name')    .then(date?=> {????????console.log('name?is?'?+?data);    })    .catch(error?=> {????????console.log('error:?'?+?error);    });  }}


NetServer?模块——native 与 js 对象绑定

模块简介

本示例展示了 on/off/once 订阅方法的实现,同时也包含了?C++?与?js 对象通过?wrap?接口的绑定。NetServer?模块实现了一个网络服务。

接口声明

export?class?NetServer {??function?start(port:?number):?void;??function?stop():?void;??function?on('start'?| 'stop',?callback: Function):?void;??function?once('start'?| 'stop',?callback: Function):?void;??function?off('start'?| 'stop',?callback: Function):?void;}


具体实现

完整代码参见:OpenHarmony/arkui_napi仓库 sample/native_module_netserver/

1、模块注册

static?napi_value?NetServer::Export(napi_env?env,?napi_value?exports)
{
    const char?className[] = "NetServer";
????napi_property_descriptor?properties[] = {
        DECLARE_NAPI_FUNCTION("start", JS_Start),
        DECLARE_NAPI_FUNCTION("stop", JS_Stop),
        DECLARE_NAPI_FUNCTION("on", JS_On),
        DECLARE_NAPI_FUNCTION("once", JS_Once),
        DECLARE_NAPI_FUNCTION("off", JS_Off),
    };
????napi_value?netServerClass?=?nullptr;

    napi_define_class(env,?className, sizeof(className), JS_Constructor,?nullptr, countof(properties),?properties,
                      &netServerClass);

    napi_set_named_property(env,?exports, "NetServer",?netServerClass);

    return?exports;
}

2、在构造函数中绑定?C++?与?JS?对象

napi_value?NetServer::JS_Constructor(napi_env?env,?napi_callback_info?cbinfo){????napi_value?thisVar?=?nullptr;????void*?data?=?nullptr;    napi_get_cb_info(env,?cbinfo,?nullptr,?nullptr, &thisVar, &data);
    //?C++?Native对象,准备与JS对象映射在一起    NetServer*?netServer?=?new?NetServer(env,?thisVar);
    //?使用napi_wrap将netServer与thisVar(即当前创建的这个JS对象)做绑定    napi_wrap(????????env,?thisVar,?netServer,        //?JS对象由引擎自动回收释放,当JS对象释放时触发该回调,在改回调中释放netServer        [](napi_env?env,?void*?data,?void*?hint) {            printf("NetServer::Destructor\n");            NetServer*?netServer?= (NetServer*)data;????????????delete?netServer;        },????????nullptr,?nullptr);
    return?thisVar;}


3、从?JS?对象中取出?C++?对象

napi_value?NetServer::JS_Start(napi_env?env,?napi_callback_info?cbinfo){????size_t?argc?= 1;????napi_value?argv[1] = {0};????napi_value?thisVar?=?nullptr;????void*?data?=?nullptr;    napi_get_cb_info(env,?cbinfo, &argc,?argv, &thisVar, &data);
    NetServer*?netServer?=?nullptr;    //?通过napi_unwrap从thisVar中取出C++对象    napi_unwrap(env,?thisVar, (void**)&netServer);
    NAPI_ASSERT(env,?argc?>= 1, "requires?1?parameter");
????napi_valuetype?valueType;    napi_typeof(env,?argv[0], &valueType);    NAPI_ASSERT(env,?valueType?==?napi_number, "type?mismatch?for?parameter?1");
????int32_t?port?= 0;    napi_get_value_int32(env,?argv[0], &port);
    //?开启服务????netServer->Start(port);
????napi_value?result?=?nullptr;    napi_get_undefined(env, &result);    return?result;}

netServer->Start 后回调通过 on 注册的 start 事件。

int?NetServer::Start(int?port){    printf("NetServer::Start?thread_id:?%ld?\n", uv_thread_self());
    struct sockaddr_in?addr;????int?r;
    uv_ip4_addr("0.0.0.0",?port, &addr);
????r?= uv_tcp_init(loop_, &tcpServer_);    if (r) {        fprintf(stderr, "Socket?creation?error\n");        return 1;    }
????r?= uv_tcp_bind(&tcpServer_, (const struct sockaddr*)&addr, 0);    if (r) {        fprintf(stderr, "Bind?error\n");        return 1;    }
????r?= uv_listen((uv_stream_t*)&tcpServer_, SOMAXCONN, OnConnection);    if (r) {        fprintf(stderr, "Listen?error?%s\n", uv_err_name(r));        return 1;    }
    //?服务启动后触发“start”事件    Emit("start",?nullptr);
    return 0;}

4、注册或释放(on/off/once)事件,以?on?为例

napi_value?NetServer::JS_On(napi_env?env,?napi_callback_info?cbinfo)
{
????size_t?argc?= 2;
????napi_value?argv[2] = {0};
????napi_value?thisVar?= 0;
????void*?data?=?nullptr;
    napi_get_cb_info(env,?cbinfo, &argc,?argv, &thisVar, &data);

    NetServer*?netServer?=?nullptr;
    //?通过napi_unwrap取出NetServer指针
    napi_unwrap(env,?thisVar, (void**)&netServer);

    NAPI_ASSERT(env,?argc?>= 2, "requires?2?parameter");

    //?参数类型校验
????napi_valuetype?eventValueType;
    napi_typeof(env,?argv[0], &eventValueType);
    NAPI_ASSERT(env,?eventValueType?==?napi_string, "type?mismatch?for?parameter?1");

????napi_valuetype?eventHandleType;
    napi_typeof(env,?argv[1], &eventHandleType);
    NAPI_ASSERT(env,?eventHandleType?==?napi_function, "type?mismatch?for?parameter?2");

    char type[64] = {0};
????size_t?typeLen?= 0;

    napi_get_value_string_utf8(env,?argv[0], type, 63, &typeLen);

    //?注册事件handler
????netServer->On((const char*)type,?argv[1]);

????napi_value?result?=?nullptr;
    napi_get_undefined(env, &result);
    return?result;
}

5、js 示例代码

import?{ NetServer }?from?'libnetserver.so';
export?default?{  testNetServer() {??????var?netServer?=?new?NetServer();??????netServer.on('start', (event) => {});??????netServer.start(1000); //?端口号1000,?start完成后回调上面注册的?“start”?回调  }}

在非 JS 线程中回调 JS 接口

模块简介

本示例介绍如何在非 JS 线程中回调 JS 应用的回调函数。例如 JS 应用中注册了某个 sensor 的监听,这个 sensor 的数据是由一个 SA 服务来上报的,当 SA 通过 IPC 调到客户端时,此时的执行线程是一个 IPC 通信线程,与应用的 JS 线程是两个不同的线程。这时就需要将执行 JS 回调的任务抛到 JS 线程中才能执行,否则会出现崩溃。

具体实现

完整代码参见:OpenHarmony/arkui_napi仓库 sample/native_module_callback/

1、模块注册

如下,注册了 1 个接口 test,会传入一个参数,类型为包含一个参数的函数。

/***********************************************
?*?Module?export?and?register
?***********************************************/
static?napi_value?CallbackExport(napi_env?env,?napi_value?exports)
{
    static?napi_property_descriptor?desc[] = {
        DECLARE_NAPI_FUNCTION("test", JSTest)
    };
    NAPI_CALL(env, napi_define_properties(env,?exports, sizeof(desc) / sizeof(desc[0]),?desc));
    return?exports;
}

//?callback?module?define
static?napi_module?callbackModule?= {
    .nm_version?= 1,
    .nm_flags?= 0,
    .nm_filename?=?nullptr,
    .nm_register_func?= CallbackExport,
    .nm_modname?= "callback",
    .nm_priv?= ((void*)0),
    .reserved?= { 0 },
};

//?callback?module?register
extern "C" __attribute__((constructor))?void?CallbackTestRegister()
{
    napi_module_register(&callbackModule);
}

2、获取 env 中的 loop,抛任务回 JS 线程

#include?<thread>

#include?"napi/native_api.h"
#include?"napi/native_node_api.h"

#include?"uv.h"

struct CallbackContext {
????napi_env?env?=?nullptr;
????napi_ref?callbackRef?=?nullptr;
????int?retData?= 0;
};

void?callbackTest(CallbackContext*?context)
{
????uv_loop_s* loop =?nullptr;
    //?此处的env需要在注册JS回调时保存下来。从env中获取对应的JS线程的loop。
    napi_get_uv_event_loop(context->env, &loop);
    
    //?创建uv_work_t用于传递私有数据,注意回调完成后需要释放内存,此处省略生成回传数据的逻辑,传回int类型1。
????uv_work_t*?work?=?new?uv_work_t;
????context->retData?= 1;
????work->data?= (void*)context;
    
    //?调用libuv接口抛JS任务到loop中执行。
    uv_queue_work(
        loop,
????????work,
        //?此回调在另一个普通线程中执行,用于处理异步任务,回调执行完后执行下面的回调。本场景下该回调不需要执行任务。
        [](uv_work_t*?work) {},
        //?此回调会在env对应的JS线程中执行。
        [](uv_work_t*?work,?int?status) {
            CallbackContext*?context?= (CallbackContext*)work->data;
????????????napi_handle_scope?scope?=?nullptr;
            //?打开handle?scope用于管理napi_value的生命周期,否则会内存泄露。
            napi_open_handle_scope(context->env, &scope);
            if (scope?==?nullptr) {
                return;
            }

            //?调用napi。
????????????napi_value?callback?=?nullptr;
            napi_get_reference_value(context->env,?context->callbackRef, &callback);
????????????napi_value?retArg;
            napi_create_int32(context->env,?context->retData, &retArg);
????????????napi_value?ret;
            napi_call_function(context->env,?nullptr,?callback, 1, &retArg, &ret);
            napi_delete_reference(context->env,?context->callbackRef);

            //?关闭handle?scope释放napi_value。
            napi_close_handle_scope(context->env,?scope);

            //?释放work指针。
            if (work?!=?nullptr) {
????????????????delete?work;
            }

????????????delete?context;
        }
    );
}

static?napi_value?JSTest(napi_env?env,?napi_callback_info?info)
{
????size_t?argc?= 1;
????napi_value?argv[1] = { 0 };
????napi_value?thisVar?=?nullptr;
????void*?data?=?nullptr;
    napi_get_cb_info(env,?info, &argc,?argv, &thisVar, &data);

    //?获取第一个入参,即需要后续触发的回调函数
????napi_valuetype?valueType?=?napi_undefined;
    napi_typeof(env,?argv[0], &valueType);
    if (valueType?!=?napi_function) {
        return?nullptr;
    }
    //?存下env与回调函数,用于传递
????auto?asyncContext?=?new?CallbackContext();
????asyncContext->env?=?env;
    napi_create_reference(env,?argv[0], 1, &asyncContext->callbackRef);
    //?模拟抛到非js线程执行逻辑
    std::thread?testThread(callbackTest,?asyncContext);
????testThread.detach();

    return?nullptr;
}

3、? js 示例代码

import?callback?from?'libcallback.so';
export?default?{  testcallback() {??????callback.test((data) => {??????console.error('test?result?=?'?+?data)    })  }}

文章来源:https://blog.csdn.net/HarmonyOSDev/article/details/134808384
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。