背景:最近采用Electron改造公司的ERP客户端,由于界面功能较多,最终打包的文件有130M,更关键的问题是系统的更新比较频繁基本每月都会有一次版本的迭代更新。Electron虽然提供系统自动更新的功能,但采用的是整个系统文件全部替换的方式,如果更新文件较大,频率高,且用户体量较大的情况下采用Electron自带的系统更新功能显然不是很合适。
思路:Electron分为主进程和渲染进程,主进程的主要功能是使用BrowserWindow 实例创建页面,主进程管理所有的web页面和它们对应的渲染进程。可以这么理解无论我们的系统多么复杂、庞大,实际都是由当个单个独立的web页面组成的,每个页面对应着渲染进程。我们系统的功能的更新主要是针对渲染进程,本质上就是更新功能对应的web页面及其页面包含的图片、样式及实现业务功能的js文件。从主进程和渲染进程的用途着手,主进程主要负责调度各个渲染进程打开关闭,我们可以把这部分的功能进行整合转移通过ipcMain消息的方式把业务逻辑的实现转移到渲染进程中,这样我就能保证主进程的代码是不需要更新的。
实现过程:
1. 在主进程实现系统的热更新,访问远程服务端系统版本信息,返回当前系统的最新版本号及当前版本对应的升级文件和升级说明。服务端采用node.js实现,功能很简单实现返回版本信息及静态文件的下载
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("应用实例,访问地址为 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', // 版本号
file:['js/background-bundle.js','page/login.html'],
detail:['更新登陆页面']
};
module.exports = config;
Electron主线程代码:
app.on('ready', async function () {
//获取服务端的版本和本地本部对比,不一致启动热更新
getserverinfo()
})
function getserverinfo() {
settingDB.getItem({ key: 'curversion' }, 'version', (curversion) => { //从nedb数据库获取本地版本
commonfun.getHttpData(autoUpdateUrl, (data) => {
let serverconfig = JSON.parse(data)
if (curversion != serverconfig.version) {
//启动自动更新页面
let updateconfig = {}
if (curversion == '')
curversion = '1.0.0'
Object.assign(updateconfig, { localversion: curversion }, serverconfig)
hotupdatepage.setup(updateconfig) //启动更新的页面,实现文件下载,本地替换(这里启动渲染进程,实现更新的进度的可视化,及更新信息的提示)
settingDB.setItem({ key: 'curversion', value: serverconfig.version }, 'version')
} else {
//启动安全验证界面(系统入口页面,约定不会改变系统入口页面)
//securityvalid.setup()
}
}, () => {
dialog.showMessageBox(null,
{
type: 'error', buttons: ['确定'], title: '系统启动失败', message: '系统启动失败,请联系管理员!'
})
app.exit()
})
})
}
2. 改造主进程,通过ipcMain接收渲染进程发送的消息,实现主进程打开,关闭具体的功能页面
//打开页面
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'
})
let page = {
id: (Math.random() * 1000 | 0) + Date.now(),
browserwin: pagewindow,
name: pageconfig.name
}
pages.push(page) //定义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)
})
})
//根据页面唯一ID关闭页面
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
}
}
})
//根据页面名称关闭页面
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
}
}
})
网友评论