electron-updater实现electron全量更新和增量更新——主进程部分

news2024/9/21 23:39:49

同学们可以私信我加入学习群!


正文开始

  • 前言
  • 更新功能所有文章汇总
  • 一、更新插件选择
  • 二、在main.js中引入我们的更新模块
  • 三、更新模块UpdateController.js暴露的方法checkUpdate
  • 四、更新模块UpdateController.js中的监听
    • 4.1监听是否有新版本需要更新?
    • 4.2 监听更新时的下载信息
    • 4.3 监听下载完成
  • 五、完整的更新逻辑
  • 六、优化后的监听
  • 6.1新增检查更新的监听
    • 6.2 新增执行更新的监听
    • 6.3 新增安装的监听
    • 6.4优化第四节的几个更新
  • 总结
  • 附件


前言

最近好久不更文,一是公司的事确实很忙,二是把时间都用在开发工具上了,写文总是提不起兴趣。

好消息是工具总算是憋出来几个,坏消息功能怎么实现的,代码快忘差不多了。我经常看着我写过的代码一脸茫然:这真的是我写的???

软件目前功能汇总:svga预览、node版本管理、前端部署-nginx管理、webstorm破解、浏览器插件等。
在这里插入图片描述
围绕这些功能,可能会重新开一个文章系列——pc工具源码系列,详细讲解它们都是怎么实现的。但是对它们的讲解放在这里不太合适,因为本系列主要讲解的是electron的基础技能,demo只是辅助。

唠了这么多,其实本文重点要讲解的内容是——electron更新。

更新功能所有文章汇总

  1. electron-updater实现electron全量更新和增量更新——主进程部分
  2. electron-updater实现electron全量更新和增量更新——注意事项/技巧汇总
  3. electron-updater实现electron全量更新和增量更新——渲染进程UI部分
  4. electron-updater实现electron全量更新和增量更新——渲染进程交互部分

一、更新插件选择

官网给了简单的更新api:autoUpdater,可以自行查看。
在这里插入图片描述
本文采取的是更新插件:electron-updater。

选择它的理由也很简单,因为它是electron-builder打包工具推荐的更新插件,看过前面文章的同学应该知道,我的项目打包工具都是基于electron-builder。

所以选择electron-updater不仅可以和electron-builder更契合,也会有便捷的增量更新功能。简单好用,就是理由。

二、在main.js中引入我们的更新模块

能接触到更新这一步的同学,手里的项目肯定是已经存在main.js等文件。

如果把所有的逻辑都放到main.js中,最终main.js会过于臃肿。为了更好地组织代码,我们需要把更新部分的功能放到一个独立的文件中:UpdateController.js

在main.js中引入这个更新功能模块,代码可能如下:

//引入更新功能
const checkUpdate = require('./controller/UpdateController');

app.whenReady().then(() => {
    let win = new getWindow().createWindow() //创建窗口

    const ipcSend=require('./ipc/ipc-send')
    ipcSend.init(win)  //监听渲染进程
    checkUpdate(win);  //检查更新
    new getMenuPersonal(win).createMenu()  //创建工具栏
    // 注册快捷键监听器
    getGlobalShortcut.create(win)
})

上面和更新相关的代码主要是两行:

const checkUpdate = require('./controller/UpdateController');
checkUpdate(win);  //检查更新

checkUpdate 是UpdateController中暴露的方法,接收一个window对象,这个window对象用于主进程向渲染进程主动通信时使用:mainWin.webContents.send,后文会涉及。

三、更新模块UpdateController.js暴露的方法checkUpdate

首先通过npm下载electron-updater:

npm i electron-updater

然后再更新模块中引用electron-updater

const { autoUpdater } = require('electron-updater');

上一节把更新模块放到main.js中时,我们提到过更新模块暴露的方法checkUpdate,下面我们来看一下它的具体实现:

let mainWin = null;
const checkUpdate = (win) => {
    mainWin = win;

    if(app.isPackaged){
        autoUpdater.setFeedURL('http://xxxxx:8888/updater/')
    }else{
        autoUpdater.setFeedURL('http://localhost:8888/updater/')
    }
    autoUpdater.forceDevUpdateConfig = true //开发环境下强制更新
    autoUpdater.autoDownload = false; // 自动下载
    autoUpdater.autoInstallOnAppQuit = true; // 应用退出后自动安装
};

可以看到我的checkUpdate实现十分简单,它主要的作用就是操作autoUpdater对象完成一些基础配置。

  • 我们在方法体外定义了一个mainWin全局变量,checkUpdate方法中,首先为mainWin变量赋值从main.js中传来的window对象。
  • autoUpdater.setFeedURL是设置更新的远程地址,设置的地址下应当能直接看到我们的exe文件,electron-updater插件会自动从这个远程地址下,获取最新安装包。
  • 下面是autoUpdater的一些配置,重点注意autoDownload要设置为false,不要在检测到更新时,自动下载,对用户体验不好。我们应该能让用户控制下载、跳过下载等操作。

我不喜欢把和下载相关的所有操作一股脑都放到checkUpdate方法中,尤其是一些监听。checkUpdate的职责应该单一而纯粹,只是在做一些autoUpdater对象的基础配置。

如果你不喜欢分层分类地去构建代码,那把和更新相关的监听都放到这个方法,也是可行的。

四、更新模块UpdateController.js中的监听

更新模块的职责其实可以很简单,它可以分为两个部分,就完成最基础的更新功能:

  1. 一是对autoUpdater对象的配置,让更新插件知道以什么效果去执行更新。这部分工作上面已经做了。
  2. 二是监听更新全生命周期,让electron知道更新进行到哪一步了,都需要做什么操作。

这就是最简单的一个更新功能。这节内容,就是要监听更新的全生命周期。

4.1监听是否有新版本需要更新?

autoUpdater.on('update-available', (info) => {
    console.log('有新版本需要更新',info);
    //这里可以写个主进程到渲染进程的通信,主动告诉渲染进程;
    //因为我实际项目中的逻辑要略复杂,所以这里先省略
});
autoUpdater.on('update-not-available', (info) => {
    console.log('无需更新');
    //业务代码
});

4.2 监听更新时的下载信息

autoUpdater.on('download-progress', (prog) => {
    let speed=prog.bytesPerSecond / 1000000>1?Math.ceil(prog.bytesPerSecond / 1000000)+'M/s':Math.ceil(prog.bytesPerSecond / 1000)+'K/s'

    mainWin.webContents.send('pc-update-progress',  {
        speed, // 网速
        percent: Math.ceil(prog.percent), // 百分比
    });

});

prog参数里,有更新过程中所有的信息,我们可以根据里面信息来计算我们需要的参数。网速就是个估算值,较真你就输了,但是百分比必须要准,不能学某些软件,前面百分之99用时1秒钟,最后百分之1用时1小时。

4.3 监听下载完成

autoUpdater.on('update-downloaded', (info) => {
    isDownloading=false
    mainWin.webContents.send('pc-downloaded');  //告诉页面,更新完成了
    // 下载完成后强制用户安装,不推荐
    // autoUpdater.quitAndInstall();
});

监听的最简代码至此就完成了。

五、完整的更新逻辑

如果按照上面的代码照搬,大概率是不会触发更新的,因为里面还缺少了一个关键的触发方法:autoUpdater.checkForUpdatesAndNotify()。这个方法是检查更新的api,只有调用它,才会触发后续一系列监听。

大部分文章都把这个方法放到checkUpdate方法中,意味着当electron主进程加载时,就会调用checkUpdate方法,此时就会同步检查更新,并触发对应的监听方法。

可这样合理吗?会不会更新逻辑运行完,向渲染进程通信了消息,但是渲染进程还未结束,导致显示出现异常?可能会有人在checkUpdate方法执行的地方增加setTimeout,以确保更新的逻辑都正常运行。但是当项目变大,逻辑变复杂后,写setTimeout强行异步的方式,就是混乱之源。

我们正常的更新逻辑,不应该是主进程加载后,就检查更新,而应该是页面加载后,检查更新,并获取更新模块反馈的信息。

因为你不知道是主进程更新逻辑运行得快,还是页面渲染得快。即使在某些电脑上,主进程更新逻辑运行速度优于页面渲染速度,最终表现正常,也无法保证在不同性能电脑上都能表现一致。

所以现在的完整逻辑就是:

第一步:页面渲染完毕,并询问主进程,是否有更新?
第二步:主进程检查更新,并反馈给页面,有更新/无更新。
第三步:如果无更新,页面直接显示提示信息。如果有更新,页面产生交互逻辑,将决定权交给用户,用户决定是否更新。
第四步:用户点击更新,页面将指令发送给主进程,主进程开始执行更新。

六、优化后的监听

6.1新增检查更新的监听

经过优化后,我们需要设计有用户交互逻辑的更新功能,第一步就是要监听页面渲染完毕后,询问主进程是否有更新,并把结果反馈给页面,这是一个双向通信。

很多监听都会给页面反馈消息,所以我们创建一个反馈信息的全局变量:judgeRs。

用户可能会刷新页面,这时页面会重新渲染,重新发送检查更新的信息,如果不加控制,就会出现重复的更新下载,所以我们创建一个控制是否检查更新的全局变量:isDownloading。

let judgeRs={}
let isDownloading=false

ipcMain.handle('check-pc-update',async ()=>{
    try {
        if(isDownloading){
            return {
                success:true,
                isDownloading:true,
                msg:'正在下载中,请稍后'
            }
        }else{
            const res= await autoUpdater.checkForUpdatesAndNotify()
            console.log('judge',res)
            //如果check结果正常,则使用上面监听构造的judgeRs
            return judgeRs
        }

    }catch (e){
    //    check报错
        judgeRs = {
            success: false,
            msg: '没有更新包:博主财力有限,服务器被下架了,软件最新版本,请通过"中二少年工具箱"小程序,查询网盘下载地址'
        }
        return judgeRs
    }
})

6.2 新增执行更新的监听

通过上面优化后的更新逻辑,我们知道,更新操作不再是自动进行,而是由用户点击按钮操作的。所以要监听用户的操作,并触发更新。这是由渲染进程到主进程的单向通信。

/*监听渲染进程指令,执行更新*/
ipcMain.on('send-update', () => {
    autoUpdater.autoDownload = true;
    autoUpdater.checkForUpdates();
})

注意autoUpdater.autoDownload = true;这就是在checkUpdate方法中,为什么要把autoDownload默认设置成false,因为如果默认是true,就无法实现由用户控制更新。在’check-pc-update’监听中,执行检查更新autoUpdater.checkForUpdatesAndNotify()方法时,就会自动更新下载安装包。我们就是通过autoDownload 属性的开闭,来实现是否下载的控制。

6.3 新增安装的监听

更新下载完毕后,是否立即安装,也应该由用户控制。

// 监听渲染进程的 install 事件,触发退出应用并安装
ipcMain.handle('pc-install', () => autoUpdater.quitAndInstall());

6.4优化第四节的几个更新

在本节中,增加了两个全局变量judgeRs、isDownloading。

judgeRs在是否有更新的监听中,可以赋值,如下:

autoUpdater.on('update-available', (info) => {
    console.log('有新版本需要更新',info);
    judgeRs={
        success:true,
        needUpdate:true,
        msg:'有新版本需要更新',
        version:info.version
    }
});
autoUpdater.on('update-not-available', (info) => {
    console.log('无需更新');
    judgeRs={
        success:true,
        needUpdate:false,
        msg:'无需更新'
    }
});

当监听到正在下载资源时,可以把isDownloading赋值为true:

autoUpdater.on('download-progress', (prog) => {
    let speed=prog.bytesPerSecond / 1000000>1?Math.ceil(prog.bytesPerSecond / 1000000)+'M/s':Math.ceil(prog.bytesPerSecond / 1000)+'K/s'

    mainWin.webContents.send('pc-update-progress',  {
        speed, // 网速
        percent: Math.ceil(prog.percent), // 百分比
    });
    isDownloading=true

});

至此,主进程所有的更新操作就完成了。后续还可以增加强制更新、回退版本等各种功能。


总结

大家如果需要联系博主,或者获取博主各系列文章对应的资源,可以通过私信博主来获取。

有任何前端项目、demo、教程需求,都可以联系博主,博主会视精力更新,免费的羊毛,不薅白不薅!~

附件

更新模块UpdateController完整的代码参考:

const { autoUpdater } = require('electron-updater');
const {ipcMain,app} = require('electron')

let mainWin = null;
let judgeRs={}
let isDownloading=false
const checkUpdate = (win) => {
    mainWin = win;

    if(app.isPackaged){
        autoUpdater.setFeedURL('http://lizetoolbox.top:83/updater/lize-tools-pc')
    }else{
        autoUpdater.setFeedURL('http://localhost:83/updater/lize-tools-pc/')
    }
    autoUpdater.forceDevUpdateConfig = true //开发环境下强制更新
    autoUpdater.autoDownload = false; // 自动下载
    autoUpdater.autoInstallOnAppQuit = true; // 应用退出后自动安装
};

autoUpdater.on('update-available', (info) => {
    console.log('有新版本需要更新',info);
    judgeRs={
        success:true,
        needUpdate:true,
        msg:'有新版本需要更新',
        version:info.version
    }
});
autoUpdater.on('update-not-available', (info) => {
    console.log('无需更新');
    judgeRs={
        success:true,
        needUpdate:false,
        msg:'无需更新'
    }
});

// 监听渲染进程的 install 事件,触发退出应用并安装
ipcMain.handle('pc-install', () => autoUpdater.quitAndInstall());

ipcMain.handle('check-pc-update',async ()=>{
    try {
        if(isDownloading){
            return {
                success:true,
                isDownloading:true,
                msg:'正在下载中,请稍后'
            }
        }else{
            const res= await autoUpdater.checkForUpdatesAndNotify()
            console.log('judge',res)
            //如果check结果正常,则使用上面监听构造的judgeRs
            return judgeRs
        }

    }catch (e){
    //    check报错
        judgeRs = {
            success: false,
            msg: '没有更新包:博主财力有限,服务器被下架了,软件最新版本,请通过"中二少年工具箱"小程序,查询网盘下载地址'
        }
        return judgeRs
    }
})
autoUpdater.on('download-progress', (prog) => {
    let speed=prog.bytesPerSecond / 1000000>1?Math.ceil(prog.bytesPerSecond / 1000000)+'M/s':Math.ceil(prog.bytesPerSecond / 1000)+'K/s'

    mainWin.webContents.send('pc-update-progress',  {
        speed, // 网速
        percent: Math.ceil(prog.percent), // 百分比
    });
    isDownloading=true

});
autoUpdater.on('update-downloaded', (info) => {
    isDownloading=false
    mainWin.webContents.send('pc-downloaded');
    // 下载完成后强制用户安装,不推荐
    // autoUpdater.quitAndInstall();
});
/*监听渲染进程指令,执行更新*/
ipcMain.on('send-update', () => {
    autoUpdater.autoDownload = true;
    autoUpdater.checkForUpdates();
})
module.exports = checkUpdate;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1977027.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

红黑树与平衡二叉树的相同之处与不同之处

红黑树很多资料上写的非常繁杂,初次接触真的难以理解。写本文也就是为了记录一些思考和想法,并不会记录如何使用代码实现。 不记录代码还有个原因:黑红树的算法就是根据各种情况进行一些操作,情况很复杂,分插入的和删…

数据结构 二叉树和堆总结

树 概念 树是一种层次结构非线性的数据结构,其是由节点和边组成,可以用来表示层次关系的数据。 树的相关概念 节点:树的基本组成单位,每个节点都包含数据,同时与其他节点相互连接根节点:树的顶层节点&…

SpringBoot_第十一章(Thymeleaf模板引擎)

目录 1:什么是Thymeleaf模板引擎 2:springboot怎使用Thymeleaf 2.1:导入pom文件 2.2:查看ThymeleafAutoConfiguration 3:Thymeleaf核心语法 4:使用Thymeleaf 5:具体语法练习 1&#xff1a…

数据集划分方法

数据集划分是机器学习和数据科学中的一个重要步骤,主要目的是为了确保模型的有效性和可靠性。 留出法(简单交叉验证) 将数据集划分为互斥的子集:训练集和测试集。 训练集: 用于训练模型。 测试集: 用于评估模型的性能和验证其准确…

图神经网络揭秘:视觉和实用指南

目录 一、说明 二、图如何网络化? 三、你需要知道的 3.1 进入图神经网络 3.2 消息传递 3.3 我们如何处理最终的向量表示? 四、图神经网络,总结 4.1 为什么选择图形神经网络? 4.2 简而言之 一、说明 了解图神经网络的世界&#xff…

C#中投影运算的深入解析与实例应用

文章目录 1、投影运算的基本语法2、投影运算的高级用法3、投影运算在向量空间中的运用4、投影运算在数据库和XML中的实际应用5、投影运算能用于哪些实际场景?6、结论 在C#编程中,投影运算是一种常用的数据操作技术,它可以将一个数据集合转换成…

开放式耳机推荐?时尚潮流品牌:悠律ringbud pro开放式耳机实测测评

作为一位音乐发烧友,什么类型的耳机都体验过,有些几百上千的耳机音质还是差点意思,还是会有听久了感觉不舒服的情况,低音量感不够的问题,直到用了悠律ringbud pro开放式耳机,才算真正打开新世界的大门&…

C语言程序设计-[2] 数据类型、常量和变量

1、数据类型 C语言支持的数据类型如下: 2、常量 常量就是不同数据类型下的值。这里主要讲整型、实型和字符型常量。 (1)整型常量:用十进制、八进制和十六进制三种形式表示。 (1)实型常量:由整…

HCIP实验-MGRE

实验拓扑: 实验要求: 1.R2为ISP,其上只能配置IP地址 2.R1-R2之间为HDLC封装 3.R2-R3之间为PPP封装,pap认证,R2为主认证方 4.R2-R4之间为PPP分装,chap认证,R2为主认证方 5.R1、R3、R4构建MG…

unity拖拽物品遇到的bug及解决思路

记录一下拖拽实现过程中遇到的bug RectTransform 专门用在UI中transform 判断点击是否在UI中 使用这个函数就可以判断点击的是否是UI面板,返回true表明在UI面板中 EventSystem.current.IsPointerOverGameObject()值得一提的是,如果发现了有UI穿透效…

【C语言】分支与循环(分支篇)

前言 C语言是一种结构化的计算机语言,这里指的通常是顺序结构、选择结构、循环结构,掌握这三种结构之后我们就可以解决大多数问题。 分支结构可以使用if、switch来实现,而循环可以使用for、while、do while来实现。 1. if语句 1.1 if if…

【滴水三期】32/64位——PE文件节表打印与解析

【作业内容】 1、手动查&#xff0c;画个PE文件图。 2、编写程序打印节表中的信息。 3、根据节表中的信息&#xff0c;到文件中找到所有的节&#xff0c;观察节的开始位置与大小是否与节表中的描述一致 【PE file_buffer文件图】 【IMAGE_SECTION_HEADER解析】 <winNT.h…

web浏览器播放rtsp视频流,海康监控API

概述 这里记录一下如何让前端播放rtsp协议的视频流 ​ 项目中调用海康API&#xff0c;生成的视频流(hls、ws、rtmp等)通过PotPlayer播放器都无法播放&#xff0c;说明视频流有问题&#xff0c;唯独rtsp视频流可以播放。 但是浏览器本身是无法播放rtsp视频的&#xff0c;即使…

Qt3D给圆环等立体图形添加纹理图片

添加纹理图片&#xff0c;首先需要自己找一个纹理图&#xff0c;当然了&#xff0c;随便什么图片都行。 创建3D图形的主要步骤查看另一篇文章。 这里主要代码如下&#xff1a; 使用QTextureLoader加载图片&#xff0c;图片路径需为qrc:/的路径。 auto *planeTransform1 ne…

PyMongo

什么是PyMongo PyMongo 是一个 Python 库&#xff0c;用于与 MongoDB 数据库进行交互。MongoDB 是一个基于文档的 NoSQL 数据库&#xff0c;提供高性能、可扩展性和灵活的架构。PyMongo 提供了一套工具&#xff0c;使得在 Python 程序中操作 MongoDB 变得简单和高效。 安装PyMo…

【C++程序设计】——利用数组处理批量数据(二)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-削好皮的Pineapple! &#x1f468;‍&#x1f4bb; hello 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 削好皮的Pineapple! 原创 &#x1f468;‍&#x1f4…

17085 工作分配问题(优先做)

这个问题可以通过回溯法来解决。我们可以遍历所有可能的工作分配方案&#xff0c;然后找出总劳务费用最小的方案。 以下是C代码实现&#xff1a; #include <iostream> #include <vector> #include <algorithm> using namespace std;const int INF 1e9; co…

羌活基因组--文献精读-36

The chromosome-scale assembly of the Notopterygium incisum genome provides insight into the structural diversity of coumarins 羌活&#xff08;Notopterygium incisum&#xff09;基因组的染色体级别组装为香豆素的结构多样性提供了新的见解 摘要 香豆素是由苯丙素途…

内网安全:多种横向移动方式

1.MMC20.Application远程执行命令 2.ShellWindows远程执行命令 3.ShellBrowserWindow远程执行命令 4.WinRM远程执行命令横向移动 5.使用系统漏洞ms17010横向移动 DCOM&#xff1a; DCOM&#xff08;分布式组件对象模型&#xff09;是微软的一系列概念和程序接口。它支持不同…

Java中操作文件

认识⽂件 我们先来认识狭义上的⽂件(file)。针对硬盘这种持久化存储的I/O设备&#xff0c;当我们想要进⾏数据保存时&#xff0c; 往往不是保存成⼀个整体&#xff0c;⽽是独⽴成⼀个个的单位进⾏保存&#xff0c;这个独⽴的单位就被抽象成⽂件的概 念&#xff0c;就类似办公桌…