背景:最近采用Electron改造公司的ERP客戶端,由于界面功能較多,最終打包的文件有130M,更關(guān)鍵的問題是系統(tǒng)的更新比較頻繁基本每月都會(huì)有一次版本的迭代更新。Electron雖然提供系統(tǒng)自動(dòng)更新的功能,但采用的是整個(gè)系統(tǒng)文件全部替換的方式,如果更新文件較大,頻率高,且用戶體量較大的情況下采用Electron自帶的系統(tǒng)更新功能顯然不是很合適。
思路:Electron分為主進(jìn)程和渲染進(jìn)程,主進(jìn)程的功能主要是使用BrowserWindow 實(shí)例創(chuàng)建頁面,主進(jìn)程管理所有的web頁面和它們對(duì)應(yīng)的渲染進(jìn)程。可以這么理解無論我們的系統(tǒng)多么復(fù)雜、龐大,實(shí)際都是由單個(gè)獨(dú)立的web頁面組成的,每個(gè)頁面對(duì)應(yīng)著渲染進(jìn)程。我們系統(tǒng)的功能的更新主要是針對(duì)渲染進(jìn)程,本質(zhì)上就是更新功能對(duì)應(yīng)的web頁面及其頁面包含的圖片、樣式及實(shí)現(xiàn)業(yè)務(wù)功能的js文件(這些文件都是可以從服務(wù)端遠(yuǎn)程下載到本地運(yùn)行的)。從主進(jìn)程和渲染進(jìn)程的用途著手,主進(jìn)程主要負(fù)責(zé)調(diào)度各個(gè)渲染進(jìn)程打開、關(guān)閉、銷毀,渲染進(jìn)程負(fù)責(zé)各個(gè)功能的業(yè)務(wù)邏輯的實(shí)現(xiàn),主進(jìn)程和渲染進(jìn)程之間通過ipcMain消息的方式進(jìn)行相互通訊。我們的前提是主進(jìn)程不更新,所以需要把主進(jìn)程的功能簡化或轉(zhuǎn)移到渲染進(jìn)程中實(shí)現(xiàn),主進(jìn)程通過ipcMain消息的方式接受渲染進(jìn)程發(fā)送的消息去實(shí)現(xiàn)頁面的打開、關(guān)閉、系統(tǒng)退出等功能。
實(shí)現(xiàn)步驟
- 搭建遠(yuǎn)程服務(wù)端系統(tǒng)
提供系統(tǒng)當(dāng)前最新的版本號(hào)及當(dāng)前版本號(hào)需要更新的文件(html頁面、css文件、圖片、js文件等) - 編寫主進(jìn)程
- 系統(tǒng)啟動(dòng)時(shí),獲取本地系統(tǒng)的版本信息,項(xiàng)目使用了electron-json-storage存儲(chǔ)本地的版本信息。
- 訪問遠(yuǎn)程服務(wù)器,獲取當(dāng)前最新版本記錄,同本地版本進(jìn)行對(duì)比,不一致采用強(qiáng)制更新,啟動(dòng)更新頁面顯示更新進(jìn)度及更新日志,系統(tǒng)自動(dòng)下載更新的文件,保存并替換到本地的文件。
- 啟動(dòng)系統(tǒng)入口頁面、安全登錄驗(yàn)證頁面。
- 處理ipcMain消息,實(shí)現(xiàn)頁面的打開、關(guān)閉、系統(tǒng)退出等功能。
部分代碼:
- 服務(wù)端功能簡單,用nodejs實(shí)現(xiàn)文件下載,獲取當(dāng)前版本號(hào)功能
var express = require('express');
var app = express();
app.use(express.static('resource'));
var server = app.listen(92, function () {
var host = server.address().address
var port = server.address().port
console.log("應(yīng)用實(shí)例,訪問地址為 http://%s:%s", host, port)
})
app.get('/', function (req, res) {
var config = require('./config');
var json = JSON.stringify(config)
res.setHeader("Content-Type", "application/json");
res.write(json);
res.end();
})
配置文件:
var config = {
version: '1.0.0.3', // 版本號(hào)
file:['js/background-bundle.js','page/login.html'],
detail:['更新登陸頁面']
};
module.exports = config;
- Electron主線程代碼:
app.on('ready', async function () {
getserverinfo()
})
//獲取服務(wù)端的版本和本地本部對(duì)比,不一致啟動(dòng)熱更新
function getserverinfo() {
settingDB.getItem('version', (result) => {
let curversion = ''
if (result.version) {
curversion = result.version
}
commonfun.getHttpData(global.backgroundparam.autoUpdateUrl, (data) => {
let serverconfig = JSON.parse(data)
global.backgroundparam.serverurl = serverconfig.serverurl
//curversion = '1.0.0'
if (curversion != serverconfig.version) {
//啟動(dòng)自動(dòng)更新頁面
let updateconfig = {}
if (curversion == '')
curversion = '1.0.0'
Object.assign(updateconfig, {localversion: curversion}, serverconfig)
hotupdatepage.setup(updateconfig)
settingDB.setItem('version', {version: serverconfig.version})
} else {
//啟動(dòng)安全驗(yàn)證界面
securityvalid.setup()
}
}, () => {
dialog.showMessageBox(null,
{
type: 'error', buttons: ['確定'], title: '系統(tǒng)啟動(dòng)失敗', message: '系統(tǒng)啟動(dòng)失敗,請(qǐng)聯(lián)系管理員!'
})
app.exit()
})
})
}
//根據(jù)配置打開新頁面
ipcMain.on('openpage', ({sender}, pageconfig) => {
let {x, y, width, height} = pageconfig
let pagewindow = new BrowserWindow({
titleBarStyle: 'hiddenInset',
autoHideMenuBar: true,
fullscreenable: false,
frame: false,
x,
y,
width,
height,
defaultEncoding: 'UTF-8',
webPreferences: {
nodeIntegration: true,
webviewTag: true
}
})
let page = {
id: (Math.random() * 1000 | 0) + Date.now(),
browserwin: pagewindow,
name: pageconfig.name
}
pages.push(page)
pagewindow.loadURL(`file://${global.backgroundparam.rootpath}/page/${pageconfig.path}`)
//pagewindow.webContents.openDevTools()
pagewindow.show()
pagewindow.webContents.on('did-finish-load', () => {
pagewindow.webContents.send('receivepageID', page.id)
})
})
//根據(jù)ID關(guān)閉頁面
ipcMain.on('closepagebyself', ({sender}, arg) => {
for (var i = 0; i < pages.length; i++) {
if (pages[i].id === arg) {
pages[i].browserwin.close()
pages.splice(i, 1)
return
}
}
})
//根據(jù)名稱關(guān)閉頁面
ipcMain.on('closepagebypagename', ({sender}, arg) => {
for (var i = 0; i < pages.length; i++) {
if (pages[i].name == arg) {
pages[i].browserwin.close()
pages.splice(i, 1)
return
}
}
})
//中轉(zhuǎn)主頁面消息命令
ipcMain.on('mainpage:command', ({sender}, commandtype, paramobj) => {
let mainpagewin = getMainPageWin()
if (mainpagewin != null) {
mainpagewin.webContents.send('command', commandtype, paramobj)
}
})
//退出系統(tǒng)
ipcMain.on('existapp', function () {
app.quit()
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') {
app.quit()
}
})
//獲取操作主頁面
function getMainPageWin() {
for (var i = 0; i < pages.length; i++) {
if (pages[i].name == 'mainpage') {
return pages[i].browserwin
}
}
return null
}