electron的更新通常來講有兩種方式,全量和增量,顧名思義全量就是下載咱們打包好的exe文件或者zip文件,進行全面替換。咱們以前說過electron就是用瀏覽器打開咱們的頁面,不少時候咱們的更新可能只會修改渲染進程,那麼咱們把咱們的渲染進程的文件給替換了不久更新嗎,也就是說增量其實是替換打包好的html,js等文件。那麼更新的方式以下:html
增量更新放到下一期來講,本篇是講全量更新的事,本文旨在對全量更新進行詳細的說明,包括主進程的更新流程和渲染進程展現等以及更新問題如何排查。vue
咱們這裏先說明一下咱們的處理流程:react
全量更新咱們使用的是electron-updater
這個包,進入咱們的項目:nginx
npm i -D electron-updater
electron-updater更新只須要把一個url傳入,而後它會自動查找${url}/xxx.yml
文件,根據這個yml
文件檢測更新及下載,咱們能夠經過其各類事件拿到對應的更新狀態,下載完成以後調用autoUpdater.quitAndInstall()
重啓當前的應用而且安裝更新
這個url下是放靜態資源的,簡單來講就是這個連接相似訪問一個目錄,這個目錄下須要有更新的文件以及檢測更新的文件。
須要哪些東西呢:git
總的來講咱們就是要一個url實現更新,先讓渲染進程把這個url傳過來吧,咱們先模擬一下更新請求。github
通常來講,更新都是調用後端接口,上面說了更新須要一個url,那麼咱們這裏就用json文件模擬請求以及模擬更新文件的地址web
npm i -g http-server 新建一個目錄server http-server ./ -p 4000 // 啓一個4000端口的服務http://127.0.0.1:4000 server下新建一個index.json來當接口返回數據 { "code": 200, "success": true, "data": { "forceUpdate": false, // 是否強制更新 "fullUpdate": true, // 是否全量更新 "upDateUrl": "http://127.0.0.1:4000/", // electron-updater傳入的url "restart": true, // 增量更新是否重啓 "message": "我要升級成0.0.2", // 更新說明 "version": "0.0.2" // 版本號 } } 瀏覽器打開http://localhost:4000/,看是否有index.json。 咱們在渲染進程用請求獲取index.json的數據(這裏是我本身作的api請求封裝) const api = proxy.$api api('http://localhost:4000/index.json', {}, { method: 'get' }).then(res => { console.log(res) })
而後咱們打一個高版本包,這裏是打dev包,修改.env.dev
的VUE_APP_VERSION
爲0.0.2vue-cli
npm run build:dev:win64 mac同理,將打包好的文件放入server目錄 win:latest.yml,0.0.2的exe mac:latest-mac.yml,0.0.2的zip(注意是zip不是dmg)
不出意料的話會報一個跨域的錯誤,畢竟咱們的端口號不一樣,在electron中咱們的權限要比瀏覽器中大得多,
咱們能夠把同源策略關閉,就不會出現跨域的問題了,能夠獲得返回了。npm
BrowserWindow的 webPreferences: { ..... webSecurity: false }
補充:固然還有其餘跨域的處理方式,想了解的話能夠看一下我對跨域的說明連接json
當前咱們的版本號的話,是在.env
裏面設置的,不知道的可看搭建篇環境變量說明。開發時不用檢測版本更新,因此有個判斷。
isClick是自動檢測的話沒有message提示,手動有。
<template> <div class="update"> <div class="version">當前版本爲:{{ config.VUE_APP_VERSION }}</div> <a-button type="primary" @click="upDateClick(true)">檢測更新</a-button> </div> </template> <script> import cfg from '@/config' import update from '@/utils/update' import { defineComponent, getCurrentInstance } from 'vue' export default defineComponent({ setup() { const state = reactive({ visible: false, upDateData: {}, upDateProgress: { show: false, percent: 0 } }) const { proxy } = getCurrentInstance() const config = cfg const api = proxy.$api function upDateClick(isClick) { api('http://localhost:4000/index.json', {}, { method: 'get' }).then(res => { console.log(res) if (cfg.NODE_ENV !== 'development') { update(config.VUE_APP_VERSION, res).then(() => { state.upDateData = res if (res.fullUpdate) { updateNotice(isClick) } else { console.log() } }).catch(err => { if (err.code === 0) { isClick && message.success('已爲最新版本') } }) } else { message.success('請在打包環境下更新') } }) } return { config, upDateClick } } }) </script>
這裏用當前版本與接口版本進行對比,向主進程發送通知(更新message彈窗)。
/utils/update.js export default (version, data) => { return new Promise((resolve, reject) => { const isUpdate = data ? versionCompare(data.version, version) : 0 switch (isUpdate) { case 0: console.log('不更新') reject({ code: isUpdate }) break; case 1: if (data.fullUpdate) { console.log('全量更新') resolve() } else { console.log('增量更新') } break case -1: console.log('降級') reject({ code: isUpdate }) break } }) } // 1爲當前版本比更新版本低,0爲版本一致,-1爲當前版本比更新版本高 function versionCompare(stra, strb) { const straArr = stra.split('.') const strbArr = strb.split('.') const maxLen = Math.max(straArr.length, strbArr.length) let result let sa let sb for (let i = 0; i < maxLen; i++) { sa = ~~straArr[i] sb = ~~strbArr[i] if (sa > sb) { result = 1 } else if (sa < sb) { result = -1 } else { result = 0 } if (result !== 0) { return result } } return result }
檢測到更新後咱們顯示更新彈窗,根據forceUpdate
判斷若是是強制更新的話咱們把彈窗的關閉除去,只能點肯定。若是不是的話,那麼點擊取消後,咱們記錄一個更新版本號的localStorage
,下次再檢測更新時若是有值的話,更新彈窗將不會出現
<a-modal v-model:visible="visible" title="更新" @ok="upDateOk" @cancel="upDateCancel" :closable="!upDateData.forceUpdate" :maskClosable="false" :keyboard="!upDateData.forceUpdate" :destroyOnClose="true" > <p v-html="upDateData.message"></p> <template #footer v-if="upDateData.forceUpdate"> <div class="footer"> <a-button type="primary" @click="upDateOk">肯定</a-button> </div> </template> </a-modal> 調用updateNotice(isClick),這裏是按鈕點擊檢測更新,固然你也能夠程序自動檢測更新,放在App.vue中主動調用updateNotice(false) function updateNotice(isClick) { if (LgetItem(UPDATE_LIST) && LgetItem(UPDATE_LIST)[state.upDateData.version] && !isClick) { // 用戶手動點擊檢測的話出現彈窗 return } state.visible = true } function upDateOk() { state.visible = false window.ipcRenderer.invoke('win-update', {...state.upDateData}) } function upDateCancel() { if (!LgetItem(UPDATE_LIST)) { LsetItem(UPDATE_LIST, {}) } LsetItem(UPDATE_LIST, { ...LgetItem(UPDATE_LIST), [state.upDateData.version]: true }) }
咱們向主進程推送了更新信息win-update
ipcMain.js接收渲染進程的更新信息 import checkUpdate from './checkUpdate' ipcMain.handle('win-update', (_, data) => { checkUpdate(data) })
上面說了electron-updater須要一個url,咱們這裏實現一下其更新邏輯,electron-updater
有各類更新狀態,咱們把這些狀態推送renderer-updateMsg
給渲染進程,讓其有對應的提示或者進度,autoUpdater.quitAndInstall()
被調用後會自動安裝重啓。
新建checkUpdate.js import { autoUpdater } from 'electron-updater' import log from '../config/log.js' import global from '../config/global' autoUpdater.logger = log /** * -1 檢查更新失敗 0 正在檢查更新 1 檢測到新版本,準備下載 2 未檢測到新版本 3 下載中 4 下載完成 **/ function Message(type, data) { const sendData = { type, data } global.sharedObject.win.webContents.send('renderer-updateMsg', sendData) } // 當更新發生錯誤的時候觸發。 autoUpdater.on('error', (err) => { log.info('更新出現錯誤') log.info(err.message) if (err.message.includes('sha512 checksum mismatch')) { Message(-1, 'sha512校驗失敗') } }) // 當開始檢查更新的時候觸發 autoUpdater.on('checking-for-update', () => { log.info('開始檢查更新') Message(0) }) // 發現可更新數據時 autoUpdater.on('update-available', () => { log.info('有更新') Message(1) }) // 沒有可更新數據時 autoUpdater.on('update-not-available', () => { log.info('沒有更新') Message(2) }) // 下載監聽 autoUpdater.on('download-progress', (progressObj) => { Message(3, progressObj) }) // 下載完成 autoUpdater.on('update-downloaded', () => { log.info('下載完成') Message(4) setTimeout(() => { // 重啓更新提示1秒後在進行重啓安裝 global.willQuitApp = true autoUpdater.quitAndInstall() }, 1000) }) export default function (data) { log.info('Update', data) autoUpdater.setFeedURL(data.upDateUrl) autoUpdater.checkForUpdates().catch(err => { log.info('網絡鏈接問題', err) Message(5, err.code) }) }
在上面的更新彈窗中新增更新進度,其實就是接收主進程的更新狀態作個展現:
<a-modal v-model:visible="upDateProgress.show" title="下載中" :closable="false" :maskClosable="false" :keyboard="false" :destroyOnClose="true" :footer="null" > <a-progress :percent="upDateProgress.percent" status="active" /> </a-modal> const message = proxy.$message onMounted(() => { window.ipcRenderer.on('renderer-updateMsg', (_, data) => { switch (data.type) { case -1: message.error(data.data) break case 0: message.info('正在檢查更新') break case 1: message.destroy() message.success('已檢查到新版本,開始下載') state.upDateProgress.show = true break case 2: message.destroy() message.success('無新版本') break case 3: state.upDateProgress.percent = data.data.percent.toFixed(1) break case 4: state.upDateProgress.show = false message.loading('重啓更新中...', 1) break case 5: message.destroy() message.warning(data.data) state.upDateProgress.show = false break default: break } }) }) onUnmounted(() => { window.ipcRenderer.removeListener('renderer-updateMsg') })
開發完成後咱們打個0.0.1版本的dev包(上面咱們放入server文件夾的是0.0.2的dev包),而後檢測更新,更新重啓後看打印的config是不是0.0.2。
經過上面的步驟,咱們的一個全量更新的流程就算完成了,這裏對一些常見問題進行補充。
咱們的請求是一個json文件,咱們可能會修改其中的參數,有時候發現返回仍是沒變,請將控制檯的Network
的Disable cache
勾上(請求緩存常見處理方式)
在開發時咱們可能會作開發調試,好比看下載進度呀什麼的,先打了一個高版本的放在遠程地址,本地重複安裝低版本的看更新效果,可是在第一次更新完成後,後面的更新都是瞬間完成了,看不到進度,這裏是因爲electron-updater
在更新時會檢測本地是否下載過這個高版本,有的話直接用本地的進行安裝,咱們能夠把這個緩存文件刪除掉。AppData這個文件夾呢多是處於隱藏的,後面挺多地方會用到這個的,能夠在頂端查看中勾選隱藏的項目讓其顯示,具體的話百度吧。
electronvuedev這個是你設置的name(本框架在vue.config.js中) win:C:\Users\Administrator(你的用戶)\AppData\Local\electronvuedev-updater mac:~/Library/Application Support/Caches/electronvuedev-updater
有時候咱們更新時會遇到問題,好比檢測出更新,可是下載卻不動,或是更新完成後並無安裝重啓,這裏先說一下比較常見的幾個可能會引起的問題
e.preventDefault()
阻止窗口的關閉,好比咱們在第二期裏使用了:win.on('close', (e) => { console.log('close', global.willQuitApp) if (!global.willQuitApp) { win.webContents.send('renderer-close-tips', { isMac }) e.preventDefault() } })
針對於本項目,咱們在調用重啓更新時,因爲咱們設置了這個可能會引發窗口關閉的失敗,這裏咱們在關閉前設置willQuitApp
,讓其正常關閉:
global.willQuitApp = true autoUpdater.quitAndInstall()
其餘緣由排查:固然不可能只有上面幾種緣由的,那麼咱們如何進行排查呢:
electron-log
這個包把autoUpdater的error收集到本地日誌中:npm i electron-log 新建log.js import log from 'electron-log' log.transports.file.level = 'silly' log.transports.console.level = false // 禁用console輸出 export default log 在咱們的checkUpdate.js更新中使用 import log from '../config/log.js' autoUpdater.logger = log autoUpdater.on('error', (err) => { log.info('更新出現錯誤') log.info(err.message) })
當更新出現問題後查看對應的log文件
win:C:\Users\Administrator(你的用戶)\AppData\Roaming\<app name>\logs mac: ~/Library/Logs/<app name>
本系列更新只有利用週末和下班時間整理,比較多的內容的話更新會比較慢,但願能對你有所幫助,請多多star或點贊收藏支持一下