Electron
第一章:热更新概述
第一章:热更新概述
术语说明:本章中的“热更新”主要指 应用更新(OTA) 与增量更新策略, 不等同于前端开发期的 HMR(Hot Module Replacement)。
目录
为什么桌面应用需要热更新
Web 应用 vs 桌面应用的更新差异
Web 应用更新:
开发者部署到服务器 → 用户刷新页面 → 立即获得新版本
延迟: 0(用户无感知)
用户参与: 无
回滚: 服务端回滚,用户无感知
桌面应用"传统"更新:
开发者发布新版本
│
▼
用户收到通知 → 下载完整安装包 (100-200MB)
│
▼
关闭应用 → 运行安装程序 → 重启应用
延迟: 数分钟到数小时
用户参与: 高(需要主动操作)
用户可能: 忽略更新 → 使用旧版本 → 遇到已修复的 bug
SaaS 时代的需求
现代桌面应用(如 VS Code、Slack、Notion)需要接近 Web 应用的更新速度:
SaaS 快速迭代需求:
┌──────────────────────────────────────────────────┐
│ │
│ 需求1: 快速修复 │
│ 发现严重 bug → 修复 → 用户几小时内获得修复 │
│ │
│ 需求2: 灰度发布 │
│ 新功能先推给 10% 用户测试 → 确认无误 → 全量推送 │
│ │
│ 需求3: 强制更新 │
│ 安全漏洞修复必须推到所有用户 │
│ │
│ 需求4: 最小打扰 │
│ 后台下载 → 下次启动时自动更新 │
│ 或甚至不需要重启 │
│ │
│ 需求5: 可回滚 │
│ 新版本有问题时能快速回退 │
│ │
└──────────────────────────────────────────────────┘
更新策略全景图
三大类更新策略
更新策略分类:
策略 1: 全量替换
┌──────────────────────────────────────────────┐
│ 下载完整的新版本安装包,替换整个应用 │
│ │
│ 更新内容: Electron 运行时 + 应用代码 + 依赖 │
│ 下载量: 80-200MB │
│ 需要重启: ✅ 是 │
│ 实现复杂度: ★☆☆☆☆ │
└──────────────────────────────────────────────┘
│
│ 优化
▼
策略 2: 增量更新 (差分更新)
┌──────────────────────────────────────────────┐
│ 只下载新旧版本之间的差异部分 │
│ │
│ 更新内容: 变更的文件或二进制 diff │
│ 下载量: 5-30MB │
│ 需要重启: ✅ 是 │
│ 实现复杂度: ★★★☆☆ │
└──────────────────────────────────────────────┘
│
│ 进一步优化
▼
策略 3: 热补丁 (Hot Patch)
┌──────────────────────────────────────────────┐
│ 只替换前端代码(HTML/CSS/JS),不动原生部分 │
│ │
│ 更新内容: 渲染层代码 │
│ 下载量: 0.1-5MB │
│ 需要重启: ⚠️ 可能不需要 │
│ 实现复杂度: ★★★★☆ │
└──────────────────────────────────────────────┘
策略组合
实际项目中,通常组合使用多种策略:
典型的组合方案:
┌─────────────────────────────────────────────────────┐
│ │
│ Electron 运行时更新 → 全量替换(低频,每月一次)│
│ 主进程代码更新 → 增量更新(需要重启) │
│ 渲染层(前端)代码更新 → 热补丁(高频,无需重启) │
│ 紧急安全修复 → 强制全量更新 │
│ │
└─────────────────────────────────────────────────────┘
更新频率与策略的关系:
每天/每周 ────→ 热补丁(前端代码)
每两周 ────→ 增量更新(含主进程变更)
每月/每季度 ────→ 全量更新(Electron 版本升级)
紧急修复 ────→ 强制更新(安全漏洞)
各方案优劣对比
详细对比表
┌──────────────┬───────────────┬───────────────┬──────────────────┐
│ │ 全量替换 │ 增量/差分更新 │ 热补丁 │
├──────────────┼───────────────┼───────────────┼──────────────────┤
│ 下载量 │ 80-200MB │ 5-30MB │ 0.1-5MB │
│ 更新速度 │ 慢 │ 中等 │ 快 │
│ 需要重启 │ 是 │ 是 │ 可能不需要 │
│ 可更新范围 │ 一切 │ 一切 │ 仅前端代码 │
│ 实现复杂度 │ 低 │ 中 │ 高 │
│ 回滚能力 │ 需要旧安装包 │ 需要旧版快照 │ 容易(换回旧包) │
│ 适用频率 │ 低频 │ 中频 │ 高频 │
│ 安全性 │ 高(签名) │ 高(签名) │ 需要额外校验 │
│ 代表方案 │ electron- │ blockmap │ asar 替换 │
│ │ updater │ (electron- │ web bundle │
│ │ │ updater) │ 更新 │
│ 用户体验 │ 差(等待久) │ 中等 │ 好(几乎无感) │
└──────────────┴───────────────┴───────────────┴──────────────────┘
数据流对比
全量替换:
服务器 ──[完整安装包 150MB]──→ 客户端
客户端 ──→ 关闭应用 → 安装 → 重启
增量更新 (blockmap):
服务器 ──[差分数据 10MB]──→ 客户端
客户端 ──→ 合并差异 → 关闭应用 → 替换 → 重启
热补丁 (asar 替换):
服务器 ──[新 asar 包 3MB]──→ 客户端
客户端 ──→ 下载 → 替换 asar → 刷新/重启渲染进程
热补丁 (Web Bundle):
服务器 ──[前端资源包 0.5MB]──→ 客户端
客户端 ──→ 下载 → 替换前端文件 → 刷新页面(无需重启应用)
Electron 生态方案总览
1. electron-updater(最主流)
electron-updater 是 electron-builder 的配套更新方案:
特点:
- 与 electron-builder 深度集成
- 支持 GitHub Releases、S3、自建服务器
- 支持 blockmap 差分更新
- 支持代码签名校验
- 完整的事件生命周期
- Windows: NSIS 静默安装
- macOS: zip 解压替换
适用: 大多数 Electron 应用的首选方案
2. Sparkle(macOS 原生)
Sparkle 是 macOS 上历史悠久的更新框架:
特点:
- macOS 原生体验
- 支持 EdDSA 签名
- 支持增量更新
- 需要额外集成到 Electron
适用: 对 macOS 体验有极致要求的应用
3. 自建更新系统
完全自建更新服务:
特点:
- 完全控制更新逻辑
- 可实现任意更新策略
- 维护成本高
- 需要自己处理签名校验
适用: 大型团队、有特殊需求(灰度发布、A/B 测试等)
4. Hazel / Nuts(更新服务器)
开源的 Electron 更新服务器:
Hazel: Vercel 一键部署,读取 GitHub Releases
Nuts: 自托管服务,支持更多功能(下载统计、渠道管理)
适用: 需要自定义更新服务器但不想从零开发
方案选择决策树
你需要什么样的更新?
│
├── 简单的全量更新
│ └── electron-updater + GitHub Releases ✓
│
├── 需要灰度发布/渠道管理
│ └── 自建服务器 + electron-updater
│
├── 需要无感知热更新(前端代码)
│ └── Web Bundle 更新(自建)
│
├── 需要快速修复线上 bug
│ └── asar 热补丁 + electron-updater 组合
│
└── 需要完全可控
└── 自建更新系统
更新的技术挑战
1. 权限问题
更新过程中的权限挑战:
macOS:
┌──────────────────────────────────────────┐
│ /Applications/MyApp.app/ │
│ - 需要写入权限替换文件 │
│ - 如果用户从 DMG 安装到 /Applications │
│ 需要 admin 权限 │
│ - 如果安装到 ~/Applications │
│ 不需要额外权限 │
│ │
│ 解决: 更新助手进程 + 权限提升 │
└──────────────────────────────────────────┘
Windows:
┌──────────────────────────────────────────┐
│ C:\Users\<user>\AppData\Local\Programs\ │
│ - 通常安装在用户目录,不需要 admin │
│ - 但 per-machine 安装需要 admin │
│ - NSIS 静默安装模式处理 │
│ │
│ 解决: 使用用户级安装 (perMachine: false) │
└──────────────────────────────────────────┘
Linux:
┌──────────────────────────────────────────┐
│ AppImage: 不需要安装,替换文件即可 │
│ deb/rpm: 需要 root 权限 │
│ │
│ 解决: 推荐 AppImage 分发方式 │
└──────────────────────────────────────────┘
2. 签名校验
签名校验流程:
更新服务器
│
│ 下载更新包 + 签名信息
▼
┌─────────────────────────────┐
│ 客户端验证流程: │
│ │
│ 1. 下载 latest.yml │
│ (包含版本号、文件名、 │
│ SHA512 hash、大小) │
│ │
│ 2. 下载更新包 │
│ │
│ 3. 校验 SHA512 hash │
│ hash(下载文件) === yml 中的 hash?
│ │
│ 4. (macOS) 验证代码签名 │
│ codesign --verify │
│ │
│ 5. (Windows) 验证 Authenticode │
│ 签名证书是否匹配 │
│ │
│ 6. 校验通过 → 执行安装 │
│ 校验失败 → 拒绝更新 │
└─────────────────────────────┘
3. 回滚机制
回滚策略:
方案 A: 保留旧版本文件
┌──────────────────────────────────────────┐
│ 更新前: 备份当前版本到 backup/ │
│ 更新后: 如果启动失败,恢复备份 │
│ 清理: 确认新版本稳定后删除备份 │
└──────────────────────────────────────────┘
方案 B: 服务端回滚
┌──────────────────────────────────────────┐
│ 发现问题: 在更新服务器上将 latest 指向 │
│ 旧版本 │
│ 效果: 用户下次检查更新时"更新"到旧版本 │
└──────────────────────────────────────────┘
方案 C: 版本快照 (热补丁方案)
┌──────────────────────────────────────────┐
│ 本地保留最近 N 个版本的 asar/bundle │
│ 出问题时切换到上一个版本 │
│ 不需要重新下载 │
└──────────────────────────────────────────┘
4. 更新中断与恢复
如果更新过程中发生意外:
下载中断:
├── 网络断开 → 支持断点续传
├── 应用被关闭 → 下次启动继续下载
└── 磁盘空间不足 → 提示用户清理
安装中断:
├── 替换文件时崩溃 → 需要原子替换 (rename)
├── 签名校验失败 → 丢弃下载文件,不安装
└── 安装后启动失败 → 自动回滚
最佳实践: 原子更新
┌──────────────────────────────────────────┐
│ 1. 下载到临时目录 (不影响当前运行) │
│ 2. 校验完整性 (hash + 签名) │
│ 3. 原子替换 (rename,不是逐文件复制) │
│ 4. 重启应用 │
│ 5. 如果启动失败,自动恢复旧版本 │
└──────────────────────────────────────────┘
深入理解
更新策略与应用架构的关系
你的 Electron 应用架构决定了可用的更新策略:
架构 A: 单体应用(代码全在 main.js 和几个渲染页面中)
├── 任何改动都需要重启
└── 推荐: electron-updater 全量/增量更新
架构 B: 前后端分离(主进程薄层 + 前端 SPA)
├── 主进程: 窗口管理、IPC、原生 API
├── 渲染层: React/Vue SPA,独立构建产物
├── 主进程改动 → 需要重启
├── 前端改动 → 可以热替换
└── 推荐: electron-updater + Web Bundle 热更新
架构 C: 插件化架构
├── 核心: 稳定的 Electron 壳
├── 插件: 动态加载的功能模块
├── 核心更新 → 全量更新
├── 插件更新 → 独立热加载
└── 推荐: 核心用 electron-updater + 插件用自建更新
更新频率与用户体验的平衡
更新频率 vs 用户感知:
频率过低:
├── bug 修复慢
├── 用户版本碎片化
└── 安全风险高
频率过高:
├── 用户频繁被打扰
├── 更新疲劳 → 用户开始忽略更新
└── 测试不充分 → 引入新 bug
最佳实践:
┌──────────────────────────────────────────┐
│ 类型 │ 频率 │ 方式 │
│──────────────│───────────│───────────────│
│ 安全修复 │ 立即 │ 强制更新 │
│ bug 修复 │ 每周 │ 静默更新 │
│ 新功能 │ 每两周 │ 通知+自愿 │
│ 大版本 │ 每季度 │ 引导更新 │
└──────────────────────────────────────────┘
常见问题
Q1: 能否在不重启应用的情况下更新?
部分可以。渲染层代码(HTML/CSS/JS)可以通过替换文件后刷新页面来更新。但主进程代码(main.js、preload.js)的变更必须重启应用才能生效,因为 Node.js 模块一旦加载就会被缓存。
Q2: 自动更新需要 HTTPS 吗?
强烈建议用 HTTPS。虽然 electron-updater 有 SHA512 校验,但没有 HTTPS 的话,中间人可以替换整个更新包(包括 yml 和安装包),只要它们的 hash 一致。
Q3: 用户拒绝更新怎么办?
策略分级:
- 建议更新:通知用户有新版本,用户可选择稍后
- 推荐更新:每次启动都提醒,但允许跳过
- 强制更新:低于最低版本号时,必须更新才能使用
Q4: 离线用户怎么更新?
方案:
- 提供离线安装包下载链接
- 支持手动导入更新包
- 在有网络时自动检查并缓存更新
Q5: 如何测试更新流程?
- 本地起一个静态文件服务器模拟更新服务
- 使用
electron-updater的 dev 模式 - staging 和 production 使用不同的更新渠道
实践建议
1. 方案选择
大多数项目: electron-updater(足够好,维护少)
需要热更新: electron-updater + asar/web bundle 补丁
大型产品: 自建更新服务 + 灰度 + 回滚 + 监控
2. 更新体验设计原则
- 尽量静默:后台下载,不打扰用户
- 给予控制:让用户选择何时安装
- 快速完成:差分更新减少下载量
- 安全第一:强制的安全更新不可跳过
- 透明沟通:告诉用户更新了什么(changelog)
3. 上线前检查
□ 更新服务器可用且返回正确的 yml
□ 代码签名正确
□ SHA512 hash 匹配
□ 不同版本之间的升级路径测试
□ 更新失败的处理(重试、回滚)
□ 灰度发布机制就绪
□ 监控和报警配置
本章小结
桌面应用的热更新是一个复杂但关键的基础设施:
- 三种策略:全量替换、增量更新、热补丁
- 组合使用:不同类型的变更用不同策略
- electron-updater 是大多数项目的起点
- 安全、回滚、灰度是生产环境的必需品
- 更新体验直接影响用户满意度
接下来几章将逐一深入各种更新方案的实现细节。