适配:HarmonyOS7,API26
连载:01/10

开篇:很多人踩坑的ArkWeb内存坑

做鸿蒙混合开发的,基本都遇到过这个问题。直接把H5页面丢进ArkWeb,来回切页面,用个几十次应用就开始卡顿,严重直接被系统杀掉。

很多人第一反应,去前端查JS内存泄漏。我之前项目也是这么干,查了两三天,H5代码干干净净。反复排查才发现,锅根本不在前端。

问题根源:ArkWeb内核实例,和ArkUI页面生命周期,根本不是一回事。
旧版鸿蒙教程很多不会强调这点,放到HarmonyOS7上,这个问题被放大了。系统对内存管控收紧,页面关掉,但Web内核还在后台活着,定时器、缓存、JS虚拟机全部留在内存里,越堆越多。

这篇手记,我们就从这个真实线上问题入手,一步步搞定ArkWeb页面加载,把Web组件的创建、挂载、销毁完整管起来。

本篇目标:

  1. 分清ArkUI页面、ArkWeb内核两套生命周期,不再混淆
  2. 实现可控H5页面加载,捕获白屏、加载失败异常
  3. 正确释放Web资源,解决反复打开页面带来的内存堆积
  4. 完成鸿蒙原生和H5之间双向JS通信基础能力
  5. 拿到一份可以直接粘贴进DevEco Studio跑通的完整代码
    在这里插入图片描述

一、核心误区:两个生命周期不要混为一谈

ArkUI页面生命周期大家很熟:aboutToAppear页面准备渲染,aboutToDisappear页面即将销毁。
但ArkWeb不是普通UI组件。它是一个完整浏览器内核。只要实例创建,系统就分配独立JS虚拟机、缓存、网络资源。

一句话类比:ArkUI页面只是外壳,ArkWeb内核是独立运行的浏览器。外壳关掉,浏览器进程不会自动关闭,你必须手动关闭它。

常见错误认知
❌ 页面aboutToDisappear执行,Web资源自动释放
✅ aboutToDisappear只是页面销毁回调,必须手动调用接口销毁Web内核

HarmonyOS7下ArkWeb生命周期时序:

  1. aboutToAppear:初始化WebController,预先配置内核参数
  2. build渲染Web组件,创建Web内核,发起页面加载请求
  3. onPageBegin:页面开始加载
  4. onPageEnd:页面全部资源加载完成
  5. onError:捕获网络、证书、404这类加载异常
  6. aboutToDisappear:先停止加载,再调用destroy销毁Web实例

划重点:API26文档里,高频复用场景,destroy不再是可选优化,是硬性要求。很多网上老教程不提这个,拿来直接用,上线必翻车。

在这里插入图片描述

二、项目准备:DevEco新建HarmonyOS7工程

新建工程,模板选Empty Activity,SDK版本选API26,也就是HarmonyOS7。注意不要选7.0。

第一件事,配置module.json5权限。加载线上H5,INTERNET权限必须写,很多新手卡在这,页面一直白屏,日志还看不出明显报错。

// module.json5
"requestPermissions": [
  {
    "name": "ohos.permission.INTERNET"
  }
]
简单说明:访问外网 H5 才需要这个权限。如果只是读取工程 rawfile 本地 html,不需要网络权限。

三、基础代码:WebController 管理页面加载

WebController 是 ArkWeb 的控制器,所有对 Web 实例的操作,都通过它。
不要在 build 函数内部 new WebController。build 会反复执行,多次实例化控制器,内存直接爆炸。控制器定义在组件属性,只初始化一次。

完整基础页面代码:完整基础页面代码:


import { Web, WebController, WebResourceError } from '@kit.ArkWeb';

@Entry
@Component
struct ArkWebDemoPage {
  // 控制器,仅实例化一次,不要加@State
  private webController: WebController = new WebController();
  @State isLoading: boolean = true;
  @State errorText: string = "";

  aboutToAppear() {
    console.info("准备初始化ArkWeb");
    // 开启JS执行、本地存储,大部分H5页面依赖这两项
    this.webController.setJavaScriptEnabled(true);
    this.webController.setDomStorageEnabled(true);
  }

  build() {
    Column() {
      // 加载错误提示
      if(this.errorText.length > 0) {
        Text(`加载失败:${this.errorText}`)
          .fontSize(16)
          .fontColor(Color.Red)
          .margin(12)
      }
      Web({
        src: "https://example.com/index.html",
        controller: this.webController
      })
      .onPageBegin(() => {
        this.isLoading = true;
        console.info("H5页面开始加载");
      })
      .onPageEnd(() => {
        this.isLoading = false;
        console.info("H5页面加载完成");
      })
      .onError((err: WebResourceError) => {
        this.isLoading = false;
        this.errorText = `错误码${err.errorCode}:${err.description}`;
        console.error("页面加载异常", err);
      })
      .width('100%')
      .layoutWeight(1)
    }
  }

  aboutToDisappear() {
    console.info("页面退出,释放ArkWeb资源");
    // HarmonyOS7核心代码,销毁Web内核
    this.webController.destroy();
  }
}

代码逐行解释

  1. private webController: WebController:控制器作为组件私有属性,全局唯一实例。不需要响应式,不要加 @State。
  2. setJavaScriptEnabled(true):开启 JS。静态纯展示页面可以关闭,减少内存占用。
  3. onPageBegin /onPageEnd:监听页面加载状态,我们可以在这里写加载动画、进度条。
  4. onError 回调:捕获各类加载异常,给用户展示错误提示,而不是一片空白。
  5. aboutToDisappear 里webController.destroy(),本篇最关键一行。
    不写这一行,反复进出页面,Web 内核持续驻留内存,越堆越高。

吐槽一句:网上很多鸿蒙 ArkWeb 示例直接省略 destroy。那些是简单 demo,只打开一次页面。放到业务项目高频切换场景,就是定时炸弹。

四、本地 H5 资源加载

很多混合应用会把静态 html、js、css 打包进应用,不依赖外网。资源放到 rawfile 目录。
rawfile 页面地址写法:$rawfile("index.html")

Web({
  src: $rawfile("index.html"),
  controller: this.webController
})

小提醒:rawfile 里面文件名、路径不要写中文,部分内核版本读取资源会直接失败。

五、进阶:H5 与 ArkTS 双向通信

混合开发最核心能力:H5 页面触发事件,通知鸿蒙原生代码;原生主动调用 H5 里面 JS 函数。
HarmonyOS7 使用 registerJavaScriptProxy 注册 JS 桥对象。

重点坑:JS 代理必须在页面加载之前注册。页面加载完再注册,H5 拿不到桥对象,调用直接报错。

修改 aboutToAppear:

aboutToAppear() {
  console.info("准备初始化ArkWeb");
  this.webController.setJavaScriptEnabled(true);
  this.webController.setDomStorageEnabled(true);

  // 注册JS通信桥,H5中访问 window.harmonyBridge
  this.webController.registerJavaScriptProxy({
    sendMsgToNative: (msg: string) => {
      console.info("收到H5消息:", msg);
    }
  }, "harmonyBridge", ["sendMsgToNative"])
  // 刷新页面,让注册的代理生效
  this.webController.refresh();
}

H5 页面 index.html 里面 JS 代码

<script>
// H5调用鸿蒙原生方法
window.harmonyBridge.sendMsgToNative("来自H5表单提交消息");

// 鸿蒙原生可以调用这个H5函数
function receiveNativeMsg(msg) {
  console.log("原生传来消息:", msg);
}
</script>

鸿蒙主动调用 H5 JS 函数示例

this.webController.runJavaScript("receiveNativeMsg('原生通知H5:数据已保存')", (res)=>{
  console.info("调用H5JS返回结果", res);
})

在这里插入图片描述

通信踩坑清单

  1. registerJavaScriptProxy,注册时机必须早于页面加载
  2. 方法名数组必须手动列出,不写的方法 H5 无法访问
  3. 尽量传递字符串,复杂对象先 JSON 序列化,不要直接传大对象,容易出现序列化异常

六、边界场景优化:退出前停止页面加载

有个很常见的场景:页面正在加载网络资源,用户快速返回退出页面。Web 还在后台请求,页面销毁时网络回调回来,容易出现异常、崩溃。

我们在销毁内核之前,先调用 stop 终止所有网络请求。

aboutToDisappear() {
  console.info("页面退出,释放ArkWeb资源");
  // 终止正在进行的页面请求
  this.webController.stop();
  // 销毁Web内核实例
  this.webController.destroy();
}

执行顺序不能颠倒:stop 在前,destroy 在后。

七、真机验证:看内存曲线,确认泄漏修复

模拟器的内存统计不准,一定要真机跑测试。
测试步骤:打开页面,等待 H5 加载完成,返回关闭页面。循环 20 次,观察内存。

  • 缺少 stop+destroy:内存持续上涨,几乎不会回落
  • 增加 stop+destroy:页面关闭后,内存回落至初始基线

我们用 DevEco Profiler 查看内存曲线,直观看到差别。
在这里插入图片描述

调试小技巧:Profiler 观察内存,重点看每次页面销毁后的内存回落幅度。如果内存只涨不跌,说明 Web 实例没有正常销毁。

八、本篇总结 + 课后练习任务

知识点汇总

  1. ArkWeb 是重量级浏览器内核组件,ArkUI 页面生命周期不等于 Web 内核生命周期,HarmonyOS7 必须手动销毁实例。
  2. WebController 单独在组件属性定义,禁止在 build 内部重复实例化。
  3. 线上 H5 记得配置 INTERNET 权限,本地静态资源使用 $rawfile 加载。
  4. JS 双向通信桥,注册动作要放在页面加载之前。
  5. 页面销毁标准顺序:stop 停止请求 → destroy 销毁 Web 实例,规避残留资源。

实践任务,为下一期做铺垫

基于本篇代码自行实现两点:

  1. 增加 H5 页面加载进度条 UI;
  2. 增加 URL 拦截逻辑,捕获 H5 页面跳转事件,接管路由。

下一期手记 02,我们专门讲 ArkWeb 拦截跳转链接、新窗口处理,H5 路由和鸿蒙原生路由打通方案。
在这里插入图片描述

Logo

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

更多推荐