一、说明:

本文的自动更新功能使用的项目为 electron-vue 脚手架搭建一个默认项目。
参考的文章如下:

二、开始:新建一个 electron 项目

首先你得有一个需要配置自动更新功能的 electron 项目。这里我为了测试自动更新功能是否成功搭建使用的是 electron-vue 脚手架搭建的项目。
搭建过程如下:

# 安装 vue-cli 和 脚手架样板代码
npm install -g vue-cli
vue init simulatedgreg/electron-vue autoUpdataTest

# 安装依赖并运行你的程序
cd autoUpdataTest
npm install
npm run dev

程序运行后的界面如下:

脚手架生成的文件结构:

|- autoUpdateTest
  |- .electron-vue # 压缩及运行环境的配置文件
  |- build # 
    |- icons # 图标文件
    |- ... # 打包生成的文件在此处 
  |- dist # 用 webpack 压缩项目后生成的压缩文件在此处
  |- node_modules
  |- src # 资源文件
    |- main # 主进程
    |- renderer # 渲染进程
    |- index.ejx # 入口文件
  |- static # 静态资源
  |- .babelrc
  |- .gitignore
  |- .travis
  |- appveyor.yml
  |- package-lock.json # npm 自动生成的文件
  |- package.json
  |- README.md

使用 electron-builder 最关键的配置在 package.json 里:(为了观察我们所需要的地方,把此篇文章里不需要关注的代码给删掉了。)

{
  "name": "autoupdatetest",
  "version": "0.0.0",
  "author": "wonder <xxxxxxxxx@qq.com>",
  "description": "An electron-vue project",
  "main": "./dist/electron/main.js", 
  "scripts": {
    "build": "node .electron-vue/build.js && electron-builder",
    "dev": "node .electron-vue/dev-runner.js",
  },
  "build": {
    "productName": "autoupdateteset",
    "appId": "org.simulatedgreg.electron-vue",
    "directories": {
      "output": "build"
    },
    "files": "dist/electron/**/*",
    "win": {
      "icon": "build/icons/icon.ico"
    }
  },
  "dependencies": {
  },
  "devDependencies": {
  }
}

解析:
前四行是一般的 package.json 会有的:

  • name — 项目名
  • version — 版本号
  • author — 开发人员及邮箱号
  • description — 项目描述 。

下面重点看后面的内容:electron-builder详细配置文档

  • "main": "./dist/electron/main.js" — 这里的 main 入口文件指的是用 electron-builder 打包主程序的入口文件,这里的路径是使用 webpack 压缩项目后文件输出的位置。
  • scripts — 脚本
    • "build": "node .electron-vue/build.js && electron-builder" — 生产环境,压缩打包项目。先运行 .electron-vue 文件夹下的 build.js 脚本对项目进行压缩,输出的位置在 dist 文件夹下,然后再使用配置好的 electron-builder 对 dist 文件夹下的文件进行打包生成应用的安装包。
    • "dev": "node .electron-vue/dev-runner.js" — 开发环境,可以运行我们的项目并测试。这里使用了热更新,改动代码不需要刷新即可看到应用的改变。
  • build — electron-builder 配置项
    • "productName": "autoupdateteset", — 工程项目名
    • "appId": "org.simulatedgreg.electron-vue" — 应用程序 ID。强烈建议设置显式ID。
    • directories
      • "output": "build" — 生成的安装包输出目录。
    • "files": "dist/electron/**/*" — 安装包源文件目录,支持多路径(数组)
    • "win": { "icon": "build/icons/icon.ico"} — 打包成 Windows 系统下安装包应用程序图标路径,还有别的配置项可以在详细文档中查看。

有关 electron-vue 的使用的更详细的说明请看 中文文档
 

三、自动更新

1. 安装依赖

自动更新功能的实现依赖 electron-builder electron-updater
因为我们是用的electron-builder脚手架生成的项目,已经有 electron-builder 依赖了,所以只需要安装 electron-updater

# 目录 E:\GitHub\autoupdateteset
npm i electron-updater --save # 必须安装为运行依赖,否则运行会出错

2. 配置 package.json

为了配合打包 package.json 需要给 build 新增配置项:

"build": {
	"publish": [
		{
		  "provider": "generic",
		  "url": "http://updata.electron.com/" #这里是我本地开的服务器的地址
		}
	],
	...
}

3. 主进程(参考:electron 中文文档)

主进程的入口文件是 src/main/index.js

import { 
app, 			// app 模块是为了控制整个应用的生命周期设计的。
BrowserWindow,  // BrowserWindow 类让你有创建一个浏览器窗口的权力。
ipcMain 
} from 'electron';

// 引入自动更新模块
const { autoUpdater } = require('electron-updater');
// 不支持 ES6 则用如下方式引入
// const autoUpdater = require("electron-updater").autoUpdater

const feedUrl = 'http://updata.electron.com'; // 更新包位置


/**
 * Set `__static` path to static files in production
 * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
 */
if (process.env.NODE_ENV !== 'development') {
    global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\');
}

let mainWindow, webContents;
const winURL = process.env.NODE_ENV === 'development' ?
    `http://localhost:9080` :
    `file://${__dirname}/index.html`;

function createWindow() {
    /**
     * Initial window options
     */
    mainWindow = new BrowserWindow({
        height: 563,
        useContentSize: true,
        width: 1000
    });

    mainWindow.loadURL(winURL);

    webContents = mainWindow.webContents;

    mainWindow.on('closed', () => {
        mainWindow = null;
    });
}

// 主进程监听渲染进程传来的信息
ipcMain.on('update', (e, arg) => {
    console.log("update");
    checkForUpdates();
});

let checkForUpdates = () => {
	// 配置安装包远端服务器
    autoUpdater.setFeedURL(feedUrl);

	// 下面是自动更新的整个生命周期所发生的事件
    autoUpdater.on('error', function(message) {
        sendUpdateMessage('error', message);
    });
    autoUpdater.on('checking-for-update', function(message) {
        sendUpdateMessage('checking-for-update', message);
    });
    autoUpdater.on('update-available', function(message) {
        sendUpdateMessage('update-available', message);
    });
    autoUpdater.on('update-not-available', function(message) {
        sendUpdateMessage('update-not-available', message);
    });

    // 更新下载进度事件
    autoUpdater.on('download-progress', function(progressObj) {
        sendUpdateMessage('downloadProgress', progressObj);
    });
    // 更新下载完成事件
    autoUpdater.on('update-downloaded', function(event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
        sendUpdateMessage('isUpdateNow');
        ipcMain.on('updateNow', (e, arg) => {
            autoUpdater.quitAndInstall();
        });
    });

    //执行自动更新检查
    autoUpdater.checkForUpdates();
};

// 主进程主动发送消息给渲染进程函数
function sendUpdateMessage(message, data) {
    console.log({ message, data });
    webContents.send('message', { message, data });
}

app.on('ready', () => {
    createWindow();
});

app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit();
    }
});

app.on('activate', () => {
    if (mainWindow === null) {
        createWindow();
    }
});

4. 渲染进程

渲染进程的入口文件是 src/renderer/index.js
这里我们主要修改 App.vue,将原来的内容全删掉并使更新的整个周期在界面上打印出来。

<template>
    <div id="app">
        <!-- <router-view></router-view> -->
        <button @click="autoUpdate()">获取更新</button>
        <ol id="content">
            <li>生命周期过程展示</li>
        </ol>
    </div>
</template>

<script>
// import { ipcRenderer } from 'electron';
const { ipcRenderer } = require('electron');
export default {
  name: 'my-project1',
  mounted() {
      var _ol = document.getElementById("content");
      ipcRenderer.on('message',(event,{message,data}) => {
          let _li = document.createElement("li");
          _li.innerHTML = message + " <br>data:" + JSON.stringify(data) +"<hr>";
          _ol.appendChild(_li);
          if (message === 'isUpdateNow') {
		      if (confirm('是否现在更新?')) {
		          ipcRenderer.send('updateNow');
	          }
	      }
      });
  },
  methods: {
    autoUpdate() {
      ipcRenderer.send('update');
    }
  }
};
</script>

<style>
/* CSS */
</style>

显示的界面如下:

5. 自动更新过程简单介绍

1.将 package.json 里的版本号先改为 0.0.1,然后npm run build生成一个版本为0.0.1的安装包。

注意上面一步会生成一个latest.yml文件,autoUpdate 实际上通过检查该文件中安装包版本号与当前应用版本号对比来进行更新判断的。
latest.yml文件内容如下:


2.然后将上一步生成的安装包放在本地开启的服务器文件夹下,对应你在主程序入口文件中配置的服务器位置。


3.将 package.json 中的版本号改回0.0.0,再npm run build一遍,运行 build 文件夹下的 exe 安装包,就将软件安装在你电脑里面了。点击安装完成后桌面上的快捷方式,再次点击上面的获取更新的按钮就可以看到显示在界面的自动更新生命周期了。(但这里因为会给你直接自动更新,所以会一闪而过,你可以在 autoUpdate 的各个生命周期事件里设置主进程与渲染进程通信,则可以一步一步观察到整个自动更新的生命周期了。)

三、通过测试总结 autoUpdate 生命周期图

四、更新过程展示

1、无版本更新

2、有版本更新


点击取消后会先不更新,在应用关闭后更新:

点击确认后则会直接更新:

五、踩过的坑

主进程与渲染进程通信
最开始我是直接在主进程直接运行更新

然后想在渲染进程中打印主进程传过来的消息,但是发现只有 isUpdateNow 事件运行时才有日志显示。
结果发现原来主进程与渲染进程之间通信必须在渲染进程已经运行的时候(即那个界面完全显示出来)才能够进行。所以我将自动更新改为界面按钮触发,这样才能检测到自动更新的整个流程。

 

Logo

前往低代码交流专区

更多推荐