DEV Community

Cover image for Mutual invocation between application side and front-end pages (C/C++)
liu yang
liu yang

Posted on

Mutual invocation between application side and front-end pages (C/C++)

Native JSBridge Implementation in HarmonyOS Next Applications

Applicable Architecture

This solution is suitable for applications developed using a mix of ArkTS and C++. It is particularly recommended for applications with an architecture similar to a mini-program, which already includes a C++ environment. By utilizing the ArkWeb_ControllerAPI and ArkWeb_ComponentAPI provided on the Native side, developers can implement JSBridge functionality efficiently.

Image description

Mini-Program Architecture

Below is a general architecture of a mini-program. The logic layer relies on the JavaScript runtime provided by the application, which operates within an existing C++ environment. Through Native interfaces, the logic layer communicates directly with the view layer (where ArkWeb acts as the renderer) within the C++ environment, eliminating the need to fall back to the ArkTS environment and use the ArkTS JSBridge interface.

Image description

Native JSBridge Benefits

The Native JSBridge approach eliminates redundant switching between the ArkTS environment, allowing callbacks to run on non-UI threads and avoiding UI thread blockages.


Implementing Native JSBridge Communication

Binding ArkWeb with Native Interfaces

On the ArkTS side, declare the ArkWeb component and define a custom identifier (webTag) to establish a mapping relationship between the WebviewController and the webTag.

ArkTS Side

// Define a custom webTag and pass it as a parameter when creating the WebviewController to establish a mapping relationship
webTag: string = 'ArkWeb1';
controller: web_webview.WebviewController = new web_webview.WebviewController(this.webTag);

// In aboutToAppear, pass the webTag to the C++ side using the Node-API interface as the unique identifier for the ArkWeb component on the C++ side
aboutToAppear() {
  console.info("aboutToAppear");
  // Initialize the web ndk
  testNapi.nativeWebInit(this.webTag);
}
Enter fullscreen mode Exit fullscreen mode

C++ Side

// Parse and store the webTag
static napi_value NativeWebInit(napi_env env, napi_callback_info info) {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ArkWeb", "ndk NativeWebInit start");
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    // Retrieve the first parameter webTag
    size_t webTagSize = 0;
    napi_get_value_string_utf8(env, args[0], nullptr, 0, &webTagSize);
    char *webTagValue = new (std::nothrow) char[webTagSize + 1];
    size_t webTagLength = 0;
    napi_get_value_string_utf8(env, args[0], webTagValue, webTagSize + 1, &webTagLength);
    OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ArkWeb", "ndk NativeWebInit webTag:%{public}s", webTagValue);

    // Store the webTag in the instance object
    jsbridge_object_ptr = std::make_shared<JSBridgeObject>(webTagValue);
    // ...
}
Enter fullscreen mode Exit fullscreen mode

Obtaining API Structures on the Native Side

Before invoking Native APIs on the ArkWeb Native side, you must first obtain the API structure. Use the OH_ArkWeb_GetNativeAPI function with different type parameters to retrieve the function pointer structures for ArkWeb_ControllerAPI and ArkWeb_ComponentAPI.

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));
Enter fullscreen mode Exit fullscreen mode

Registering Component Lifecycle Callbacks on the Native Side

Use ArkWeb_ComponentAPI to register component lifecycle callbacks. Before calling these APIs, it is recommended to check if the function pointers exist using ARKWEB_MEMBER_MISSING to avoid crashes caused by SDK and device ROM mismatches.

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");
}
Enter fullscreen mode Exit fullscreen mode

Calling Application-Side Functions from Frontend Pages

Register application-side functions to the frontend page using registerJavaScriptProxy. It is recommended to register these functions in the onControllerAttached callback to ensure they take effect immediately.

OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ArkWeb", "ndk 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};
// Call the NDK interface to register the object
// After registration, in the H5 page, you can use proxy.method1 and proxy.method2 to call the ProxyMethod1 and ProxyMethod2 methods defined here
ArkWeb_ProxyObject proxyObject = {"ndkProxy", methodList, 2};
controller->registerJavaScriptProxy(webTag, &proxyObject);
Enter fullscreen mode Exit fullscreen mode

Calling Frontend Page Functions from the Application Side

Invoke frontend page functions using runJavaScript.

// Construct the runJS execution structure
char* jsCode = "runJSRetStr()";
ArkWeb_JavaScriptObject object = {(uint8_t *)jsCode, bufferSize, &JSBridgeObject::StaticRunJavaScriptCallback,
                                     static_cast<void *>(jsbridge_object_ptr->GetWeakPtr())};
// Call the frontend page function runJSRetStr()
controller->runJavaScript(webTagValue, &object);
Enter fullscreen mode Exit fullscreen mode

Top comments (0)