JSVM-API使用规范
生命周期管理
【规则】 合理使用OH_JSVM_OpenHandleScope和OH_JSVM_CloseHandleScope管理JSVM_Value的生命周期,做到生命周期最小化,避免发生内存泄漏问题。
每个JSVM_Value属于特定的HandleScope,HandleScope通过OH_JSVM_OpenHandleScope和OH_JSVM_CloseHandleScope来建立和关闭,HandleScope关闭后,所属的JSVM_Value就会自动释放。
注意事项:
- JSVM_Value必须在HandleScope打开后才可创建(Node-API无该限制),否则会造成应用崩溃;
- JSVM_Value不能在其对应的HandleScope关闭后使用,如需持久化持有,需调用OH_JSVM_CreateReference转化为JSVM_Ref;
C++使用封装
class HandleScopeWrapper {public:HandleScopeWrapper(JSVM_Env env) : env(env) {OH_JSVM_OpenHandleScope(env, &handleScope);}~HandleScopeWrapper() {OH_JSVM_CloseHandleScope(env, handleScope);}HandleScopeWrapper(const HandleScopeWrapper&) = delete;HandleScopeWrapper& operator=(const HandleScopeWrapper&) = delete;HandleScopeWrapper(HandleScopeWrapper&&) = delete;void* operator new(size_t) = delete;void* operator new[](size_t) = delete;protected:JSVM_Env env;JSVM_HandleScope handleScope;
};
示例:
// 在for循环中频繁调用JSVM接口创建js对象时,要加handle_scope及时释放不再使用的资源。
// 下面例子中,每次循环结束局部变量res的生命周期已结束,因此加scope及时释放其持有的js对象,防止内存泄漏
// 每次for循环结束后,触发HandleScopeWrapper的析构函数,释放scope持有的js对象
for (int i = 0; i < 100000; i++)
{ HandleScopeWrapper scope(env);JSVM_Value res;OH_JSVM_CreateObject(env, &res);if (i == 1000) {// break退出循环后会自动调用HandleScopeWrapper析构函数释放资源break;}
}
多引擎实例上下文敏感
【规则】 多引擎实例(VM)场景下,禁止通过JSVM-API跨引擎实例访问JS对象。
引擎实例是一个独立运行环境,JS对象创建访问等操作必须在同一个引擎实例中进行。若在不同引擎实例中操作同一个对象,可能会引发程序崩溃。引擎实例在接口中体现为JSVM_Env。
错误示例:
// 线程1执行,在env1创建string对象,值为"bar"、
OH_JSVM_CreateStringUtf8(env1, "value1", JSVM_AUTO_LENGTH , &string);
// 线程2执行,在env2创建object对象,并将上述的string对象设置到object对象中
JSVM_Status status = OH_JSVM_CreateObject(env2, &object);
if (status != JSVM_OK)
{ return;
} status = OH_JSVM_SetNamedProperty(env2, object, "string1", string);
if (status != JSVM_OK)
{ return;
}
所有的JS对象都隶属于具体的某一JSVM_Env,不可将env1的对象,设置到env2中的对象中。在env2中一旦访问到env1的对象,程序可能会发生崩溃。
多线程共享引擎实例
【规则】多线程同时使用同一个引擎实例的场景下,需要加锁使用。保证一个引擎实例在同一时刻只能在一个线程执行。多线程同一时刻同时使用引擎实例可能造成应用崩溃。
注意事项:
- OH_JSVM_IsLocked的结果为当前线程是否持有引擎实例的锁,无需设置循环等待其他线程释放锁;
- OH_JSVM_AcquireLock在同一线程中嵌套使用不会造成死锁;
- 使用OH_JSVM_ReleaseLock时需判断是否在最外层,避免同一线程中嵌套使用OH_JSVM_AcquireLock的场景下内层释放了整个线程的锁;
- OH_JSVM_AcquireLock后需调用OH_JSVM_OpenHandleScope让引擎实例进入线程;OH_JSVM_ReleaseLock后需调用OH_JSVM_ReleaseLock让引擎实例退出线程;
- 不同线程禁止嵌套使用引擎实例,如需临时切换线程使用引擎实例,请确保JSVM_Value已保存为JSVM_Ref,释放锁后对JSVM_Value将不可访问;
- 需注意资源获取的顺序为:锁 -> VMScope -> EnvScope -> HandleScope,释放资源的顺序正好相反,错误的顺序可能导致程序崩溃;
C++使用封装:
class LockWrapper {public:// 构造函数,获取锁、VMScope、EnvScopeLockWrapper(JSVM_Env env) : env(env) {OH_JSVM_IsLocked(env, &isLocked);if (!isLocked) {OH_JSVM_AcquireLock(env);OH_JSVM_GetVM(env, &vm);OH_JSVM_OpenVMScope(vm, &vmScope);OH_JSVM_OpenEnvScope(env, &envScope);}}// 析构函数,释放EnvScope、VMScope、锁~LockWrapper() {if (!isLocked) {OH_JSVM_CloseEnvScope(env, envScope);OH_JSVM_CloseVMScope(vm, vmScope);OH_JSVM_ReleaseLock(env);}}LockWrapper(const LockWrapper&) = delete;LockWrapper& operator=(const LockWrapper&) = delete;LockWrapper(LockWrapper&&) = delete;void* operator new(size_t) = delete;void* operator new[](size_t) = delete;private:JSVM_Env env;JSVM_EnvScope envScope;JSVM_VMScope vmScope;JSVM_VM vm;bool isLocked;
};
正确示例:
// 该用例演示了多线程中使用vm
// t1线程先获取锁,并继续JSVM-API的调用
// t2线程会在获取锁处阻塞,直到t1线程执行结束释放锁后,t2线程继续执行,调用JSVM-API接口
static napi_value Add([[maybe_unused]] napi_env _env, [[maybe_unused]] napi_callback_info _info) {static JSVM_VM vm;static JSVM_Env env;if (aa == 0) {OH_JSVM_Init(nullptr);aa++;// create vmJSVM_CreateVMOptions options;memset(&options, 0, sizeof(options));OH_JSVM_CreateVM(&options, &vm);// create envOH_JSVM_CreateEnv(vm, 0, nullptr, &env);}std::thread t1([]() {LockWrapper lock(env);JSVM_HandleScope handleScope;OH_JSVM_OpenHandleScope(env, &handleScope);JSVM_Value value;JSVM_Status rst = OH_JSVM_CreateInt32(env, 32, &value); // 32: numerical valueif (rst == JSVM_OK) {OH_LOG_INFO(LOG_APP, "JSVM:t1 OH_JSVM_CreateInt32 suc");} else {OH_LOG_ERROR(LOG_APP, "JSVM:t1 OH_JSVM_CreateInt32 fail");}int32_t num1;OH_JSVM_GetValueInt32(env, value, &num1);OH_LOG_INFO(LOG_APP, "JSVM:t1 num1 = %{public}d", num1);OH_JSVM_CloseHandleScope(env, handleScope);});std::thread t2([]() {LockWrapper lock(env);JSVM_HandleScope handleScope;OH_JSVM_OpenHandleScope(env, &handleScope);JSVM_Value value;JSVM_Status rst = OH_JSVM_CreateInt32(env, 32, &value); // 32: numerical valueif (rst == JSVM_OK) {OH_LOG_INFO(LOG_APP, "JSVM:t2 OH_JSVM_CreateInt32 suc");} else {OH_LOG_ERROR(LOG_APP, "JSVM:t2 OH_JSVM_CreateInt32 fail");}int32_t num1;OH_JSVM_GetValueInt32(env, value, &num1);OH_LOG_INFO(LOG_APP, "JSVM:t2 num1 = %{public}d", num1);OH_JSVM_CloseHandleScope(env, handleScope);});t1.detach();t2.detach();return nullptr;
}
获取JS传入参数及其数量
【规则】 当传入OH_JSVM_GetCbInfo的argv不为nullptr时,argv的长度必须大于等于传入argc声明的大小。
当argv不为nullptr时,OH_JSVM_GetCbInfo会根据argc声明的数量将JS实际传入的参数写入argv。如果argc小于等于实际JS传入参数的数量,该接口仅会将声明的argc数量的参数写入argv;而当argc大于实际参数数量时,该接口会在argv的尾部填充undefined。
错误示例
static JSVM_Value IncorrectDemo1(JSVM_Env env, JSVM_CallbackInfo info) {// argc 未正确的初始化,其值为不确定的随机值,导致 argv 的长度可能小于 argc 声明的数量,数据越界。size_t argc;JSVM_Value argv[10] = {nullptr};OH_JSVM_GetCbInfo(env, info, &argc, argv, nullptr, nullptr);return nullptr;
}static JSVM_Value IncorrectDemo2(JSVM_Env env, JSVM_CallbackInfo info) {// argc 声明的数量大于 argv 实际初始化的长度,导致 OH_JSVM_GetCbInfo 接口在写入 argv 时数据越界。size_t argc = 5;JSVM_Value argv[3] = {nullptr};OH_JSVM_GetCbInfo(env, info, &argc, argv, nullptr, nullptr);return nullptr;
}
正确示例
static JSVM_Value GetArgvDemo1(napi_env env, JSVM_CallbackInfo info) {size_t argc = 0;// argv 传入 nullptr 来获取传入参数真实数量OH_JSVM_GetCbInfo(env, info, &argc, nullptr, nullptr, nullptr);// JS 传入参数为0,不执行后续逻辑if (argc == 0) {return nullptr;}// 创建数组用以获取JS传入的参数JSVM_Value* argv = new JSVM_Value[argc];OH_JSVM_GetCbInfo(env, info, &argc, argv, nullptr, nullptr);// 业务代码// ... ...// argv 为 new 创建的对象,在使用完成后手动释放delete argv;return nullptr;
}static JSVM_Value GetArgvDemo2(napi_env env, JSVM_CallbackInfo info) {size_t argc = 2;JSVM_Value* argv[2] = {nullptr};// OH_JSVM_GetCbInfo 会向 argv 中写入 argc 个 JS 传入参数或 undefinedOH_JSVM_GetCbInfo(env, info, &argc, nullptr, nullptr, nullptr);// 业务代码// ... ...return nullptr;
}
异常处理
【建议】 JSVM-API接口调用发生异常需要及时处理,不能遗漏异常到后续逻辑,否则程序可能发生不可预期行为。
根据主从类型,异常处理可以分为两类:
- 回调函数(JS主,Native从)中Native发生异常,需往JSVM中抛出异常
// Native主,JSVM从
void ThrowError() {
bool isPending = false;
if (JSVM_OK == OH_JSVM_IsExceptionPending((env), &isPending) && isPending) {JSVM_Value error;// 获取并清空JSVM异常if (JSVM_OK == OH_JSVM_GetAndClearLastException((env), &error)) {// 获取异常堆栈JSVM_Value stack = nullptr;CALL_JSVM(env, OH_JSVM_GetNamedProperty((env), error, "stack", &stack));JSVM_Value message = nullptr;CALL_JSVM(env, OH_JSVM_GetNamedProperty((env), error, "message", &message));// 需实现将JS字符串转化为C++的std::stringstd::string stackstr = stack? GetValueString(stack) : "";std::string messagestr = message? GetValueString(message) : "";// 抛出异常,需实现JSErrorthrow JSError(*this, messagestr, stackstr);}
}
// 抛出异常,需实现JSError
throw JSError(*this, "JSVM Runtime: unkown execption");
}status = OH_JSVM_SetNamedProperty(env, object, "foo", string);
// JSVM-API调用失败,清空JS引擎实例pending的异常,抛出C++异常
if (status != JSVM_OK) { ThrowError();
}
- C++调用JSVM-API(Native主,JS从)失败,需清理JSVM中等待处理的异常,避免影响后续JSVM-API的执行,并设置C++异常处理分支(或抛出C++异常)
// JSVM主, Native从
void DoSomething() {throw("Do something failed");
}// Demo1: 捕获到C++异常,抛出异常到JSVM中
JSVM_Value NativeFunctionExceptionDemo1(JSVM_Env env, JSVM_CallbackInfo info) {try {DoSomething();} catch (const char *ex) {OH_JSVM_ThrowError(env, nullptr, ex);return nullptr;}return nullptr;
}// Demo2: JSVM-API调用失败,抛出异常到JSVM中
JSVM_Value NativeFunctionExceptionDemo2(JSVM_Env env, JSVM_CallbackInfo info) {JSVM_Value JSBool = nullptr;bool value = false;auto status = OH_JSVM_GetValueBool(env, JSBool, &value);if (status != JSVM_OK) {OH_JSVM_ThrowError(env, nullptr, "Get bool value failed");return nullptr;}return nullptr;
}// Demo3:JSVM-API调用失败且在调用过程中已向JSVM中添加等待处理的异常,则无需再向JSVM中抛出异常
JSVM_Value NativeFunctionExceptionDemo3(JSVM_Env env, JSVM_CallbackInfo info) {std::string sourcecodestr = R"JS(throw Error('Error throw from js');)JS";JSVM_Value sourcecodevalue = nullptr;OH_JSVM_CreateStringUtf8(env, sourcecodestr.c_str(), sourcecodestr.size(), &sourcecodevalue);JSVM_Script script;auto status = OH_JSVM_CompileScript(env, sourcecodevalue, nullptr, 0, true, nullptr, &script);JSVM_Value result;// 执行JS脚本,执行过程中抛出JS异常status = OH_JSVM_RunScript(env, script, &result);if (status != JSVM_OK) {bool isPending = false;// 如果已有异常,则无需再向JSVM中抛出异常;// 如需处理并抛出新异常,需先处理JSVM中等待的异常if (JSVM_OK == OH_JSVM_IsExceptionPending((env), &isPending) && isPending) {return nullptr;}OH_JSVM_ThrowError(env, nullptr, "Runscript failed");return nullptr;}return nullptr;
}// 绑定NativeFunction到JSVM中,省略
std::string sourcecodestr = R"JS(// consolelog需用户实现try {// 调用Native函数NativeFunction()} catch (e) {// 处理Native中产生的异常consolelog(e.message)}
)JS";
JSVM_Value sourcecodevalue = nullptr;
OH_JSVM_CreateStringUtf8(env, sourcecodestr.c_str(), sourcecodestr.size(), &sourcecodevalue);
JSVM_Script script;
auto status = OH_JSVM_CompileScript(env, sourcecodevalue, nullptr, 0, true, nullptr, &script);
OH_LOG_INFO(LOG_APP, "JSVM API TEST: %{public}d", (uint32_t)status);
JSVM_Value result;
// 执行JS脚本,JS调用Native方法
status = OH_JSVM_RunScript(env, script, &result);
注意事项:回调函数中调用JSVM-API失败,如要向JSVM中抛异常,需保证JSVM中无等待处理的异常,也可以不抛出异常,JS的try-catch块可以捕获回调函数调用API失败产生的JS异常,见NativeFunctionExceptionDemo3。
上下文绑定对象
【规则】 :调用JSVM-API生成的JS函数、对象需绑定到上下文中才能从JS侧访问,OH_JSVM_CreateFunction接口中的const char *参数为创建函数的属性name,不代表上下文中指向该函数的函数名。调用JSVM-API生成的类、对象同理。
示例
JSVM_Value JSFunc = nullptr;
const char *name = "NativeFunction";
JSVM_CallbackStruct cb = {NativeFunction, nullptr};
// 创建JS函数,该函数的属性 "name" 为 "NativeFunction"
OH_JSVM_CreateFunction(env, name, strlen(name), &cb, &JSFunc);
// 绑定函数到上下文
// 获取上下文的global对象
JSVM_Value global = nullptr;
OH_JSVM_GetGlobal(env, &global);
// 创建JS字符串"FunctionNameInJSContext"
JSVM_Value key = nullptr;
OH_JSVM_CreateStringLatin1(env, "FunctionNameInJSContext", JSVM_AUTO_LENGTH, &key);
// 设置global的属性"FunctionNameInJSContext"为JSFunc,将函数绑定到上下文中
OH_JSVM_SetProperty(env, global, key, JSFunc);
// 在JS中调用函数
std::string sourcecodestr = R"JS(// consolelog需用户实现FunctionNameInJSContext() // 调用成功consolelog(FunctionNameInJSContext.name) // 打印 "NativeFunction"try {NativeFunction() // 无法找到该函数,抛出异常} catch (e) {consolelog(e.message)}
)JS";
最后呢
很多开发朋友不知道需要学习那些鸿蒙技术?鸿蒙开发岗位需要掌握那些核心技术点?为此鸿蒙的开发学习必须要系统性的进行。
而网上有关鸿蒙的开发资料非常的少,假如你想学好鸿蒙的应用开发与系统底层开发。你可以参考这份资料,少走很多弯路,节省没必要的麻烦。由两位前阿里高级研发工程师联合打造的《鸿蒙NEXT星河版OpenHarmony开发文档》里面内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(Harmony NEXT)技术知识点
如果你是一名Android、Java、前端等等开发人员,想要转入鸿蒙方向发展。可以直接领取这份资料辅助你的学习。下面是鸿蒙开发的学习路线图。
针对鸿蒙成长路线打造的鸿蒙学习文档。话不多说,我们直接看详细鸿蒙(OpenHarmony )手册(共计1236页)与鸿蒙(OpenHarmony )开发入门视频,帮助大家在技术的道路上更进一步。
- 《鸿蒙 (OpenHarmony)开发学习视频》
- 《鸿蒙生态应用开发V2.0白皮书》
- 《鸿蒙 (OpenHarmony)开发基础到实战手册》
- OpenHarmony北向、南向开发环境搭建
- 《鸿蒙开发基础》
- 《鸿蒙开发进阶》
- 《鸿蒙开发实战》
总结
鸿蒙—作为国家主力推送的国产操作系统。部分的高校已经取消了安卓课程,从而开设鸿蒙课程;企业纷纷跟进启动了鸿蒙研发。
并且鸿蒙是完全具备无与伦比的机遇和潜力的;预计到年底将有 5,000 款的应用完成原生鸿蒙开发,未来将会支持 50 万款的应用。那么这么多的应用需要开发,也就意味着需要有更多的鸿蒙人才。鸿蒙开发工程师也将会迎来爆发式的增长,学习鸿蒙势在必行! 自↓↓↓拿