《Web 到 HarmonyOS》-- 嵌入 Web 与桥接基础

系列:《Web 到 HarmonyOS》· 插播(不占正篇序号)

文档性质:方案整理,不是本仓库的集成记录

适合人群:会 Vue / iframe / postMessage,准备把已有 H5 嵌进鸿蒙


写在前面

嵌网页有两套容器。应用里用 ArkUI 的 Web ,元服务里用 ASCF 的 web-view。名字都像 WebView,桥不是同一套。

这篇先把两者对齐,后面只展开 Web 怎么放进页面、两个方向怎么通信、按什么顺序用 。按「业务里已经有 H5」来写方案,不表示当前学习工程已经接了 Web

H5 和原生是两个运行时。Vue 的 ref 不会变成 @State,原生对象也不能直接当参数传进网页。过桥的只有 JSON。


一、Web 和元服务 web-view 对比

应用里的 Web 元服务 web-view
写在哪 ArkUI 页面的 build() 里,和 Column 同级 元服务 ASCF 页面,写法接近小程序
心智模型 你能控制的 iframe 微信小程序的 <web-view>
打开页面 Web({ src, controller })src 可以是网络地址或 $rawfile <web-view src="{{url}}">,网络地址要先在 AGC 配业务域名
H5 调原生 容器把对象注入 window,H5 直接 window.nativeApi.xxx() H5 调 has.ascfweb.postMessage,不能自己挂一个原生对象
原生调 H5 controller.runJavaScript(...),等于在 iframe 里执行一段脚本 has.createWebViewContext().postMessage({ data }),H5 用 has.ascfweb.onMessage
实时性 onPageEnd 之后两边都能立刻调 H5 发给元服务的 bindmessage,常常要等页面返回或销毁才回调,不是 message 事件
怎么判断跑在容器里 window 上有没有注入的对象 User-Agent 里有 ASCF/
适合 应用内嵌一块已有 Vue 页,还要拿 token、关原生页、调系统能力 元服务里打开一个网页,只走官方 SDK 列出的能力

选型就看宿主。这篇后面的通信和用法,都只针对左边这套 Web。元服务不要把 javaScriptProxy 抄过去,应用里也不要装 @atomicservice/ascf-web-sdk 来代替桥。


二、Web 怎么使用

Web 是页面里的一块区域,不是 EntryAbility。Ability 只负责把窗口打开。对照现在的 entry:主框架是 Tab,每个 Tab 一把导航栈。H5 是某一栈里的一页,页里面放一个 Web。退栈、切 Tab 仍走 NavPathStack。网页内部的 vue-router 只在这块区域里生效。

一个 Web 配一个控制器,控制器放在这个页面上,不要在子组件里再 new 一个去调它。

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

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

  build() {
    Column() {
      Web({ src: 'https://example.com/h5/', controller: this.controller })
        .javaScriptAccess(true)
        .domStorageAccess(true)
        .onPageEnd(() => {
          this.notifyH5Ready();
        })
        .onErrorReceive(() => {
          // 页没打开,不要在这里调桥
        })
        .width('100%')
        .height('100%')
    }
    .width('100%')
    .height('100%')
  }

  private notifyH5Ready(): void {
    this.controller.runJavaScript('window.onNativeReady && window.onNativeReady()');
  }
}

使用时先打开这几个开关,再谈桥:

配置 不开会怎样 对应的 Web 习惯
javaScriptAccess(true) 页能开,脚本不执行,window.nativeApi 不会出现 浏览器默认就能跑 JS,这里要显式打开
domStorageAccess(true) localStorage / sessionStorage 不可用 前端存登录态、草稿会静默失败
fileAccess(true) 本地 $rawfile 页要读本地文件时失败 只嵌网络页可以先不开
mixedMode(MixedMode.All) HTTPS 页里的 HTTP 资源被拦 混合内容,和浏览器策略一样,要显式放行

src 两种来源:

  • 线上已有的 Vue 站点:'https://example.com/h5/'
  • 打进安装包的静态页:$rawfile('index.html')

地址要换(例如带上不同业务 id),换的是这次加载的 src,不是改一个 @State 就指望网页 DOM 跟着变。

加载回调按 iframe 的生命周期记:

回调 什么时候用
onPageBegin 开始请求,可显示原生 loading
onProgressChange 进度
onPageEnd 相当于 iframe.onload。从这里起才能 runJavaScript
onErrorReceive 没打开。先查地址、证书、混合内容,不要先改桥

推荐的使用顺序:

  1. 页面创建 WebviewControllerbuild() 里放 Web,打开 javaScriptAccess
  2. 需要存储就再开 domStorageAccess
  3. onPageEnd,通知 H5「桥可以用了」。
  4. 之后原生用 runJavaScript 调 H5;H5 用注入对象调原生。
  5. 用户要离开这块网页时,走原生导航栈,不要只调 history.back()。那只退 H5 自己的历史。

三、Web 怎么通信

两个方向,两套 API,不要合成一次 postMessage。依据是 鸿蒙与 H5 桥接 里的基础桥。

方向 用什么 相当于
H5 → 原生 javaScriptProxy 注入的对象 容器在 window 上挂一个 SDK,只暴露白名单方法
原生 → H5 controller.runJavaScript iframe.contentWindow 上执行一段脚本

两边约定同一种消息,参数和返回值都用字符串:

ts 复制代码
interface BridgeMessage {
  type: string;
  payload: string;
}

1. H5 调原生:注入 window 上的对象

Web 上声明代理。name 是挂到 window 上的名字,methodList 是允许 H5 调用的方法。没写进名单的方法,网页调不到。

ts 复制代码
Web({ src: 'https://example.com/h5/', controller: this.controller })
  .javaScriptAccess(true)
  .javaScriptProxy({
    name: 'nativeApi',
    methodList: ['getToken', 'closePage', 'postMessage'],
    controller: this.controller,
    object: {
      getToken: (): string => {
        return JSON.stringify({ token: '原生登录态' });
      },
      closePage: (): void => {
        // 退出当前原生页
      },
      postMessage: (raw: string): string => {
        const msg = JSON.parse(raw) as BridgeMessage;
        // 按 msg.type 分发
        return JSON.stringify({ ok: true });
      }
    }
  })

H5 里就是普通函数调用。先判断对象在不在,再 parse 返回值:

js 复制代码
function readToken() {
  if (!window.nativeApi || !window.nativeApi.getToken) {
    return;
  }
  const data = JSON.parse(window.nativeApi.getToken());
  sessionStorage.setItem('token', data.token);
}

function tellNative(type, payload) {
  window.nativeApi.postMessage(JSON.stringify({
    type: type,
    payload: JSON.stringify(payload)
  }));
}

这和在网页里调用一个提前注入的 JS-SDK 相同:方法名固定,入参出参都是字符串。复杂对象在调用前 JSON.stringify,在 ArkTS 里 JSON.parse。不要把 Vue 组件、函数当参数。

登录态走这条方向拿:H5 在自己的 mounted 里调 getToken。不要假设浏览器 Cookie 会自动出现在 Web 里。退出时由 H5 再调一个 clearSession,或者由下一节的原生方向通知 H5 清掉 sessionStorage。两边都清,不能只清一边。

2. 原生调 H5:runJavaScript

H5 先把接收函数挂到 window,和先 addEventListener('message') 再等消息是同一顺序:

js 复制代码
window.onNativeReady = function () {
  readToken();
};

window.onNativeMessage = function (raw) {
  const msg = JSON.parse(raw);
  const payload = JSON.parse(msg.payload);
  // 按 msg.type 更新页面
};

原生必须等 onPageEnd。在这之前函数还不在。参数用 JSON.stringify 放进脚本,避免引号把脚本截断:

ts 复制代码
private sendToH5(type: string, payload: string): void {
  const raw = JSON.stringify({ type: type, payload: payload });
  this.controller.runJavaScript(`window.onNativeMessage(${JSON.stringify(raw)})`);
}

runJavaScript 的回调拿到的是脚本的返回值字符串。要从网页读数据时,让脚本 return JSON.stringify(...),回调里再 parse。异步函数要在脚本里自己 await 完,最后 return 一个 JSON 字符串,不要把 Promise 对象当结果。

原生把 token 推给 H5、刷新网页里某块数据、通知网页「用户点了原生按钮」,都走这个方向。它灵活,但是在拼脚本,所以脚本内容只来自业务常量或已经校验过的 JSON,不要把 H5 刚传回来的原文再执行一遍。

3. 一次业务里两边怎么配合

以「原生已登录,打开 H5,H5 要 token,并且能通知原生关页」为例,顺序是固定的:

sequenceDiagram participant N as 原生页面 participant W as Web participant H as H5 N->>W: 创建控制器并加载 src W->>H: 解析页面 W-->>N: onPageEnd N->>H: runJavaScript onNativeReady H->>N: window.nativeApi.getToken N-->>H: JSON 字符串 H->>N: window.nativeApi.closePage N->>N: 退出导航栈

对应到代码上就是上一节的三块:页面持有控制器,javaScriptProxy 提供 getToken / closePageonPageEnd 里调用 onNativeReady。H5 在 onNativeReady 里再去读 token。不要在 Vue 的 created 里假设 nativeApi 已经存在;注入时机和文档加载有关,就绪通知以 onPageEnd 为准。

methodList 里只留业务方法:取 token、关页、把 { type, payload } 交给原生。不要留「执行任意脚本」。


四、用的时候先查这些

  1. 白屏:看 srconErrorReceive。桥还没开始。
  2. nativeApi is undefinedjavaScriptAccess 没开,或 name 和网页里写的不一致。
  3. 方法存在但调用报错:方法没写进 methodList
  4. 原生调 H5 没反应:早于 onPageEnd,或 H5 还没把函数挂到 window
  5. 对面拿到 [object Object]:过桥前没有 JSON.stringify
  6. H5 里 localStorage 写不进:没开 domStorageAccess
  7. 点返回只在网页历史里跳:关页要走原生导航,不是 history.back()

五、结论

应用嵌 H5 用 Web:页面里放组件,一个控制器,打开 JS。通信是两条线,H5 调 window 上的白名单方法,原生在 onPageEnd 之后用 runJavaScript。数据只走 JSON 字符串。

元服务是另一套 postMessage,和这套不要混用。

相关推荐
一拳不是超人1 小时前
之前用 AI 两小时写的塔罗网站,我真把它做上线了,然后呢?
前端·人工智能·程序员
flash俊杰2 小时前
ASR 转写与字幕时间轴:强制对齐、CPS 约束与 SRT 工程化
前端
前端炒粉2 小时前
AI工具面经
前端
唐小码2 小时前
裁员的风刮到了三线荒凉的城市
前端
晚安日记wanna2 小时前
React 为何不做双端 diff?Vue 与 React 更新逻辑的三层差异
前端·react.js·面试
flash俊杰3 小时前
AI 分段引擎与自动剪辑决策:从语义片段到时间线草稿的映射
前端·ai编程
flash俊杰3 小时前
时间线交互引擎:从像素到帧的逆向映射与磁吸网格
前端
光影少年3 小时前
setImmediate 和 setTimeout(0) 的区别
android·前端·react.js·ios·前端框架