前言

做移动开发,纯原生的项目越来越少了。老板要快、要省钱、要热更新,H5 混合开发基本是标配。但混合开发最头疼的事就是——原生和 H5 怎么通信?H5 要调原生的支付,原生要往 H5 里塞数据,登录态怎么同步?

HarmonyOS7 的 WebView 组件提供了好几种通信方式,我整理了 4 种最常用的姿势,帮你一次搞明白。

WebView 基础搭建

先看怎么创建一个最基础的 WebView:

import { webview } from '@kit.ArkWeb';

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

  aboutToAppear() {
    webview.WebviewController.setWebDebuggingAccess(true); // 开启调试
  }

  build() {
    Column() {
      Web({
        src: $rawfile('index.html'),  // 加载本地 H5
        controller: this.controller
      })
      .width('100%')
      .height('100%')
      .javaScriptAccess(true)         // 允许执行 JS
      .domStorageAccess(true)         // 允许 DOM 存储
    }
  }
}

A hand-drawn doodle illustration on pure white pap

关键代码讲解:

  • setWebDebuggingAccess(true) —— 开发阶段必开,不然 Chrome DevTools 调不了 H5 页面,上线前关掉
  • javaScriptAccess(true) —— 不开的话 JS 啥都干不了,通信全废
  • domStorageAccess(true) —— H5 用 localStorage 的话必须开
  • $rawfile('index.html') —— 加载本地文件用 $rawfile,加载网络 URL 直接写字符串

注意:加载网络页面需要在 module.json5 里加 ohos.permission.INTERNET 权限。

4 种通信方式对比

A hand-drawn doodle illustration on pure white pap

原生和 H5 通信,核心就两个方向:H5 调原生原生调 H5

方式 方向 核心 API 适用场景
javaScriptProxy H5 → 原生 Web.javaScriptProxy() H5 调支付、弹窗、设备能力
registerJavaScriptProxy H5 → 原生 controller.registerJavaScriptProxy() 动态注册,更灵活
runJavaScript 原生 → H5 controller.runJavaScript() 原生主动调 H5 函数
消息通道 双向 onMessage / postMessage 长连接、频繁通信

实际开发中,javaScriptProxy + runJavaScript 这俩组合就能覆盖 90% 的场景。下面重点讲。

JS 调原生:javaScriptProxy

这是最常用的方式——把原生对象注入到 H5 的 window 上,H5 像调本地方法一样调原生。

// 1. 定义原生桥接类
class NativeBridge {
  private controller: webview.WebviewController | null = null;

  setController(ctrl: webview.WebviewController) {
    this.controller = ctrl;
  }

  showToast(msg: string) {
    promptAction.showToast({ message: msg });
  }

  getUserInfo(): string {
    return JSON.stringify({ name: '张三', id: '10086' });
  }

  requestPayment(amount: string): void {
    // 调起原生支付
    console.info(`发起支付:${amount}`);
    // 支付完成后通过 runJavaScript 通知 H5 结果
    this.controller?.runJavaScript(`onPaymentResult(true)`);
  }
}

// 2. 注入到 WebView
@Entry
@Component
struct WebViewDemo {
  controller: webview.WebviewController = new webview.WebviewController();
  @State bridge: NativeBridge = new NativeBridge();

  aboutToAppear() {
    this.bridge.setController(this.controller);
  }

  build() {
    Column() {
      Web({ src: $rawfile('index.html'), controller: this.controller })
        .javaScriptProxy({
          object: this.bridge,          // 注入的原生对象
          name: "nativeBridge",         // H5 通过 window.nativeBridge 调用
          methodList: ["showToast", "getUserInfo", "requestPayment"],  // 暴露的方法
          controller: this.controller
        })
    }
  }
}

A hand-drawn doodle illustration on pure white pap

H5 侧这样调用:

// H5 页面中的代码
function callNative() {
  // 调用原生 Toast
  window.nativeBridge.showToast('来自H5的问候');

  // 获取用户信息
  const user = JSON.parse(window.nativeBridge.getUserInfo());
  console.log(user.name); // 张三

  // 发起支付
  window.nativeBridge.requestPayment('9.9');
}

关键代码讲解:

  • object: this.bridge —— 要注入的原生对象实例
  • name: "nativeBridge" —— H5 通过 window.nativeBridge 访问,名字自定义
  • methodList —— 只有列在里面的方法才能被 H5 调用,安全控制靠这个
  • requestPayment 里调 runJavaScript('onPaymentResult(true)') —— 原生处理完再通知 H5,形成请求-响应闭环

这个闭环模式很重要:H5 调原生 → 原生处理 → 原生通过 runJavaScript 回调 H5。混合开发大多数交互都这么搞。

原生调 JS:runJavaScript

原生主动调 H5 的场景也很多——登录完成后通知 H5 刷新、推送消息到了让 H5 更新 UI。

Button('通知H5登录成功')
  .onClick(() => {
    const token = 'abc123xyz';
    this.controller.runJavaScript(`onLoginSuccess('${token}')`);
  })

Button('更新H5数据')
  .onClick(() => {
    const data = JSON.stringify({ count: 42, status: 'ok' });
    this.controller.runJavaScript(`updateData(${data})`);
  })

关键代码讲解:

  • runJavaScript 参数是一段 JS 代码字符串,不是函数名
  • 传参要自己拼字符串,注意引号转义和 JSON 序列化
  • 调用时机要确保 H5 页面已经加载完成,在 onPageEnd 回调后再调比较稳

还有个升级版 runJavaScriptExt,支持返回 ArrayBuffer 二进制数据,适合需要传图片、文件的场景。

Cookie 与登录态

混合开发绕不开的问题:原生登录了,H5 怎么同步登录态? 答案是 Cookie。

import { webview } from '@kit.ArkWeb';

// 设置 Cookie
webview.WebCookieManager.setCookie('https://example.com', 'token=abc123; Path=/; Max-Age=86400');

// 读取 Cookie
let cookie = webview.WebCookieManager.fetchCookieSync('https://example.com');
console.info(`当前Cookie: ${cookie}`);

// 清除所有 Cookie(退出登录时)
webview.WebCookieManager.deleteEntireCookie();

关键代码讲解:

  • setCookie —— 设置指定域名的 Cookie,URL 要写完整
  • Max-Age=86400 —— 必须加过期时间,否则是 Session Cookie,WebView 关了就没了
  • fetchCookieSync —— 同步获取 Cookie,调试时好用
  • deleteEntireCookie —— 退出登录时清掉,别只清原生的忘了 H5 的

踩坑:setCookie 最好在 WebView 初始化完成后调。太早设可能会被 H5 页面加载时的请求覆盖掉。

踩坑:白屏问题

WebView 白屏,十个混合开发八个遇到过。常见原因和解决:

原因 现象 解决
没加网络权限 网络页面完全白 module.json5INTERNET 权限
JS 执行被禁 页面加载了但交互不生效 javaScriptAccess(true)
DOM Storage 被禁 SPA 页面白屏 domStorageAccess(true)
混合内容被拦截 HTTPS 页面加载 HTTP 资源白屏 服务端全走 HTTPS
WebView 未渲染 控件没高度 检查 width/height 是否设了
H5 本身报错 控制台有 JS 错误 开 DevTools 调试排查

排查套路:先看高度有没有 → 开 DevTools 看控制台 → 检查权限配置。 大部分白屏问题都是这三类。

写在最后

原生和 H5 通信,记住这个图就够了:

H5 调原生 → javaScriptProxy(注入原生对象)
原生调 H5 → runJavaScript(执行 JS 代码)
登录态同步 → Cookie(WebCookieManager)

别搞复杂了,这仨组合能覆盖绝大多数混合开发场景。真遇到性能瓶颈再考虑消息通道那种重型方案。

有什么 WebView 踩坑经历,评论区聊聊。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐