生命周期管理
【规则】 合理使用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、EnvScope
LockWrapper(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 vm
JSVM_CreateVMOptions options;
memset(&options, 0, sizeof(options));
OH_JSVM_CreateVM(&options, &vm);
// create env
OH_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 value
if (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 value
if (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 传入参数或 undefined
OH_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::string std::string stackstr = stack? GetValueString(stack) : ""; std::string messagestr = message? GetValueString(message) : ""; // 抛出异常,需实现JSError throw 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";
最后呢
很多开发朋友不知道需要学习那些鸿蒙技术?鸿蒙开发岗位需要掌握那些核心技术点?为此鸿蒙的开发学习必须要系统性的进行。
而网上有关鸿蒙的开发资料非常的少,假如你想学好鸿蒙的应用开发与系统底层开发。你可以参考这份资料,少走很多弯路,节省没必要的麻烦。
如果你是一名Android、Java、前端等等开发人员,想要转入鸿蒙方向发展。可以点击→【纯血版鸿蒙全套最新学习资料】希望这一份鸿蒙学习资料能够给大家带来帮助~
鸿蒙(HarmonyOS NEXT)最新学习路线
该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案
路线图适合人群:
IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术
获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料
2.视频学习资料+学习PDF文档
这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、(南向驱动、嵌入式等)鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。
HarmonyOS Next 最新全套视频教程
大厂面试必问面试题
鸿蒙南向开发技术
鸿蒙APP开发必备
请点击→纯血版全套鸿蒙HarmonyOS学习资料
总结
总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,才能在这个变革的时代中立于不败之地。