目录

应用侧与前端页面的相互调用(C/C++)

本指导适用于ArkWeb应用侧与前端网页通信场景,开发者可根据应用架构选择使用ArkWeb Native接口完成业务通信机制(以下简称Native JSBridge)。

针对JSBridge进行性能优化可参考JSBridge优化解决方案

适用的应用架构

应用使用ArkTS、C++语言混合开发,或本身应用架构较贴近于小程序架构,自带C++侧环境,推荐使用ArkWeb在Native侧提供的ArkWeb_ControllerAPI、ArkWeb_ComponentAPI实现JSBridge功能。

上图展示了具有普遍适用性的小程序的通用架构。在这一架构中,逻辑层依赖于应用程序自带的JavaScript运行时,该运行时在一个已有的C++环境中运行。通过Native接口,逻辑层能够直接在C++环境中与视图层(其中ArkWeb充当渲染器)进行通信,无需回退至ArkTS环境使用ArkTS JSBridge接口。

左图是使用ArkTS JSBridge接口构建小程序的方案,如红框所示,应用需要先调用到ArkTS环境,再调用到C++环境。右图是使用Native JSBridge接口构建小程序的方案,不需要ArkTS环境和C++环境的切换,执行效率更高。

Native JSBridge方案解决了ArkTS环境的冗余切换,同时允许回调在非UI线程上运行,避免造成UI阻塞。

使用Native接口实现JSBridge通信(推荐)

原先,Native同步接口不支持返回值,其返回类型固定为void。然而,为满足业务扩展需求,自API version 18起,引入了替代接口,支持bool、string类型的返回值。

另外针对同步接口registerJavaScriptProxyEx()和异步接口registerAsyncJavaScriptProxyEx(),新增了参数permission字段,用于调用权限控制。

[h2]接口替代关系

不推荐的接口 替代接口 说明
ArkWeb_OnJavaScriptProxyCallback ArkWeb_OnJavaScriptProxyCallbackWithResult Proxy方法被执行的回调。
ArkWeb_ProxyMethod ArkWeb_ProxyMethodWithResult 注入的Proxy方法通用结构体。
ArkWeb_ProxyObject ArkWeb_ProxyObjectWithResult 注入的Proxy对象通用结构体。
registerJavaScriptProxy registerJavaScriptProxyEx 注入JavaScript对象到window对象中,并在window对象中调用该对象的同步方法。
registerAsyncJavaScriptProxy registerAsyncJavaScriptProxyEx 注入JavaScript对象到window对象中,并在window对象中调用该对象的异步方法。

[h2]使用Native接口绑定ArkWeb

[h2]使用Native接口获取API结构体

在ArkWeb Native侧,需要先获取API结构体,才能调用结构体里的Native API。ArkWeb Native侧API通过函数OH_ArkWeb_GetNativeAPI获取,根据入参type不同,可分别获取ArkWeb_ControllerAPI、ArkWeb_ComponentAPI结构体。其中,ArkWeb_ControllerAPI对应ArkTS侧WebviewController API,ArkWeb_ComponentAPI对应ArkTS侧ArkWeb组件API。

static ArkWeb_ControllerAPI *controller = nullptr;
static ArkWeb_ComponentAPI *component = nullptr;
// ...
controller = reinterpret_cast<ArkWeb_ControllerAPI *>(OH_ArkWeb_GetNativeAPI(ARKWEB_NATIVE_CONTROLLER));
component = reinterpret_cast<ArkWeb_ComponentAPI *>(OH_ArkWeb_GetNativeAPI(ARKWEB_NATIVE_COMPONENT));

[h2]Native侧注册组件生命周期回调

通过ArkWeb_ComponentAPI注册组件生命周期回调,调用接口前,建议通过ARKWEB_MEMBER_MISSING校验该函数结构体中是否存在对应函数指针,以避免SDK与设备ROM不匹配导致crash问题。

if (!ARKWEB_MEMBER_MISSING(component, onControllerAttached)) {
    component->onControllerAttached(
        webTagValue, ValidCallback, static_cast<void *>(jsbridge_object_ptr->GetWeakPtr()));
} else {
    OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ArkWeb", "component onControllerAttached func not exist");
}

if (!ARKWEB_MEMBER_MISSING(component, onPageBegin)) {
    component->onPageBegin(webTagValue, LoadStartCallback, static_cast<void *>(jsbridge_object_ptr->GetWeakPtr()));
} else {
    OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ArkWeb", "component onPageBegin func not exist");
}

if (!ARKWEB_MEMBER_MISSING(component, onPageEnd)) {
    component->onPageEnd(webTagValue, LoadEndCallback, static_cast<void *>(jsbridge_object_ptr->GetWeakPtr()));
} else {
    OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ArkWeb", "component onPageEnd func not exist");
}

if (!ARKWEB_MEMBER_MISSING(component, onDestroy)) {
    component->onDestroy(webTagValue, DestroyCallback, static_cast<void *>(jsbridge_object_ptr->GetWeakPtr()));
} else {
    OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ArkWeb", "component onDestroy func not exist");
}

[h2]前端页面调用应用侧函数

通过registerJavaScriptProxyEx()将应用侧函数注册至前端页面,注册后在下次加载或者重新加载后生效。

// 注册对象
OH_LOG_Print(
    LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ArkWeb", "Native Development Kit registerJavaScriptProxyEx begin");
ArkWeb_ProxyMethodWithResult method1 = {
    "method1", ProxyMethod1, static_cast<void *>(jsbridge_object_ptr->GetWeakPtr())};
ArkWeb_ProxyMethodWithResult method2 = {
    "method2", ProxyMethod2, static_cast<void *>(jsbridge_object_ptr->GetWeakPtr())};
ArkWeb_ProxyMethodWithResult methodList[2] = {method1, method2};
// 调用Native Development Kit接口注册对象
// 如此注册的情况下,在H5页面就可以使用proxy.method1、proxy.method2调用此文件下的ProxyMethod1和ProxyMethod2方法了
ArkWeb_ProxyObjectWithResult proxyObject = {"ndkProxy", methodList, 2};
// 参数permission为空,表示不进行权限管控
controller->registerJavaScriptProxyEx(webTag, &proxyObject, "");
{
  "javascriptProxyPermission": {
    "urlPermissionList": [       // Object级权限,如果匹配,所有Method都授权
      {
        "scheme": "resource",    // 精确匹配,不能为空
        "host": "rawfile",       // 精确匹配,不能为空
        "port": "",              // 精确匹配,为空不检查
        "path": ""               // 前缀匹配,为空不检查
      },
      {
        "scheme": "https",       // 精确匹配,不能为空
        "host": "xxx.com",       // 精确匹配,不能为空
        "port": "8080",          // 精确匹配,为空不检查
        "path": "a/b/c"          // 前缀匹配,为空不检查
      }
    ],
    "methodList": [
      {
        "methodName": "test",
        "urlPermissionList": [   // Method级权限
          {
            "scheme": "https",   // 精确匹配,不能为空
            "host": "xxx.com",   // 精确匹配,不能为空
            "port": "",          // 精确匹配,为空不检查
            "path": ""           // 前缀匹配,为空不检查
          },
          {
            "scheme": "resource",// 精确匹配,不能为空
            "host": "rawfile",   // 精确匹配,不能为空
            "port": "",          // 精确匹配,为空不检查
            "path": ""           // 前缀匹配,为空不检查
          }
        ]
      },
      {
        "methodName": "test11",
        "urlPermissionList": [   // Method级权限
          {
            "scheme": "q",       // 精确匹配,不能为空
            "host": "r",         // 精确匹配,不能为空
            "port": "",          // 精确匹配,为空不检查
            "path": "t"          // 前缀匹配,为空不检查
          },
          {
            "scheme": "u",       // 精确匹配,不能为空
            "host": "v",         // 精确匹配,不能为空
            "port": "",          // 精确匹配,为空不检查
            "path": ""           // 前缀匹配,为空不检查
          }
        ]
      }
    ]
  }
}

[h2]应用侧调用前端页面函数

使用runJavaScript()调用前端页面函数。

// 构造runJS执行的结构体
char* jsCode = "runJSRetStr()";
ArkWeb_JavaScriptObject object = {(uint8_t *)jsCode, bufferSize, &JSBridgeObject::StaticRunJavaScriptCallback,
                                     static_cast<void *>(jsbridge_object_ptr->GetWeakPtr())};
// 调用前端页面runJSRetStr()函数
controller->runJavaScript(webTagValue, &object);

[h2]完整示例

使用Native接口实现JSBridge通信

[h2]使用Native接口绑定ArkWeb

[h2]使用Native接口获取API结构体

ArkWeb Native侧需要先获取API结构体,才能调用结构体里的Native API。ArkWeb Native侧API通过函数OH_ArkWeb_GetNativeAPI获取,根据入参type不同,可分别获取ArkWeb_ControllerAPI、ArkWeb_ComponentAPI函数指针结构体。其中,ArkWeb_ControllerAPI对应ArkTS侧WebviewController API,ArkWeb_ComponentAPI对应ArkTS侧ArkWeb组件API。

static ArkWeb_ControllerAPI *controller = nullptr;
static ArkWeb_ComponentAPI *component = nullptr;
// ...
controller = reinterpret_cast<ArkWeb_ControllerAPI *>(OH_ArkWeb_GetNativeAPI(ARKWEB_NATIVE_CONTROLLER));
component = reinterpret_cast<ArkWeb_ComponentAPI *>(OH_ArkWeb_GetNativeAPI(ARKWEB_NATIVE_COMPONENT));

[h2]Native侧注册组件生命周期回调

通过ArkWeb_ComponentAPI注册组件生命周期回调,在调用接口前建议通过ARKWEB_MEMBER_MISSING校验该函数结构体是否有对应函数指针,避免SDK与设备ROM不匹配导致crash问题。

if (!ARKWEB_MEMBER_MISSING(component, onControllerAttached)) {
    component->onControllerAttached(
        webTagValue, ValidCallback, static_cast<void *>(jsbridge_object_ptr->GetWeakPtr()));
} else {
    OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ArkWeb", "component onControllerAttached func not exist");
}

if (!ARKWEB_MEMBER_MISSING(component, onPageBegin)) {
    component->onPageBegin(webTagValue, LoadStartCallback, static_cast<void *>(jsbridge_object_ptr->GetWeakPtr()));
} else {
    OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ArkWeb", "component onPageBegin func not exist");
}

if (!ARKWEB_MEMBER_MISSING(component, onPageEnd)) {
    component->onPageEnd(webTagValue, LoadEndCallback, static_cast<void *>(jsbridge_object_ptr->GetWeakPtr()));
} else {
    OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ArkWeb", "component onPageEnd func not exist");
}

if (!ARKWEB_MEMBER_MISSING(component, onDestroy)) {
    component->onDestroy(webTagValue, DestroyCallback, static_cast<void *>(jsbridge_object_ptr->GetWeakPtr()));
} else {
    OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ArkWeb", "component onDestroy func not exist");
}

[h2]前端页面调用应用侧函数

通过registerJavaScriptProxy()将应用侧函数注册至前端页面,注册后在下次加载或者重新加载后生效。

// 注册对象
OH_LOG_Print(
    LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ArkWeb", "Native Development Kit registerJavaScriptProxy begin");
ArkWeb_ProxyMethod method1 = {"method1", ProxyMethod1, static_cast<void *>(jsbridge_object_ptr->GetWeakPtr())};
ArkWeb_ProxyMethod method2 = {"method2", ProxyMethod2, static_cast<void *>(jsbridge_object_ptr->GetWeakPtr())};
ArkWeb_ProxyMethod methodList[2] = {method1, method2};
// 调用Native Development Kit接口注册对象
// 如此注册的情况下,在H5页面就可以使用proxy.method1、proxy.method2调用此文件下的ProxyMethod1和ProxyMethod2方法了
ArkWeb_ProxyObject proxyObject = {"ndkProxy", methodList, 2};
controller->registerJavaScriptProxy(webTag, &proxyObject);

[h2]应用侧调用前端页面函数

通过runJavaScript()调用前端页面函数。

// 构造runJS执行的结构体
const char* jsCode = "runJSRetStr()";
ArkWeb_JavaScriptObject object = {(uint8_t *)jsCode, bufferSize, &JSBridgeObject::StaticRunJavaScriptCallback,
                                     static_cast<void *>(jsbridge_object_ptr->GetWeakPtr())};
// 调用前端页面runJSRetStr()函数
controller->runJavaScript(webTagValue, &object);

[h2]完整示例