JS 互相调用导致内存爆炸?HarmonyOS 7 Web 混合开发进阶

之前接手了一个鸿蒙工程,里面有一个很重的 H5 页面。用户在 App 里来回切换,只要切上几十次,App 绝对闪退。

我拿着性能工具抓了半天内存,发现是 Web 组件和原生的 JavaScriptProxy 内存泄漏。

这篇复盘,讲讲 Web 混合开发里那些极易导致内存爆炸的雷区。

https://placeholder-01.jpg/

Bridge 里藏了上下文

我们在原生侧注册给 H5 的桥接类(Bridge),往往会用到 Context。

typescript

复制代码
// 极其容易泄漏的写法
class MyBridge {
  context: common.UIAbilityContext;

  constructor(context: common.UIAbilityContext) {
    // 把上下文存到了成员变量里
    this.context = context; 
  }

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

// 注册时
const bridge = new MyBridge(getContext(this) as common.UIAbilityContext);
this.controller.registerJavaScriptProxy(bridge, 'nativeBridge', ['showToast']);

致命点: UIAbilityContext 的生命周期是跟整个 Ability 绑定的。Web 组件被销毁了,但这个 Bridge 对象被底层(C++ 侧)引用着,导致整个 Context 无法释放,最终导致窗口泄漏。

解法: Bridge 里的方法,尽量不要持有长生命周期的上下文。如果非要用,用弱引用,或者通过方法参数动态获取。

忘记注销 Proxy

当用户退出含有 Web 组件的页面时,如果不注销注册的 Proxy,底层依然保留着对原生对象的引用。

必须在页面的 aboutToDisappear 里,把注册的桥接对象全部干掉。

typescript

复制代码
aboutToDisappear() {
  if (this.controller) {
    // 注销所有注册到 JS 的 Proxy 对象
    this.controller.deleteJavaScriptProxy('nativeBridge');
    // 如果 H5 内部还在跑定时器,最好强制停止
    this.controller.stop(); 
  }
}

H5 侧的连带责任

很多时候,原生代码没问题,是 H5 那边把原生坑了。

有一次,H5 端为了防止按键连点,在 onClick 里写了个 setInterval 一直调原生的方法。结果 H5 页面关闭时,定时器没清,原生一直在响应,直接卡死。

约定大于配置: 在制定 Bridge 协议时,必须给 H5 前端立规矩。

  1. 原生向 H5 推送数据,必须由原生主动调用 runJavaScript。

  2. H5 调用原生方法,必须保证"短平快",禁止在定时器里高频调用。

混合开发,就像是在两个不同的世界之间修桥。桥修得好,交通顺畅;桥基不牢,全盘崩溃。

  1. 千万别在 Bridge 类里随意持有 UI 上下文。

  2. 页面销毁前,务必 deleteJavaScriptProxy。

  3. 多用 runJavaScript 替代 H5 的轮询主动拉取。