Electron 在鸿蒙 PC 上跑 webview,我是怎么把首屏从 4.2s 干到 1.1s 的
一、事情是这样的
我手头在做一个 App,叫雷达鸭,鸿蒙 PC 版是直接用 Electron 套壳跑的。同一份代码,我同事在 Windows 上点开是 800ms 出首页,我在他那台鸿蒙 PC 伴侣上点开是......你看:
[Windows 11] 首屏渲染:812ms
[鸿蒙 PC] 首屏渲染:4237ms
差了 5 倍。我当时第一反应是鸿蒙的 Chromium 内核版本老,于是查了一遍:
- Electron 28.0 内置 Chromium 120
- 鸿蒙 PC 的 WebView 内核(ArkWeb)版本号 120+ ✓
内核是新的。那为啥慢?
我用 app.getAppMetrics() 把进程指标全打出来,又用 Chrome DevTools 远程调试挂上去看 Network 面板,发现一个反常的现象:
所有静态资源(JS/CSS/图片)在鸿蒙 PC 上的
Waiting (TTFB)时间几乎是 Windows 的 3 倍。
但这些资源都是 file:// 协议从本地读的,根本不应该有 TTFB。我盯着 DevTools 看了十分钟,差点把屏幕戳穿。
二、被我忽略的"假后台"机制
直到我翻到 Electron 官方一份边角文档------就是 Electron 性能优化建议 里第四小节,提到一个东西:
When a window is in the background, Electron will throttle timers and animations to reduce CPU usage. This can be useful for power consumption, but it can also cause performance issues if your app does heavy work in the background.
说白了,Electron 默认开启了一个后台节流机制。如果你的窗口在用户视角里是"非激活状态",它会自动降频渲染、节流 JS 定时器。
我一看,鸿蒙 PC 上我的应用窗口启动时的 z-order 是低于系统桌面图标的。我点开应用的瞬间,窗口从"后台"切到"前台",但 Electron 内部的状态机有约 200ms 的延迟才反应过来。
这 200ms 里,首屏的 HTML 已经加载完毕,但 JS 执行被节流,渲染管线被卡住。等到节流解除,开始绘制,4 秒就这么过去了。
三、那个被雪藏的参数
解决方案出乎意料地简单------关掉这个节流:
javascript
// main.js
const { app, BrowserWindow } = require('electron')
app.commandLine.appendSwitch(
'disable-renderer-backgrounding',
'--disable-renderer-backgrounding'
)
app.commandLine.appendSwitch(
'disable-background-timer-throttling',
'--disable-background-timer-throttling'
)
app.commandLine.appendSwitch(
'disable-backgrounding-occluded-windows',
'--disable-backgrounding-occluded-windows'
)
app.whenReady().then(() => {
const win = new BrowserWindow({
width: 1280,
height: 800,
webPreferences: {
backgroundThrottling: false, // 这一行才是关键
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
})
win.loadFile('index.html')
})
webPreferences.backgroundThrottling: false 是显式关闭窗口级节流,前面三个 appendSwitch 是从 Chromium 命令行层兜底。两个都得加,只加一个实测会漏掉一部分场景。
我改完打包,跑了一轮:
| 优化项 | 首屏 |
|---|---|
| 原始版本 | 4237ms |
| + backgroundThrottling:false | 2180ms |
| + 三个 appendSwitch | 1143ms |
| + preload 拆分(见下) | 1080ms |
backgroundThrottling 这一项就吃掉了 50% 的优化空间。文档里就一句话带过,鬼知道踩坑的人有多少。
四、preload 拆分的隐藏收益
顺手再提一个相关技巧。Electron 的 preload 脚本默认是同步加载的,会阻塞首屏渲染。如果你的 preload 里有 require 了一堆 npm 包(特别是带 native binding 的),首屏会更慢。
我当时的 preload 里有个 require('sqlite3'),光初始化就 600ms。拆成异步加载:
javascript
// preload.js
const { contextBridge } = require('electron')
// 同步的、必须阻塞的(少量)
contextBridge.exposeInMainWorld('appReady', true)
// 异步加载的(用 ipc 走)
contextBridge.exposeInMainWorld('db', {
query: (sql) => window.electronAPI.query(sql),
})
javascript
// main.js
ipcMain.handle('db:query', async (event, sql) => {
const sqlite3 = require('sqlite3') // 懒加载
// ...
})
这样 sqlite3 不会在首屏阶段同步阻塞 window.appReady 的暴露。优化后能再砍 60-80ms,蚊子腿也是肉。
五、我的反思
官方文档不一定写得对 ------"backgroundThrottling 默认开启"这个行为,在桌面端是合理的(你切走窗口时省电),但在鸿蒙 PC 这种窗口 z-order 行为跟 Windows 不一样的环境里,就变成了性能陷阱。
我后来还发现另一个类似的情况:鸿蒙 PC 的 Electron 窗口在用户最小化后 会自动进入类后台状态,但恢复时不会主动触发 visibilitychange 事件 。这导致我代码里的 document.addEventListener('visibilitychange', ...) 在鸿蒙 PC 上根本没反应。Windows 上没问题,鸿蒙上死活不触发。
最后没办法,我自己加了个轮询:
javascript
let lastHidden = document.hidden
setInterval(() => {
if (lastHidden !== document.hidden) {
document.dispatchEvent(new Event('visibilitychange'))
lastHidden = document.hidden
}
}, 500)
丑是丑了点,但管用。
六、一个我最近开始犹豫的方案
最后说点掏心窝的:如果你正在考虑要不要上 Electron 跑鸿蒙 PC,我个人更建议你直接用鸿蒙原生 ArkWeb(WebView)。
原因有三条:
- 性能差距抹不平。即使我优化到 1.1s,鸿蒙原生 WebView 同样页面是 400ms 出头。这是底层 IPC 的差距,不是代码能解决的。
- 安装包体积。Electron 打出来 180MB,ArkWeb 套壳的元服务只有 30MB。用户在华为应用市场看到 180MB 的安装包,下载意愿会打折扣。
- 审核麻烦。雷达鸭上架时,审核员问了一句"为什么需要 Electron",我解释了半天跨端代码复用,最后被打回要求补充说明。
我现在新做的页面已经默认 ArkTS + ArkWeb 写原生了,Electron 只用来跑老的历史包袱。除非你有强需求(比如必须用某个 Electron 专属的 Node 模块),否则真的别硬上 Electron。
写到这里有点累了。文章里 1.1s 那个数字是我在 5 次跑分中取的中位数,最快一次 980ms,最慢 1280ms。如果你想自己复现我的测试环境,可以装个 electron-to-chromium 工具看版本对齐情况。
你最近在鸿蒙 PC 上跑 Electron 踩过什么坑?留言聊聊。
关于作者:十年以上软件开发老兵,软件设计师 + 注册人工智能工程师 + agent 工程师,平时主要写鸿蒙 ArkTS 北向开发和 Web 前端,最近在折腾 AI 自动化提效。不定期在 CSDN 分享一些鸿蒙 / AI 方向的实战笔记。
本文遵循 MIT 协议,转载请注明出处。