OpenHarmony 源码解析之JavaScript API框架(NAPI) 原创 精华

深开鸿
发布于 2021-9-9 11:08
浏览
32收藏

作者:赵军霞

1 简介

JS API: JavaScript Application Programming Interface,JavaScript应用程序编程接口。

NAPI: 一句话概括NAPI,就是L2设备上的 JS API实现方式。

1.1 UI架构相关系列

【OpenHarmony 源码解析之ACE (JavaScript运行环境初始化)】

【OpenHarmony 源码解析之JavaScript API框架(NAPI)】

【OpenHarmony 源码解析之JavaScript(文件管理API)】

1.2 OpenHarmony架构图

OpenHarmony 源码解析之JavaScript API框架(NAPI)-鸿蒙开发者社区

1.3 JS UI架构

JS UI框架包括应用层(Application)、前端框架层(Framework)、引擎层(Engine)和平台适配层(Porting Layer),其架构如下图所示:
OpenHarmony 源码解析之JavaScript API框架(NAPI)-鸿蒙开发者社区
OpenHarmony 源码解析之JavaScript API框架(NAPI)-鸿蒙开发者社区

1.4 NAPI架构

NAPI机制属于JS UI框架中的前端框架层,其架构图如下:
OpenHarmony 源码解析之JavaScript API框架(NAPI)-鸿蒙开发者社区

1.5 JS API实现方式

OpenHarmony上JS API实现方式有两种,分别是:JSI机制、NAPI机制。

JSI机制:L0~L1设备支持。

NAPI机制:目前仅L2设备支持,后续须推广到L3~L5设备。

2 NAPI机制介绍

2.1 实现原则

  • 优先封装异步方法!同步方法可待社区反馈需要时再行添加;
  • 若引擎开启Promise特性支持,则异步方法必须同时支持Callback方式和Promise方式;
  • 使用哪种方式由应用开发者决定,是否传递Callback进行区分。不传递Callback即为Promise方式,方法执行结果为Promise实例对象;
  • L0L1设备上受限于硬件水平,只实现Callback方式的异步方法;
  • L2L5设备上,必须实现同时支持Callback方式和Promise方式的异步方法。

2.2 异步编程模型

Promise、Callback 异步模型都是 OHOS 标准异步模型之一。

2.2.1 Promise 异步模型

Promise对象:

  • ES6原生提供了Promise对象,Promise是异步编程的一种解决方案,可以替代传统的解决方案回调函数和事件
  • promise对象是一个异步操作的结果,提供了一些API使得异步执行可以按照同步的流表示出来,避免了层层嵌套的回调函数,保证了回调是以异步的方式进行调用的;
  • 用户在调用这些接口的时候,接口实现将异步执行任务,同时返回一个 Promise 对象,其代表异步操作的结果;
  • 在返回的结果的个数超过一个时,其以对象属性的形式返回。

Promise特点: 作为对象,Promise有两个特点:

  • 对象的状态不受外界影响;

  • 一旦状态改变了就不会再变,也就是说任何时候Promise都只有一种状态。

2.2.2 Callback 异步模型

  • 用户在调用这些接口的时候,接口实现将异步执行任务;

  • 任务执行结果以参数的形式提供给用户注册的回调函数;

  • 这些参数的第一个是 Error 或 undefined 类型,分别表示执行出错与正常。

3 实现步骤

3.1 模块注册

API集合按业务功能进行模块划分。开发者使用前须import对应的模块。

  • 命名:@ohos.模块名

  • 注意:

    • 模块名须唯一,由ACE团队统一维护,子系统新增模块时须向ACE团队申请。
    • 模块名最好是单个名词。实在不行,也可以由多个名词组成,但必须遵循小驼峰命名规则。
    • 一个模块,一个声明文件(*.d.ts)。声明文件命名遵循@ohos.模块名.d.ts,文件名全小写,单词间无分割。

N-API通过注册函数进行模块的注册,其接受一个全局变量参数,全局变量结构体中定义了模块名及模块初始化函数。在模块的初始化中,我们可以定义模块需要暴露的方法及属性。

  • 示例:

    static napi_value StorageExport(napi_env env, napi_value exports)
    {
    
    const char* storageClassName = "Storage";
    napi_value storageClass = nullptr;
    
    /* 定义模块需要对外暴露的方法 */
    static napi_property_descriptor storageDesc[] = {
        DECLARE_NAPI_FUNCTION("get", JSStorageGet),
        DECLARE_NAPI_FUNCTION("getSync", JSStorageGetSync),
    };
    
    /* 定义C++类对应的JavaScript类,包括JS类名、JS构造函数 */
    napi_define_class(env, storageClassName, strlen(storageClassName), JSStorageConstructor, nullptr,
                      sizeof(storageDesc) / sizeof(storageDesc[0]), storageDesc, &storageClass);
    
    /* 定义模块需要对外暴露的属性 */
    static napi_property_descriptor desc[] = {
        DECLARE_NAPI_PROPERTY("Storage", storageClass),
    };
    
    /* 设置exports对象属性 */
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
    }
    

    模块定义

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

    模块注册

    extern "C" __attribute__((constructor)) void StorageRegister()
    {
    	napi_module_register(&storageModule);
    }
    

3.2 NAPI声明

  • 声明文件模板

    @ohos.模块名.d.ts文件:

    /**
    * 模块描述
    * @since API版本号,IT Release3 对应 4,以此类推
    * @sysCap 系统能力
    * @devices 支持设备
    * @import 导入模块
    * @permission 权限列表
    */
    declare namespace 模块名 {
    // 在此处定义功能方法
    }
    
    export default 模块名;
    
  • 示例:

    声明文件@ohos.storage.d.ts

    /**
     * 存储
     * @since 3
     * @sysCap ACE Engine
     * @devices phone, tablet, tv, wearable, liteWearable, smartVision
     * @import import storage from '@ohos.storage';
     * @permission N/A
     */
    declare namespace storage {
      // 在此处定义功能方法
    }
      
    export default storage;
    

3.3 NAPI实现

JS API 调用流程如下图所示:

OpenHarmony 源码解析之JavaScript API框架(NAPI)-鸿蒙开发者社区

接口定义

 /**入参**	
  napi_env:表示一个上下文的变量;	
  napi_callback_info:传递给回调函数的一个封装的数据类型,可以用于获取有关调用时的上下文信息,也可以用于设置回调函数的返回值;

 **返回值**	
 napi_value:对所有js的基本值的一个密闭封装,就是表示一个基本值;
 */

static napi_value Get(napi_env env, napi_callback_info info);
static napi_value GetSync(napi_env env, napi_callback_info info);

3.4 同步回调

同步方法调用之后,将阻塞住JS线程直至获取到返回值。

  • 命名:动词+Sync动词+名词+Sync

  • 格式:

    • 无参:方法名()
    • 有参:方法名Sync(必填参数[, 可选参数])
  • 返回值:有

  • 声明文件模板

    declare namespace 模块名 
    {
    
    /**
    * 方法描述
    * @note 特殊说明
    * @since (可选,方法支持版本与模块不一致时需标明)
    * @sysCap 系统能力
    * @devices 支持设备 (可选,支持设备类型与模块不一致时需标明)
    * @param 参数 参数说明(可选,没有参数或参数用interface包含时不需要标明)
    * @return 返回值说明(可选,没有返回值或返回值用interface包含时不需要标明)
    */
    
    // 无参
    function 方法名Sync(): 返回值类型;
    
    // 有参
    function 方法名Sync(必填参数: 参数类型, options?: 可选参数类型): 返回值类型;
    
    interface 可选参数类型 {
    参数名: 参数类型;
    }
    }
    
    export default 模块名;
    
  • 示例:

    声明

    declare namespace storage {
      /**
       * getSync方法描述
       * @since 6
       * @sysCap ACE Enginge
       * @param key key值说明
       * @return 返回值说明
       */
      function getSync(key: string,  options?: GetStorageOptions): string;
      
      interface GetStorageOptions {
        /**
         * default参数描述
         * @since 6
         * @sysCap ACE Enginge
         */
        default: string;
      }
    }
      
    export default storage;
    

    实现

    static napi_value GetSync(napi_env env, napi_callback_info info)		
    		{		
    			size_t requireArgc = 1;
    			size_t argc = 2; //参数个数
    			napi_value argv[2] = { 0 }; //参数定义
    			napi_value thisVar = nullptr; //JS对象的this参数
    			void* data = nullptr; //回调数据指针
            /* 根据环境变量获取参数 */
    		napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
    		
    		NAPI_ASSERT(env, argc >= requireArgc, "requires 1 parameter");
    		
    		char key[KEY_BUFFER_SIZE] = { 0 };
    		size_t keyLen = 0;
    		char value[VALUE_BUFFER_SIZE] = { 0 };
    		size_t valueLen = 0;
    		for (size_t i = 0; i < argc; i++) {
    		    napi_valuetype valueType = napi_undefined;
    		    napi_typeof(env, argv[i], &valueType);	
    		    if (i == 0 && valueType == napi_string) {
    
    				/* 根据JS字符串获取对应的UTF8编码格式的C/C++字符串 */
    		        napi_get_value_string_utf8(env, argv[i], key, KEY_BUFFER_SIZE, &keyLen);
    		    } else if (i == 1 && valueType == napi_string) {
    		        napi_get_value_string_utf8(env, argv[i], value, VALUE_BUFFER_SIZE, &valueLen);
    		        break;
    		    } else {
    		        NAPI_ASSERT(env, false, "type mismatch");
    		    }
    		}
    		StorageObjectInfo* objectInfo = nullptr;
    
    		/* 根据JS对象获取与之绑定的原生对象实例 */
    		napi_unwrap(env, thisVar, (void**)&objectInfo);
    		auto itr = g_keyValueStorage.find(key);
    		napi_value result = nullptr; // JS字符串对象
    		if (itr != g_keyValueStorage.end()) {
    			
    			/* 根据UTF8编码格式的 C/C++字符串 创建一个 JS字符串对象 */
    		    napi_create_string_utf8(env, itr->second.c_str(), itr->second.length(), &result);
    		} else if (valueLen > 0) {
    		    napi_create_string_utf8(env, value, valueLen, &result);
    		} else {
    		    objectInfo->Emit(nullptr, "error");
    		    NAPI_ASSERT(env, false, "key does not exist");
    		}
    		return result; //返回JS对象
    	}
    

3.5 异步回调

异步方法调用整个过程不会阻碍调用者的工作。

  • 命名:动词动词+名词

  • 格式:

    • 无参:方法名([回调函数])
    • 有参:方法名(必填参数[, 可选参数][, 回调函数])
  • 返回值

    • 回调函数非空,则返回void
    • 回调函数为空,则返回Promise实例对象
  • 声明文件模板

    declare namespace 模块名 {
    
    /**
     * 方法描述
     * @note 特殊说明
     * @since (可选,方法支持版本与模块不一致时需标明)
     * @sysCap 系统能力
     * @devices 支持设备 (可选,支持设备类型与模块不一致时需标明)
     * @param 参数 参数说明(可选,没有参数或参数用interface包含时不需要标明)
     */
    
    // 无参
    function 方法名(callback: AsyncCallback<结果数据类型>): void;
    function 方法名(): Promise<结果数据类型>;
    
    // 有参
    function 方法名(必填参数: 参数类型, callback: AsyncCallback<结果数据类型>): void;
    function 方法名(必填参数: 参数类型, options: 可选参数类型, callback: AsyncCallback<结果数据类型>): void;
    function 方法名(必填参数: 参数类型, options?: 可选参数类型): Promise<结果数据类型>;
    
    interface 可选参数类型 {
      参数名: 参数类型;
    }
    }
    
    export default 模块名;
    
  • 示例:

    声明

    import { AsyncCallback } from './basic';
    
    declare namespace storage {
    /**
    * get方法描述
    * @note N/A
    * @since 5
    * @sysCap ACE Engine
    * @devices phone, tablet, tv, wearable
    * @param key key值说明
    */
    function get(key: string, callback: AsyncCallback<string>): void;
    function get(key: string, options: GetStorageOptions, callback: AsyncCallback<string>): void;
    function get(key: string, options?: GetStorageOptions): Promise<string>;
    
    interface GetStorageOptions {
    default: string;
    }
    }
    
    export default storage;
    

    实现-异步回调流程如下图所示:

OpenHarmony 源码解析之JavaScript API框架(NAPI)-鸿蒙开发者社区

static napi_value Get(napi_env env, napi_callback_info info)
{
    size_t requireArgc = 1;
    size_t argc = 3; //参数个数
    napi_value argv[3] = { 0 }; //参数定义
    napi_value thisVar = nullptr; //JS对象的this参数
    void* data = nullptr; //回调数据指针

	/* 根据环境变量获取参数 */
    napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);

    NAPI_ASSERT(env, argc >= requireArgc, "requires 1 parameter");

	/* 异步接口上下文,用于接收JS接口传进来的环境变量、参数、回调函数、接口返回值等*/
    auto asyncContext = new StorageAsyncContext();

    asyncContext->env = env;

    for (size_t i = 0; i < argc; i++) {
        napi_valuetype valueType = napi_undefined;
        napi_typeof(env, argv[i], &valueType);

        if ((i == 0) && (valueType == napi_string)) {

			/* 根据JS字符串获取对应的UTF8编码格式的C/C++字符串 */
            napi_get_value_string_utf8(env, argv[i], asyncContext->key, KEY_BUFFER_SIZE, &asyncContext->keyLen);
        } else if (valueType == napi_string) {
            napi_get_value_string_utf8(env, argv[i], asyncContext->value, VALUE_BUFFER_SIZE, &asyncContext->valueLen);
        } else if (valueType == napi_function) {

			/* 根据JS对象参数argv[i]新建引用 */
            napi_create_reference(env, argv[i], 1, &asyncContext->callbackRef);
            break;
        } else {
            NAPI_ASSERT(env, false, "type mismatch");
        }
    }

    napi_value result = nullptr;

    if (asyncContext->callbackRef == nullptr) {

		/* Promise方式异步调用,创建延迟对象、JS Promise对象,使二者进行关联 */
        napi_create_promise(env, &asyncContext->deferred, &result);
    } else {

		/* Callback方式异步调用,不需要返回Promise对象,返回一个JS未定义值 */
        napi_get_undefined(env, &result);
    }

	/* 根据JS对象获取与之绑定的原生对象实例 */
    napi_unwrap(env, thisVar, (void**)&asyncContext->objectInfo);

    napi_value resource = nullptr;
    napi_create_string_utf8(env, "JSStorageGet", NAPI_AUTO_LENGTH, &resource); //获取JS异步资源名称

	/* 创建异步工作 */
    napi_create_async_work(
        env, nullptr, resource,
		
		/* 执行异步逻辑的原生函数 */
        [](napi_env env, void* data) {
            StorageAsyncContext* asyncContext = (StorageAsyncContext*)data;
            auto itr = g_keyValueStorage.find(asyncContext->key);
            if (itr != g_keyValueStorage.end()) {
                if (strncpy_s(asyncContext->value, VALUE_BUFFER_SIZE, itr->second.c_str(), itr->second.length()) ==
                    -1) {
                    asyncContext->status = 1; //失败
                } else {
                    asyncContext->status = 0; //成功
                }
            } else {
                asyncContext->status = 1; //失败
            }
        },

		/* 异步函数执行完成或者取消后,需要执行的后处理函数 */
        [](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]);
                asyncContext->objectInfo->Emit(nullptr, "error");
            }
            if (asyncContext->deferred) {
                if (!asyncContext->status) {

					/* 异步函数执行成功后,执行成功后处理函数 */
                    napi_resolve_deferred(env, asyncContext->deferred, result[1]); 
                } else {

					/* 异步函数执行失败后,执行失败后处理函数 */
                    napi_reject_deferred(env, asyncContext->deferred, result[0]);
                }
            } else {
                napi_value callback = nullptr;
                napi_get_reference_value(env, asyncContext->callbackRef, &callback);
                napi_call_function(env, nullptr, callback, sizeof(result) / sizeof(result[0]), result, nullptr);
                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应用引用NAPI接口时,须先引用接口定义的对应模块,才能进行接口的调用。

import storage from '@ohos.storage'
export default { 

	testGetSync() {
		//同步接口	
		var 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) ); 
	} 
} 

更多原创内容请关注:开鸿 HarmonyOS 学院

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
OpenHarmony JavaScript API框架之NAP.rar 184.65K 305次下载
已于2021-11-30 09:53:20修改
33
收藏 32
回复
举报
29条回复
按时间正序
/
按时间倒序
wx5ba6548f1b21b
wx5ba6548f1b21b

作者写的很详细,可以对NAPI流程有完整的认知,感谢作者分享

回复
2021-9-9 11:30:29
强哥1999
强哥1999

目前鸿蒙JS方面的开发文档和参考源码相对JAVA比较少,这个文章对于我这个JSER来说真的很有帮助。

回复
2021-9-9 11:31:12
flyzhyg
flyzhyg

终于可以一窥openharmony的NAPI机制了,nice

回复
2021-9-9 11:31:38
执着的刀斯林
执着的刀斯林

感谢分享,受益良多

回复
2021-9-9 11:31:41
wx60b7765540463
wx60b7765540463

这个专栏好文不断啊~ 棒棒哒~

回复
2021-9-9 11:31:47
ㄩ

原来 OpenHarmony 是用这种方法提供上层 JS 接口的, 豁然开朗~

感谢博主分享~

1
回复
2021-9-9 11:32:46
萨瓦迪迪
萨瓦迪迪

目录结构清晰,条理清楚,讲解到位。感谢分享

回复
2021-9-9 11:33:36
李祥志
李祥志

寫的太詳細啦,感謝分享。

回复
2021-9-9 13:41:15
mb6096018f96945
mb6096018f96945

结构清晰,层次分明,详细!感谢分享

回复
2021-9-9 15:22:00
iamzgr
iamzgr 回复了 李祥志
寫的太詳細啦,感謝分享。

这是港台小兄弟来捧场了?

回复
2021-9-9 15:47:18
Anzia
Anzia

厉害,不过作为上层应用开发,不是看的很懂C++源码555

回复
2021-9-9 19:54:53
爱吃土豆丝的打工人
爱吃土豆丝的打工人

支持~   学习~

回复
2021-9-10 09:36:57
深开鸿
深开鸿 回复了 wx5ba6548f1b21b
作者写的很详细,可以对NAPI流程有完整的认知,感谢作者分享

感谢认可,可以关注专栏,后期还有更多的优秀内容

回复
2021-9-10 16:26:36
深开鸿
深开鸿 回复了 强哥1999
目前鸿蒙JS方面的开发文档和参考源码相对JAVA比较少,这个文章对于我这个JSER来说真的很有帮助。

感谢认可,可以关注专栏,后期还有更多的优秀内容

回复
2021-9-10 16:26:43
深开鸿
深开鸿 回复了 flyzhyg
终于可以一窥openharmony的NAPI机制了,nice

感谢认可,可以关注专栏,后期还有更多的优秀内容

回复
2021-9-10 16:26:52
深开鸿
深开鸿 回复了 wx60b7765540463
这个专栏好文不断啊~ 棒棒哒~

感谢认可,可以关注专栏,后期还有更多的优秀内容

回复
2021-9-10 16:26:58
深开鸿
深开鸿 回复了
原来 OpenHarmony 是用这种方法提供上层 JS 接口的, 豁然开朗~ 感谢博主分享~

感谢认可,可以关注专栏,后期还有更多的优秀内容

回复
2021-9-10 16:27:04
深开鸿
深开鸿 回复了 mb6096018f96945
结构清晰,层次分明,详细!感谢分享

感谢认可,可以关注专栏,后期还有更多的优秀内容

回复
2021-9-10 16:27:15
深开鸿
深开鸿 回复了 Anzia
厉害,不过作为上层应用开发,不是看的很懂C++源码555

哈哈哈,观摩观摩,期待你的新作。

回复
2021-9-10 16:27:50
深开鸿
深开鸿 回复了 爱吃土豆丝的打工人
支持~ 学习~

还有更多OpenHarmony源码解析,小伙伴们可以关注专栏哦

1
回复
2021-9-15 16:18:28
回复
    相关推荐