鸿蒙HarmonyOS开发规范:JSVM-API详解总结

197 篇文章 0 订阅
193 篇文章 0 订阅

 生命周期管理

【规则】 合理使用OH_JSVM_OpenHandleScope和OH_JSVM_CloseHandleScope管理JSVM_Value的生命周期,做到生命周期最小化,避免发生内存泄漏问题。

每个JSVM_Value属于特定的HandleScope,HandleScope通过OH_JSVM_OpenHandleScope和OH_JSVM_CloseHandleScope来建立和关闭,HandleScope关闭后,所属的JSVM_Value就会自动释放。

注意事项

  1. JSVM_Value必须在HandleScope打开后才可创建(Node-API无该限制),否则会造成应用崩溃;
  2. 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的对象,程序可能会发生崩溃。

多线程共享引擎实例

【规则】多线程同时使用同一个引擎实例的场景下,需要加锁使用。保证一个引擎实例在同一时刻只能在一个线程执行。多线程同一时刻同时使用引擎实例可能造成应用崩溃。

注意事项:

  1. OH_JSVM_IsLocked的结果为当前线程是否持有引擎实例的锁,无需设置循环等待其他线程释放锁;
  2. OH_JSVM_AcquireLock在同一线程中嵌套使用不会造成死锁;
  3. 使用OH_JSVM_ReleaseLock时需判断是否在最外层,避免同一线程中嵌套使用OH_JSVM_AcquireLock的场景下内层释放了整个线程的锁;
  4. OH_JSVM_AcquireLock后需调用OH_JSVM_OpenHandleScope让引擎实例进入线程;OH_JSVM_ReleaseLock后需调用OH_JSVM_ReleaseLock让引擎实例退出线程;
  5. 不同线程禁止嵌套使用引擎实例,如需临时切换线程使用引擎实例,请确保JSVM_Value已保存为JSVM_Ref,释放锁后对JSVM_Value将不可访问;
  6. 需注意资源获取的顺序为:锁 -> 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接口调用发生异常需要及时处理,不能遗漏异常到后续逻辑,否则程序可能发生不可预期行为。

根据主从类型,异常处理可以分为两类:

  1. 回调函数(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();
    }
  2. 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学习资料

总结

总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,才能在这个变革的时代中立于不败之地。 

                   

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值