ArkWeb实战学习笔记04-JavaScript与Native通信

ArkWeb实战学习笔记04-JavaScript与Native通信

Hybrid开发中,Native与Web页面间的双向通信是核心需求。ArkWeb(方舟Web)提供JavaBridge(通过javaScriptProxy)和Proxy双向通信(通过WebMessagePort)两种方式。本文基于HarmonyOS API 9+,演示Native调用JS、JS回调Native以及双向消息传递的完整实现步骤,附带代码和关键注意事项。

核心概念

ArkWeb框架下,JavaScript与Native通信依赖以下机制:

  • JavaBridge :通过Web组件的javaScriptProxy()方法注册一个JS Bridge对象。Native可以直接调用JS方法(@NativeCall),JS也可以通过该对象来回调Native方法。
  • WebMessagePort :基于createWebMessagePorts()创建一对端口(Native端与JS端),通过postMessage()onMessage()实现双向数据传递,不依赖JS Bridge对象。

官方文档中提及的"Proxy双向通信"即指WebMessagePort通道。通信数据均为字符串,复杂对象需要手动序列化。

环境准备

在DevEco Studio中创建工程并引入ArkWeb组件。确保module.json5中声明权限(如ohos.permission.INTERNET),并配置compileSdkVersion为9或以上。当前(2024年)推荐API 10,因为WebMessagePort在API 10中有更完善的支持。

⚠️ 注意:若使用API 9,createWebMessagePorts()返回的类型与API 10略有差异,建议统一使用API 10。

核心实现

以下代码基于HarmonyOS官方API文档编写,测试环境为DevEco Studio 4.0 + API 10。所有示例均嵌入在EntryAbility或自定义WebComponent中。

1. Native调用JavaScript

Native侧通过webview.runJavaScript()直接执行JS代码。适用于简单调用,无需返回值或异步处理。

Native侧代码(ts):

typescript 复制代码
import { webview } from '@kit.ArkWeb';

// 获取Web控制器
@Entry
@Component
struct WebPage {
  private controller: webview.WebviewController = new webview.WebviewController();

  build() {
    Column() {
      Button('Native调用JS: 修改标题')
        .onClick(() => {
          // 调用JS方法,无返回值
          this.controller.runJavaScript("document.title = 'Hello from Native';");
        })
      Web({ src: $rawfile('index.html'), controller: this.controller })
        .javaScriptAccess(true)
    }
  }
}

JS侧(index.html): 无需特殊处理,document.title直接响应。

⚠️ 注意:runJavaScript不能同步接收返回值。如需获取JS执行结果,需要搭配javaScriptProxy或使用evaluateJavaScript(API 10新增,但本文不展开)。

2. JavaScript回调Native

通过javaScriptProxy()注册一个对象到JS环境,JS可以调用该对象上的同步方法。

Native侧代码:

typescript 复制代码
import { webview } from '@kit.ArkWeb';

class JsBridge {
  // 此方法必须使用@NativeCall装饰(API 10支持)
  @NativeCall
  public onJsCall(message: string): string {
    console.log('Native收到JS消息: ' + message);
    return 'Native已收到: ' + message;
  }
}

@Entry
@Component
struct WebPage {
  private controller: webview.WebviewController = new webview.WebviewController();

  aboutToAppear() {
    // 注册JS Bridge,名称任意,JS访问时用该名称
    this.controller.javaScriptProxy({
      object: new JsBridge(),
      name: 'nativeBridge',
      methodList: ['onJsCall'],
      // 控制JS线程是否同步等待Native返回
      syncCall: true   // 若不需要返回值可设为false
    });
  }

  build() {
    Column() {
      Button('激活JS回调')
        .onClick(() => {
          // 让JS主动调用nativeBridge.onJsCall
          this.controller.runJavaScript("nativeBridge.onJsCall('来自JS的问候'); alert('结果: ' + result);");
        })
      Web({ src: $rawfile('index.html'), controller: this.controller })
        .javaScriptAccess(true)
    }
  }
}

JS侧(index.html):

html 复制代码
<script>
  // 在页面加载完成后,可以通过nativeBridge对象直接调用
  window.onload = function() {
    // 测试:点击按钮后Native注册的Bridge已经可用
    var btn = document.getElementById('btn');
    btn.onclick = function() {
      var ret = nativeBridge.onJsCall('Hello Native!');
      console.log('JS收到Native返回: ' + ret);
    };
  };
</script>

⚠️ 注意:javaScriptProxy必须在Web组件加载页面之前调用(在aboutToAppear或页面初始化时),否则JS侧可能找不到对象。syncCall设置为true时,JS会阻塞等待Native方法返回,容易导致性能问题,建议仅在必须同步获取结果时使用。

3. Proxy双向通信(基于WebMessagePort)

WebMessagePort提供更灵活的双向通道,不依赖对象注册,适合大量消息或跨页面通信。

Native侧代码:

typescript 复制代码
import { webview } from '@kit.ArkWeb';

@Entry
@Component
struct WebPage {
  private controller: webview.WebviewController = new webview.WebviewController();
  private nativePort: webview.WebMessagePort | null = null;

  aboutToAppear() {
    // 创建一对端口,nativePort和jsPort
    let ports = this.controller.createWebMessagePorts();
    this.nativePort = ports[0];
    // 将jsPort发送给Web页面(目标URL可以使用通配符*)
    this.controller.postWebMessage(
      'bridge-init',
      [ports[1]],
      '*'
    );
    // 监听Native端消息
    this.nativePort.on('message', (event) => {
      console.log('Native收到消息: ' + event.data);
      // 回复消息
      this.nativePort?.postMessage('Native已收到: ' + event.data);
    });
  }

  build() {
    Column() {
      Button('发送消息给JS')
        .onClick(() => {
          this.nativePort?.postMessage('Hello from Native!');
        })
      Web({ src: $rawfile('index.html'), controller: this.controller })
        .javaScriptAccess(true)
    }
  }
}

JS侧(index.html):

html 复制代码
<script>
  var jsPort;

  // 监听来自Native的端口传递事件
  window.addEventListener('message', function(event) {
    if (event.data === 'bridge-init') {
      // 取出端口对象
      jsPort = event.ports[0];
      // 设置JS端消息监听
      jsPort.onmessage = function(e) {
        console.log('JS收到消息: ' + e.data);
        // 回复
        jsPort.postMessage('JS已收到: ' + e.data);
      };
    }
  });
</script>

⚠️ 注意:postWebMessage的第二个参数是端口数组,第三个参数targetOrigin建议使用具体域名或*(仅调试)。生产环境中务必限定targetOrigin,避免消息泄露。WebMessagePort通信是异步的,不适合高频实时请求。

注意事项

  • 数据大小限制:postMessage传递的字符串长度在不同系统中可能有限制(官方建议单条消息不超过1MB),超长数据需分块或使用Blob等方案。
  • 线程模型:Native调用JS在UI线程执行,长时间阻塞会导致页面卡顿。javaScriptProxysyncCall为true时尤其小心。
  • 生命周期:Web组件销毁后,WebMessagePortjavaScriptProxy自动失效。若页面重新加载,需要重新注册。
  • 安全性:javaScriptAccess设置为false会阻止所有JS执行,需根据场景灵活控制。另需注意不要将敏感Native对象暴露给JS。

上一篇 介绍了ArkWeb的基本页面加载与生命周期管理。下一篇将聚焦Web组件的安全策略与权限管理。

关于JavaBridge和Proxy通信还有哪些坑?欢迎在评论区交流实际踩过的版本兼容问题。

相关推荐
Junerver2 天前
把 DevEco Code 的 HarmonyOS 开发能力装进口袋——harmonyos-dev-skill
harmonyos
通信小呆呆3 天前
当算法有了“五感”:多模态数据融合如何向人体感官协同学习?
人工智能·学习·算法·机器学习·机器人
程序猿追3 天前
那个右下角的小数字怎么“卡”住我打字——我用 HarmonyOS 自己写了一个字数限制输入框
pytorch·华为·harmonyos
古德new3 天前
鸿蒙PC使用electron迁移:Joplin Electron 桌面适配全记录
华为·electron·harmonyos
世人万千丶3 天前
桌面便签小应用 - HarmonyOS ArkUI 开发实战-TextArea与Flex布局-PC版本
华为·harmonyos·鸿蒙·鸿蒙系统
慧海灵舟3 天前
AGenUI 鸿蒙端实战踩坑录:从 Column 布局消失到异步组件宽度为 0
华为·harmonyos
H__Rick3 天前
自动对焦学习-3
人工智能·学习·计算机视觉
Daisy Lee3 天前
量化学习-第1章-什么是量化金融
学习·金融·datawhale
yuegu7773 天前
HarmonyOS应用<节气通>开发第33篇:状态管理实战
华为·harmonyos
Alsn863 天前
等待学习-学习目录:Docker 容器安全攻防
学习·安全·docker