使用 Electron 开发桌面应用

image.png

介绍

Electron,官方简介:使用 JavaScript,HTML 和 CSS 构建跨平台的桌面应用程序。
出于我的爱好,接触到了Electron,并开始尝试开发一些本地小工具。
如下是对开发过程作的一个经验总结,便于回顾和交流。
javascript

使用

下面来构建一个简单的electron应用。
应用源码地址:github.com/zhuxingmin/…html

1. 项目初始化

项目基于 create-react-app@3.3.0 搭建,执行命令生成项目java

// 全局安装 create-react-app
npm install -g create-react-app

// 执行命令生成项目
create-react-app electronApp

// 安装依赖并启动项目
yarn && yarn start
复制代码

此时启动的只是一个react应用,下一步安装 electron electron-updater electron-builder electron-is-dev等库node

yarn add electron electron-updater electron-builder electron-is-dev
复制代码
包名 版本 做用
electron ^9.2.1 electron库
electron-builder ^22.8.0 打包库
electron-updater ^4.1.2 更新库
electron-is-dev 2.0.0 环境库

2. 配置package.json

安装完项目依赖后,在package.json中添加electron应用相关配置。react

"version": "0.0.1"              // 设置应用版本号 
"productName": "appName"        // 设置应用名称
"main": "main.js"               // 设置应用入口文件
"homepage": "."                 // 设置应用根路径
复制代码

scripts中添加应用命令,启动以及打包。git

"estart": "electron ."              // 启动
"package-win": "electron-builder"   // 打包 (此处以windows平台为例,故命名为package-win)
复制代码

新增build配置项,添加打包相关配置。
主要有如下几个配置:
github

属性 说明
appId 应用id,通常默认为应用安装路径
windows下能够在 PowerShell中输入Get-StartApps查看应用id
compression 应用打包压缩类型
nsis 应用打包安装与卸载的相关配置
files 应用打包所包含的文件范围
directories 应用打包地址和输出地址
publish 应用发布配置,目前主要做为应用更新地址来使用
protocols 自定义协议,可根据协议名唤醒app
win windows打包配置,生成exe文件
"build": {
    // 自定义appId 通常以安装路径做为id windows下能够在 PowerShell中输入Get-StartApps查看应用id
    "appId": "org.develar.zhuxingmin",
    // 打包压缩 "store" | "normal"| "maximum" 
    "compression": "store",
    // nsis安装配置
    "nsis": {
        "oneClick": false, // 一键安装
        "allowToChangeInstallationDirectory": true, // 容许修改安装目录
        // 下面这些配置不经常使用
        "guid": "haha",    // 注册表名字
        "perMachine": true, // 是否开启安装时权限限制(此电脑或当前用户)
        "allowElevation": true, // 容许请求提高。 若是为false,则用户必须使用提高的权限从新启动安装程序。
        "installerIcon": "xxx.ico", // 安装图标
        "uninstallerIcon": "xxx.ico", //卸载图标
        "installerHeaderIcon": "xxx.ico", // 安装时头部图标
        "createDesktopShortcut": true, // 建立桌面图标
        "createStartMenuShortcut": true, // 建立开始菜单图标
        "shortcutName": "lalala" // 图标名称
    },
    // 应用打包所包含文件
    "files": [
        "build/**/*",
        "main.js",
        "source/*",
        "service/*",
        "static/*",
        "commands/*"
    ],
    // 应用打包地址和输出地址
    "directories": {
        "app": "./",
        "output": "dist"
    },
    // 发布配置 用于配合自动更新
    "publish": [
        {
            // "generic" | "github"
            "provider": "generic", // 静态资源服务器
            "url": "http://你的服务器目录/latest.yml"
        }
    ],
    // 自定义协议 用于唤醒应用
    "protocols": [
        {
            "name": "myProtocol",
            "schemes": [
                "myProtocol"
            ]
        }
    ],
    // windows打包配置
    "win": {
        "icon": "build/fav.ico",
        // 运行权限 
        // "requireAdministrator" | "获取管理员权"
        // "highestAvailable" | "最高可用权限"
        "requestedExecutionLevel": "highestAvailable",
        "target": [
            {
                "target": "nsis"
            }
        ]
    },
},
复制代码

3. 编写入口文件 main.js

众所周知,基于react脚手架搭建的项目,入口文件为index.js,所以在上面配置完成后,咱们想要启动electron应用,须要修改项目入口为main.jsweb

  1. 首先在目录下新建main.js文件,并在package.json文件中,修改应用入口字段main的值为main.js
  2. 经过electron提供的BrowserWindow,建立一个窗口实例mainWindow
  3. 经过mainWindow实例方法loadURL, 加载静态资源
  4. 静态资源分两种加载方式:开发和生产;须要经过electron-is-dev判断当前环境;如果开发环境,能够开启调试入口,经过http://localhost:3000/加载本地资源(react项目启动默认地址);如果生产环境,则要关闭调试入口,并经过本地路径找到项目入口文件index.html

大致代码以下算法

const { BrowserWindow } = require("electron");
const url = require("url");
const isDev = require('electron-is-dev');
mainWindow = new BrowserWindow({
    width: 1200,              // 初始宽度
    height: 800,              // 初始高度
    minWidth: 1200,
    minHeight: 675,
    autoHideMenuBar: true,    // 隐藏应用自带菜单栏
    titleBarStyle: false,     // 隐藏应用自带标题栏
    resizable: true,          // 容许窗口拉伸
    frame: false,             // 隐藏边框
    transparent: true,        // 背景透明
    backgroundColor: "none",  // 无背景色
    show: false,              // 默认不显示
    hasShadow: false,         // 应用无阴影
    modal: true,              // 该窗口是否为禁用父窗口的子窗口
    webPreferences: {
      devTools: isDev,     // 是否开启调试功能
      nodeIntegration: true,  // 默认集成node环境
    },
});

const config = dev
    ? "http://localhost:3000/"
    : url.format({
        pathname: path.join(__dirname, "./build/index.html"),
        protocol: "file:",
        slashes: true,
      });

mainWindow.loadURL(config);
复制代码

4. 项目启动

项目前置操做完成,运行上面配置的命令来启动electron应用npm

// 启动react应用,此时应用运行在"http://localhost:3000/"
   yarn start 
   // 再启动electron应用,electron应用会在入口文件`main.js`中经过 mainWindow.loadURL(config) 来加载react应用
   yarn estart 
复制代码

文件目录

至此,一个简单的electron应用已经启动,效果图以下(这是示例项目的截图)。

效果图

做为一个客户端应用,它的更新与咱们的网页开发相比要显得稍微复杂一些,具体将会经过下面一个应用更新的例子来讲明。

5. 应用更新

electron客户端的更新与网页不一样,它须要先下载更新包到本地,而后经过覆盖源文件来达到更新效果。

首先第一步,安装依赖

yarn add electron-updater electron-builder
复制代码

应用经过electron-updater提供的api,去上文配置的服务器地址寻找并对比latest.yml文件,若是版本号有更新,则开始下载资源,并返回下载进度相关信息。下载完成后能够自动也能够手动提示用户,应用有更新,请重启以完成更新 (更新是能够作到无感的,下载完更新包以后,能够不提示,下次启动客户端时会自动更新)

// 主进程
const { autoUpdater } = require("electron-updater");
const updateUrl = "应用所在的远程服务器目录"
const message = {
    error: "检查更新出错",
    checking: "正在检查更新……",
    updateAva: "检测到新版本,正在下载……",
    updateNotAva: "如今使用的就是最新版本,不用更新",
};
autoUpdater.setFeedURL(updateUrl);
autoUpdater.on("error", (error) => {
    sendUpdateMessage("error", message.error);
});
autoUpdater.on("checking-for-update", () => {
    sendUpdateMessage("checking-for-update", message.checking);
});
autoUpdater.on("update-available", (info) => {
    sendUpdateMessage("update-available", message.updateAva);
});
autoUpdater.on("update-not-available", (info) => {
    sendUpdateMessage("update-not-available", message.updateNotAva);
});
// 更新下载进度事件
autoUpdater.on("download-progress", (progressObj) => {
    mainWindow.webContents.send("downloadProgress", progressObj);
});
autoUpdater.on("update-downloaded", function ( event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate ) {
    ipcMain.on("isUpdateNow", (e, arg) => {
        // 接收渲染进程的确认消息 退出应用并更新
        autoUpdater.quitAndInstall();
    });
    //询问是否当即更新
    mainWindow.webContents.send("isUpdateNow");
});
ipcMain.on("checkForUpdate", () => {
    //检查是否有更新
    autoUpdater.checkForUpdates();
});

function sendUpdateMessage(type, text) {
  // 将更新的消息事件通知到渲染进程
  mainWindow.webContents.send("message", { text, type });
}

复制代码
// 渲染进程
const { ipcRenderer } = window.require("electron");

// 发送检查更新的请求
ipcRenderer.send("checkForUpdate");

// 设置检查更新的监听频道

// 监听检查更新事件
ipcRenderer.on("message", (event, data) => {
  console.log(data)
});

// 监听下载进度
ipcRenderer.on("downloadProgress", (event, data) => {
  console.log("downloadProgress: ", data);
});

// 监听是否能够开始更新
ipcRenderer.on("isUpdateNow", (event, data) => {
   // 用户点击肯定更新后,回传给主进程
   ipcRenderer.send("isUpdateNow");
});
复制代码

应用更新的主要步骤

  1. 在主进程中,经过api获取远程服务器上是否有更新包
  2. 对比更新包的版本号来肯定是否更新
  3. 对比结果如需更新,则开始下载更新包并返回当前下载进度
  4. 下载完成后,开发者可选择自动提示仍是手动提示或者不提醒(应用在下次启动时会自动更新)

上文演示了在页面上(渲染进程),是如何与主进程进行通讯,让主进程去检查更新。
在实际使用中,若是咱们须要用到后台的能力或者原生功能时,主进程与渲染进程的交互必不可少。
那么他们有哪些交互方式呢?

在看下面的代码片断以前,能够先了解一下electron主进程与渲染进程 简单来讲就是,经过main.js来执行的都属于主进程,其他皆为渲染进程。

6. 主进程与渲染进程间的经常使用交互方式

// 主进程中使用
const { ipcMain } = require("electron");

// 渲染进程中使用
const { ipcRenderer } = window.require("electron");
复制代码

方式一

渲染进程 发送请求并监听回调频道

ipcRenderer.send(channel, someRequestParams);
ipcRenderer.on(`${channel}-reply`, (event, result)=>{
    // 接收到主进程返回的result
})
复制代码

主进程 监听请求并返回结果

ipcMain.on(channel, (event, someRequestParams) => {
    // 根据someRequestParams,通过操做后获得result
    event.reply(`${channel}-reply`, result)
})
复制代码

方式二

渲染进程

const result = await ipcRenderer.invoke(channel, someRequestParams);
复制代码

主进程:

ipcMain.handle(channel, (event, someRequestParams) => {
    // 根据someRequestParams,通过操做后获得result
    return result
});
复制代码

方式三 以上两种方式均为渲染进程通知主进程, 第三种是主进程通知渲染进程

主进程

/* * 使用`BrowserWindow`初始化的实例`mainWindow` */ 
mainWindow.webContents.send(channel, something)
复制代码

渲染进程

ipcRenderer.on(channel, (event, something) => {
    // do something
})
复制代码

上文的应用更新用的就是方式一

还有其它通信方式postMessage, sendTo等,能够根据具体场景决定使用何种方式。

7. 应用唤醒(与其余应用联动)

electron应用除了双击图标运行以外,还能够经过协议连接启动(浏览器地址栏或者命令行)。这使得咱们能够在网页或者其余应用中,以连接的形式唤醒该应用。连接能够携带参数 例:zhuxingmin://?a=1&b=2&c=3 ‘自定义协议名:zhuxingmin’ ‘参数:a=1&b=2&c=3’。

咱们能够经过参数,来使应用跳转到某一页或者让应用作一些功能性动做等等。

const path = require('path');
const { app } = require('electron');

// 获取单实例锁
const gotTheLock = app.requestSingleInstanceLock();

// 若是获取失败,证实已有实例在运行,直接退出
if (!gotTheLock) {
  app.quit();
}

const args = [];
// 若是是开发环境,须要脚本的绝对路径加入参数中
if (!app.isPackaged) {
  args.push(path.resolve(process.argv[1]));
}
// 加一个 `--` 以确保后面的参数不被 Electron 处理
args.push('--');
const PROTOCOL = 'zhuxingmin';
// 设置自定义协议
app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, args);

// 若是打开协议时,没有其余实例,则当前实例当作主实例,处理参数
handleArgv(process.argv);

// 其余实例启动时,主实例会经过 second-instance 事件接收其余实例的启动参数 `argv`
app.on('second-instance', (event, argv) => {
  if (process.platform === 'win32') {
    // Windows 下经过协议URL启动时,URL会做为参数,因此须要在这个事件里处理
    handleArgv(argv);
  }
});

// macOS 下经过协议URL启动时,主实例会经过 open-url 事件接收这个 URL
app.on('open-url', (event, urlStr) => {
  handleUrl(urlStr);
});

function handleArgv(argv) {
  const prefix = `${PROTOCOL}:`;
  const offset = app.isPackaged ? 1 : 2;
  const url = argv.find((arg, i) => i >= offset && arg.startsWith(prefix));
  if (url) handleUrl(url);
}

function handleUrl(urlStr) {
  // myapp://?a=1&b=2
  let paramArr = urlStr.split("?")[1].split("&");
  const params = {};
  paramArr.forEach((item) => {
      if (item) {
        const [key, value] = item.split("=");
        params[key] = value;
      }
  });
  /** { a: 1, b: 2 } */ 
}
复制代码

8. 文档参考

  1. 自动更新
  2. 自定义协议唤醒Electron应用
  3. electron主进程与渲染进程

9. 完整项目代码

github.com/zhuxingmin/…


南京三百云信息科技有限公司(车300)成立于2014年3月27日,是一家扎根于南京的移动互联网企业,目前坐落于南京、北京。通过7年积累,累计估值次数已达52亿次,得到了国内外多家优质投资机构青睐如红杉资本、上汽产业基金等。
三百云是国内优秀的以人工智能为依托、以汽车交易订价和汽车金融风控的标准化为核心产品的独立第三方的汽车交易与金融SaaS服务提供商。

欢迎加入三百云,一块儿见证汽车行业蓬勃发展,期待与您携手同行!
Java开发、Java实习、PHP实习、测试、测开、产品经理、大数据、算法实习,热招中...
官网:www.sanbaiyun.com/
投递简历:hr@che300.com,请注明来自掘金😁

相关文章
相关标签/搜索