Electron實(shí)現(xiàn)在線升級(jí)及功能的熱更新

背景:最近采用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)步驟

  1. 搭建遠(yuǎn)程服務(wù)端系統(tǒng)
    提供系統(tǒng)當(dāng)前最新的版本號(hào)及當(dāng)前版本號(hào)需要更新的文件(html頁面、css文件、圖片、js文件等)
  2. 編寫主進(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
}

附項(xiàng)目demo下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容