流程
![](https://img-blog.csdnimg.cn/0e927d13a153420ab54e083168b9b894.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5riQ5aKo5rex,size_14,color_FFFFFF,t_70,g_se,x_16)
1.设置feedUrl
- 每次登录应用后更新feedUrl,如果需要灰度测试。可以添加uuid的参数,由服务端判断是否命中
注意:feedUrl需要在checkUpdate之前调用
// 主进程
// 初始化
private cancellationToken?: CancellationToken
private isForceUpdate = false
private updating = false
autoUpdater.logger = log
autoUpdater.autoDownload = false
autoUpdater.autoInstallOnAppQuit = true
autoUpdater.retryOptions = {
max: 15,
interval: 3000,
}
const { productId } = environment
// 登录后更新FeedUrl
ipcMain.on(UpdateChannel.setFeedUrl, (event: any) => {
autoUpdater.setFeedURL(
`${environment.autoUpdaterUrl}?_productId=${productId}&version=${
environment.appVersion
}`,
)
})
//渲染进程
/**
* hooks中设置检查更新的连接
*/
const setFeedUrl = useCallback((userId?: number) => {
ipcRenderer.send(UpdateChannel.setFeedUrl, userId)
}, [])
// app中调用setFeedUrl
2.检查更新
- 渲染进程调用checkUpDate方法检查更新,主进程中监听更新消息
- 可以传入userAction参数,判断是否由用户主动检查更新
// 渲染进程
// app中调用checkUpdate(),注意在seetFeedUrl之后
/**
* 检查更新
*/
const checkUpdate = useCallback((userAction = false) => {
ipcRenderer.send(UpdateChannel.CheckUpdate, userAction)
}, [])
// 主进程
ipcMain.on(UpdateChannel.CheckUpdate, (event, userAction = false) => {
// 本地开发不执行更新操作
const serve = process.defaultApp ?? false
if (serve) {
return
}
this.update(userAction).then((updateInfo) => {
if (updateInfo) {
event.sender.send(
UpdateChannel.CheckResult,
updateInfo,
this.isForceUpdate,
)
} else if (userAction) {
event.sender.send(UpdateChannel.CheckResult, null, this.isForceUpdate)
}
})
})
3.监听检查结果
// 渲染进程
/**
* 下载进程监听检查更新的结果,如果有更新信息则弹出更新信息弹窗
* 如果是用户主动出发检查更新,则提示当前已经是最新版本
*/
ipcRenderer.on(
UpdateChannel.CheckResult,
(event: any, updateInfo: UpdateInfo, force: boolean) => {
// 服务端判断需要更新才给出更新提示
// 看接口返回逻辑是需要更新就返回新的信息,如果只是返回线上版本,则需要自己跟本地版本大小,判断是否需要更新
if (updateInfo) {
versionInfoDialog(updateInfo, force)
} else {
message.info('当前已是最新版本,无需更新', 1500)
}
},
)
/**
* 展示更新信息弹窗,如果是强制更新,则只能退出和更新
* 点击立即更新则开始下载
*/
const versionInfoDialog = useCallback(
(updateInfo: UpdateInfo, force: boolean) => {
setForceUpdate(force)
const { version: ignoreVersion } = getItem(
UpdateStorage.IGNORE_VERSION_KEY,
) || { version: '0.0.0' }
const { time } = getItem(UpdateStorage.LAST_LATER_TIME) || { time: 0 }
// NOTE: 强升不可跳过升级,每次打开都提醒,非强制升级和已经忽略过的版本不弹窗
if (
!force
&& updateInfo.version
&& ignoreVersion !== '0.0.0'
&& versionCompareLarger(updateInfo.version, ignoreVersion)
&& getZeroTimestamp(Date.now().valueOf()) - getZeroTimestamp(time) < BREAK_TIME
) {
return
}
// 记录更新版本,下载弹窗中展示
setUpdateVersion(String(updateInfo.version))
// 记录已展示的更新提示的版本和提示时间,每天提醒一次
setItem(
UpdateStorage.IGNORE_VERSION_KEY,
{
version: updateInfo.version,
},
)
setItem(UpdateStorage.LAST_LATER_TIME, {
time: Date.now(),
})
// 弹窗提示更新,展示版本信息和更新内容点击确认调用startDownload(),开始下载更新
//点击取消如果是强制更新则退出应用,如果不是则关闭弹窗
// if (force) {
// exitApp()
// } else {
// closePopupConfirm()
// }
},
[
// 依赖
],
)
4.下载更新
- 渲染进程点击开始更新,主进程监听开始下载
- 下载之前再次检查更新信息
- 渲染进程监听下载进度变化,及时更新
// 渲染进程
/**
* 开始下载,保存下载时间
*/
const startDownload = useCallback(
() => {
showProgressBar()
ipcRenderer.send(UpdateChannel.StartDownload)
},
[showProgressBar],
)
/**
* 渲染进程监听下载进度,更新进度条
*/
ipcRenderer.on(UpdateChannel.Progress, (event: any, value: number) => {
setProgressValue(Math.floor(value))
})
/**
* 渲染进程监听更新错误信息,给出error dialog
*/
ipcRenderer.on(UpdateChannel.Error, () => {
errorDialog()
})
//主进程
/**
* 监听渲染进程的开始下载事件
*/
ipcMain.on(UpdateChannel.StartDownload, (event) => {
this.download(event)
})
// 下载方法
public download(event: IpcMainEvent) {
this.updating = true
const downloadProgress = (info: any) => {
// 更新下载进度
event.sender.send(UpdateChannel.Progress, info.percent)
}
// 下载出错
const downloadError = (e: unknown) => {
this.updating = false
this.cancellationToken?.cancel()
autoUpdater.off('download-progress', downloadProgress)
autoUpdater.off('network-error', downloadError)
event.sender.send(UpdateChannel.Error)
log.error(e)
}
// 下载完成
const downloadFinish = () => {
this.updating = false
autoUpdater.off('download-progress', downloadProgress)
autoUpdater.off('network-error', downloadError)
event.sender.send(UpdateChannel.Finish)
}
autoUpdater.once('update-downloaded', downloadFinish)
autoUpdater.on('download-progress', downloadProgress)
autoUpdater.on('network-error', downloadError)
autoUpdater.on('error', downloadError)
process.once('uncaughtException', (e) => {
if (this.isNetworkError(e)) {
downloadError(e)
} else {
throw e
}
})
// 检查更新信息
this.update()
.then((updateInfo) => {
if (updateInfo) {
autoUpdater
.downloadUpdate(this.cancellationToken)
.catch(downloadError)
}
})
.catch((e) => {
log.error(e)
})
}
public update(userAction = false) {
this.isForceUpdate = false
return autoUpdater.checkForUpdates().then((result) => {
// 拿到更新信息
const { updateInfo, cancellationToken } = result
// 判断是否有可用更新,当更新版本大于当前版本时,则有可用更新
const isUpdateAvailable = autoUpdater.currentVersion.compare(updateInfo.version) < 0
// 判断是否需要强制更新,当最小要求版本大于当前版本时,则需要强制更新
// const isForceUpdateAvailable =
// autoUpdater.currentVersion.compare(updateInfo.minimumVersion) < 0;
// 当更新可用时,拿到取消更新的cancellationToken,可用于在下载过程中,中断下载
this.cancellationToken = cancellationToken
if (updateInfo.forceUpdate) {
this.isForceUpdate = true
return updateInfo
}
if (!updateInfo.forceUpdate && isUpdateAvailable) {
return updateInfo
}
if (userAction) {
return undefined
}
return undefined
})
}
public isNetworkError(errorObject: Error) {
return (
errorObject.message === 'net::ERR_INTERNET_DISCONNECTED'
|| errorObject.message === 'net::ERR_PROXY_CONNECTION_FAILED'
|| errorObject.message === 'net::ERR_CONNECTION_RESET'
|| errorObject.message === 'net::ERR_CONNECTION_CLOSE'
|| errorObject.message === 'net::ERR_NAME_NOT_RESOLVED'
|| errorObject.message === 'net::ERR_CONNECTION_TIMED_OUT'
|| errorObject.message === 'net::ERR_NETWORK_CHANGED'
)
}
5.下载完毕
- 下载完毕后执行quitAndInstall(),windows下载的是exe,如果打包方式为portable则点击安装后会直接安装完毕,如果打包方式为nsis,则需要重新走一遍安装流程
// 渲染进程
/**
* 关闭并安装
*/
const quitAndInstall = useCallback(() => {
ipcRenderer.send(UpdateChannel.Install)
}, [])
// 主进程
/**
* 监听渲染进程的安装事件
*/
ipcMain.on(UpdateChannel.Install, () => {
autoUpdater.quitAndInstall()
})
流程处理
- 在下载中途理论上不能关闭下载流程,如果用户需要关闭,需要中断下载流程
// 渲染进程
const cancelDownload = useCallback(() => {
ipcRenderer.send(UpdateChannel.CancelDownload)
}, [])
// 主进称
public cancelDownloading() {
this.updating = false
this.cancellationToken?.cancel()
}