Electron 本地图片在鸿蒙 PC 上白图,我注册了个自定义协议
上周同事扔给我个需求:在 Electron 应用里加一个图片预览面板,用户点一下就能看项目里的截图。我想了想,这不就一行 <img src="file://..." /> 的事吗,半小时搞定。
结果在鸿蒙 PC 上一跑,傻了。所有图片都是白的。
我先检查一下路径对不对。path.join(__dirname, '../assets/screenshot.png'),拼出来是 /home/user/project/assets/screenshot.png,文件也确实在那儿。换了几种写法:file:/// 三根斜杠、file:// 两根、甚至直接把绝对路径写死,全是白图。
我打开 DevTools,Network 面板里图片请求的状态是 (blocked:NotAllowed)。不是 404,不是 500,是被blocked了。这我就有点懵了------file 协议访问本地文件,这不是天经地义的吗?
查了一个小时鸿蒙文档,终于在一行不起眼的小字上找到了答案:鸿蒙 PC 的安全沙箱策略对 Web 内容的文件访问有限制,file:// 协议默认会被拦截。这和 Windows、macOS 上"本地文件随便读"的行为完全不同。
好,那怎么办?
我第一个想法是关掉 Electron 的 webSecurity。在 BrowserWindow 的配置里加上:
javascript
webPreferences: {
webSecurity: false
}
重启,图片出来了。但这感觉像个定时炸弹------webSecurity 关掉之后,CORS 限制也没了,万一后面接个外部接口,安全隐患太大。而且我在团队群里提了一嘴,斌哥直接回了一句:"你确定要这么干?"
我赶紧把配置改回去了。
第二个想法:不走 file 协议,用 IPC 把图片内容从主进程读到渲染进程,再转成 base64 塞进 img 标签。
javascript
// 主进程
ipcMain.handle('read-image', async (_, filePath) => {
const buffer = await fs.promises.readFile(filePath);
return `data:image/png;base64,${buffer.toString('base64')}`;
});
// 渲染进程
const base64 = await ipcRenderer.invoke('read-image', imagePath);
img.src = base64;
小图片还行,一张 200KB 的截图,base64 编码后 270KB 左右,肉眼感觉不出延迟。但我试了张 8MB 的设计稿,界面直接卡了将近两秒。base64 的体积膨胀了 33%,而且渲染进程要一次性把字符串塞进 DOM,内存占用也跟着涨。
这条路能走,但算不上好方案。
等等,我漏说一个前提。Electron 其实是支持注册自定义协议的,我之前从来没用过这个功能,一直觉得那是"高级玩法",跟我没关系。
那天晚上我翻 Electron 文档,看到 protocol.registerFileProtocol,突然意识到:既然 file 协议被拦截,那我自己造一个协议不就行了?
原理很简单。我注册一个 app:// 协议,当渲染进程请求 app://assets/screenshot.png 时,主进程拦截这个请求,把路径映射到真实的本地文件,然后返回文件内容。对渲染进程来说,这就是一个普通的 HTTP 请求,鸿蒙的安全策略不会拦截。
代码比我想象的还少:
javascript
const { protocol } = require('electron');
const path = require('path');
// 在 app ready 之后注册
app.whenReady().then(() => {
protocol.registerFileProtocol('app', (request, callback) => {
const url = new URL(request.url);
// 把 app:// 映射到应用目录
const filePath = path.join(__dirname, '..', url.pathname);
callback(filePath);
});
});
渲染进程里,img 标签的写法变成:
html
<img src="app://assets/screenshot.png" />
就这么简单。重启应用,图片出来了。而且因为走的是 Electron 自己的协议处理器,不经过 Chromium 的 file 安全检查,鸿蒙根本感知不到你在读本地文件。
我顺手测了一下加载速度。用 performance.now() 对比了三种方案加载同一张 3MB 图片的耗时:
- file:// 在 Windows 上:约 45ms
- file:// 在鸿蒙上:直接白图(算无限大吧)
- base64 IPC 方案:约 280ms(包含编码传输和解码)
- app:// 自定义协议:约 52ms
自定义协议在鸿蒙上的速度和 Windows 上的 file:// 几乎一样,而且比 base64 方案快了五倍多。
更妙的是,这个方案还有额外的好处。以前我的图片路径里混着 path.join(__dirname, ...) 和硬编码字符串,现在全部统一成 app:// 开头的 URL,渲染进程的代码干净了不少。甚至以后如果把资源打包进 asar,也只需要改协议处理器的映射逻辑,渲染层完全不用动。
回头想想,这坑其实不深,就是文档写得太分散了。鸿蒙的安全策略、Electron 的协议注册、Chromium 的 file 访问规则,三个东西各自为政,你得把它们串起来才能想通。
如果你也在 Electron 里读本地文件,而且打算支持鸿蒙 PC,我的建议是直接上自定义协议。别等白图了才开始查。
你遇到过类似的安全策略拦截吗?是怎么绕过去的?
关于我
我叫老三,一个写了十年代码的前端 + 鸿蒙 ArkTS 水手。
目前主业做 Taro 多端项目,业余时间全泡在 AI 自动化和独立开发上------不是因为多热爱加班,而是打心底觉得,程序开发这件事正在被 AI 重构,我不跟上就会被甩下。
这个账号记录的就是我在这条路上的真实经历:踩过的坑、推翻过的方案、以及偶尔值得高兴的小进展。不写教科书,不讲大道理,只分享我自己试过、做过、确认过的东西。
如果你也在写代码,或者也在思考 AI 时代开发者该往哪走------欢迎留言聊聊,一起摸索。
本文遵循 MIT 协议,转载请注明出处。