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通信还有哪些坑?欢迎在评论区交流实际踩过的版本兼容问题。

相关推荐
Goway_Hui1 小时前
【 鸿蒙原生应用开发--ArkUI--005 】PomodoroApp 番茄钟应用开发教程
华为·harmonyos
Goway_Hui2 小时前
【鸿蒙原生应用开发--ArkUI--004】NotesApp - 笔记应用教程
harmonyos
li星野2 小时前
RAG优化系列:HyDE(假设文档嵌入)——让LLM先写答案再检索
python·学习
知识分享小能手2 小时前
Flask入门学习教程,从入门到精通,Flask智能租房——用户中心知识点详解(9)
python·学习·flask
魔法阵维护师2 小时前
从零开发游戏需要学习的c#模块,第三十一章(技能冷却系统 —— 范围爆炸)
学习·游戏·c#
都市放羊2 小时前
网络小白自学网工——因特网与网络互联技术
网络·笔记·自学
東隅已逝,桑榆非晚2 小时前
新手入门指南:认识 C 语言文件操作(上)
c语言·开发语言·笔记
暴躁小师兄数据学院2 小时前
【AI大数据工程师特训笔记】第08讲:集合运算与超级函数
大数据·笔记·sql·ai·postgresql