IM跨平台:Electron初体验(快速开始、跨进程通信、打包、踩坑)
yuyutoo 2024-12-16 17:21 1 浏览 0 评论
本文由蘑菇街前端技术团队分享,原题“Electron 从零到一”,有修订和改动。
1、引言
在上篇《快速了解新一代跨平台桌面技术——Electron》,我们已经对Electron跨端框架有了基本的认识。
本篇将带你简单上手Electron框架开发跨平台桌面端,内容包括一个快速开始例子、跨进程通信原理、打包和分发、以及一些典型的技术踩坑等。希望能带给你启发。
2、系列文章
本文是系列文章中的第2篇,本系列总目录如下:
- 《IM跨平台技术学习(一):快速了解新一代跨平台桌面技术——Electron》
- 《IM跨平台技术学习(二):Electron初体验(快速开始、跨进程通信、打包、踩坑等)》(* 本文)
- 《IM跨平台技术学习(三):vivo的Electron详细技术栈选型、踩坑总结等 》(稍后发布.. )
- 《IM跨平台技术学习(四):蘑菇街基于Electron开发IM客户端的技术实践》(稍后发布.. )
- 《IM跨平台技术学习(五):融云基于Electron的IM跨平台SDK改造实践总结》(稍后发布.. )
- 《IM跨平台技术学习(六):网易云信基于Electron的IM消息全文检索技术实践》(稍后发布.. )
3、Electron简介
Electron 是一个赋力前端进行跨平台开发的框架,让开发人员使用 JavaScript、HTML 和 CSS 等前端技术构建跨平台的桌面应用。
Electron 通过将 Chromium(所有类Chrome的浏览器都是基于这个开源工程而来) 和 Node.js 合并到同一个运行时环境中(见下图),并将其打包为 Mac,Windows 和 Linux 系统下的应用,而开发人员只需关注前端代码的开发。
▲ 上图引用自《快速了解新一代跨平台桌面技术——Electron》
Chromium、Node.js、Native API这三者的作用分别是:
- 1)Chromium :为Electron提供了强大的UI能力,可以不考虑传统浏览器兼容性的情况下,利用强大的Web生态来开发界面;
- 2)Node.js:让Electron有了底层的操作能力(比如文件的读写,甚至是集成C++等等操作),并可以使用大量开源的npm包来完成开发需求。
- 3)Native API:Native API让Electron有了跨平台和桌面端的原生能力(比如说它有统一的原生界面,窗口、托盘、消息通知这些)。
Electron就是通过这三者的巧妙组合,让我们开发跨平台应用变的十分高效。
本质上就是chromium(chrome开源版本)浏览器,有最新的东西都会在chromium测试,所以electron可以体验最新的api,这也是好处之一。
有关Electron的基本介绍等,这里就不再赘述,如果您还不曾了解,可以先阅读本文的上篇。
4、快速开始
4.1 资料准备
Electron 官方提供了一个名为electron-quick-start 的项目,可以 clone 下来当成模版使用,本文使用 create-react-app 来一步一步学习。
其它重要的Electron开发资源:
- 1)Electron官网:https://electronjs.org;
- 2)Electron Github:https://github.com/electron;
- 3)Electron开发手册:https://www.electronjs.org/zh/docs/latest/。
4.2 创建一个 react 项目
# 安装 create-react-app 命令,如果已将安装请忽略
npm install -g create-react-app
# 创建 electron-react 项目
create-react-app electron-react
# 启动项目
cd electron-react && npm start
4.3 配置 Electron 环境
1)在 public 文件夹下新建 index.html,随便写点内容:
...
<div>hello world</div>
...
2)接下来创建 electron 主线程文件(public/main.js),建议写在 public 路径下面:
const{app, BrowserWindow} = require('electron')
// 创建全局变量并在下面引用,避免被GC
let win
function createWindow () {
// 创建浏览器窗口并设置宽高
win = newBrowserWindow({ width: 800, height: 600 })
// 加载页面
win.loadFile('./index.html')
// 打开开发者工具
win.webContents.openDevTools()
// 添加window关闭触发事件
win.on('closed', () => {
win = null// 取消引用
})
}
// 初始化后 调用函数
app.on('ready', createWindow)
// 当全部窗口关闭时退出。
app.on('window-all-closed', () => {
// 在 macOS 上,除非用户用 Cmd + Q 确定地退出,
// 否则绝大部分应用及其菜单栏会保持激活。
if(process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// 在macOS上,当单击dock图标并且没有其他窗口打开时,
// 通常在应用程序中重新创建一个窗口。
if(win === null) {
createWindow()
}
})
3)接着再修改 package.json 中的 main 字段对应的路径, 并添加 start 命令:
{
...
"main": "main.js",
"scripts": "electron ."
}
4)执行 npm start,就会弹出如下运行界面:
以上就是我简单写的一个页面,大家也可以写一写自己感兴趣的东西。
真如上面演示的这样,一个简单的Electron跨平台桌面应用就开发好了,真的 so easy!
5、进程详解
5.1 基本认知
Electron 架构和 Chromium 架构类似,也是具有1个主进程和多个渲染进程(如下图所示)。
但是也有区别:
- 1)在各个进程中暴露了 Native API ,提供了 Native 能力;
- 2)引入了 Node.js,所以可以使用 Node 的能力;
- 3)但是渲染进程使用node 需要配置。
可以简单的理解为:Electron为web项目套上了Node.js环境的壳,使得我们可以调用Node.js的丰富的API。这样我们可以用JavaScript来写桌面应用,拓展很多我们在web端不能做的事情。
下面这张图,技术原理更容易理解一点:
5.2 主进程的主要特点
Electron 运行 package.json 的 main 脚本的进程被称为主进程 (主进程只有一个)。涉及到具体代码的讲解,将在下一节中展开,本节就不作过多阐述了。
Electron主进程的具体职责:
- 1)主进程连接着操作系统和渲染进程,可以把她看做页面和计算机沟通的桥梁;
- 2)进程间通信、窗口管理
- 3)全局通用服务;
- 4)一些只能或适合在主进程做的事情(例如浏览器下载、全局快捷键处理、托盘、session);
- 5)维护一些必要的全局状态。
5.3 渲染进程的主要特点
渲染进程就是我们所熟悉前端环境了,只是载体改变了,从浏览器变成了window.
注意:出于安全考虑,渲染进程是不能直接访问本地资源的,因此都需要在主进程完成。
Electron渲染进程主要特点:
- 1)Electron 使用了 Chromium 来展示 web 页面,所以 Chromium 的多进程架构也被使用到;
- 2)每个web页面运行在它自己的渲染进程中(每个渲染进程都是相互独立的,并且只关心他们自己的网页);
- 3)使用BrowserWindow类开启一个渲染进程并将这个实例运行在该进程中,当一个BrowserWindow实例被销毁后,相应的渲染进程也会被终止;
- 4)渲染进程中不能调用原生资源,但是渲染进程中同样包含Node.js环境,所以可以引入Node.js。
5.4 主进程与渲染进程的关系
主进程与渲染进程的关系主要是这样:
- 1)主进程使用 BrowserWindow 实例创建网页;
- 2)每个 BrowserWindow 实例都在自己的渲染进程里运行着一个网页(当一个 BrowserWindow 实例被销毁后,相应的渲染进程也会被终止);
- 3)主进程管理所有页面和与之对应的渲染进程;
- 4)由于在网页里管理原生 GUI 资源是非常危险而且容易造成资源泄露,所以在网页面调用 GUI 相关的 APIs 是不被允许的(如果你想在网页里使用 GUI 操作,其对应的渲染进程必须与主进程进行通讯,请求主进程进行相关的 GUI 操作)。
具体关系如下图所示:
把它们想象成这样:
即Chrome(或其他浏览器)的每个标签页(tab)及其页面,就好比 Electron 中的一个单独渲染进程。即使关闭所有标签页,Chrome 依然存在。这好比 Electron 的主进程,能打开新的窗口或关闭这个应用。就像下图这样。
6、从代码角度理解进程
6.1 主进程和渲染进程
先来看看 electron 项目基本目录结构:
app
└─public
└─index.html---------------入口文件
├─main.js----------------------程序启动入口,主进程
├─ipc--------------------------进程间模块
├─appNetwork-------------------应用通信模块
└─src--------------------------窗口管理,渲染进程
├─components---------------通用组件模块
├─store--------------------数据共享模块
├─statics------------------静态资源模块
└─pages----------------------窗口业务模块
├─窗口A----------------窗口
└─窗口B----------------窗口
如上所示:package.json 中的 main 字段对应的文件的进程是主进程。Electron集成了Chromium来展示窗口界面,窗口中所看到的内容使用的都是HTML渲染出来的。
Chromium本身是多进程渲染页面的架构(在默认情况下,Chromium的默认策略是对每一个tab新开一个进程,以确保每个页面是独立且互不影响的,避免一个页面的崩溃导致全部页面无法使用),所以Electron在展示窗口时,也会使用到Chromium的多进程架构。而这种多进程渲染架构在Electron中,也就被是渲染进程(render process)啦。
6.2 进程间通信
在 Electron 中,GUI 相关的模块(如 dialog,menu 等)仅在主进程可用,在渲染进程中不可用。
为了在渲染进程中使用它们,需要使用 ipc 模块向主进程发送消息,下面是几种进程间通讯的方法。
1)ipcMain & ipcRenderer:
从主进程到渲染进程的异步通信,也可以将消息从主进程发送到渲染进程(参考文档)。
发送消息时,事件名称为 channel。回复同步消息时,需要设置 event.returnValue。
将异步消息发送回发送方,可以使用 event.reply(...),这个辅助方法将自动处理来自渲染进程的消息,然而 event.sender.send(...) 这个方法则始终将消息发送给主进程。
下面是在渲染和主进程之间发送和处理消息的一个例子:
// 在主进程中
const { ipcMain } = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
console.log(arg); // 输出 'ping'
event.reply('asynchronous-reply', 'pong');
})
ipcMain.on('synchronous-message', (event, arg) => {
console.log(arg) // 输出 ‘ping’
event.returnValue = 'pong'
})
// 在渲染进程(网页)中
const { ipcRenderer } = require('electron')
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // 输出 'pong'
ipcRenderer.on('asynchronous-reply', (event, arg) => {
console.log(arg); // 输出 'pong'
})
ipcRenderer.send('asynchronous-message', 'ping')
2)remote 模块:
remote 为渲染进程和主进程通信提供了一种简单的方法。你可以调用 main 进程对象的方法,而不必显式发送进程间消息。
例如:从渲染进程创建浏览器窗口
const { BrowserWindow } = require('electron').remote
let win = newBrowserWindow({ width: 800, height: 600 })
win.loadUrl('https://www.mogu.com')
注意: 反过来,如果需要从主进程访问渲染进程,可以使用 webContents.executeJavascript。
3)webContents:
即通过 channel 向渲染进程发送异步消息,可以发送任意参数。在内部,参数会被序列化为 JSON,因此参数对象伤的函数和原型链不会被发送。
除了以上这些方法,也可以使用 localStorage、sessionStorage 等。
7、打包发布
开发完成后,还需要将应用打包成可执行文件,这一环节的坑还是学习 electron 到现在踩的最多的。
目前主流的打包工具有 electron-packager和 electron-builder
7.1 electron-packager
1)安装依赖:
npm i electron-packager --save-dev
2)打包:
electron-packager --platform= --arch= [optional flags...]
也可以直接运行 npm run electron-packager 打包。
7.2 electron-builder
官方解释:
A complete solution to package and build a ready for distribution Electron, Proton Native or Muon app for macOS, Windows and Linux with “auto update” support out of the box.
简单的说:electron-builder 有比 electron-packager 更丰富的功能,支持更多的平台,同时也支持了自动更新。除了这几点外,electron-builder 打出的包更为轻量,并且可以打包出不暴露源码的 setup 安装程序。
另外使用下来感觉比 electron-packager 的坑要少一点。
1)安装依赖:
npm i electron-builder --save-dev
2)打包(在项目的 package.json 文件中定义 build 字段):
{
"build": {
"appId": "com.xxx.app",
"extends": null,
"files": [
"build/**/*"
],
"mac": {
"icon": "icons/icon.icns"
},
"win": {
"target": "nsis",
"icon": "icons/icon.png"
}
}
}
这是最基础的配置,当然打包过程中可能会碰到其他的问题需要修改配置。通常 files 配置只写一个 build 文件夹是不够的,要根据项目结构和打包情况添加其他路径。
添加 scripts 命令
{
"scripts": {
"pack": "electron-builder"
}
}
运行 npm run pack 打包。
打包完成后在 dist 目录下有可执行文件,打开后如果没有报错,则说明打包成功。
8、踩坑总结
我所遇到的大部分都是打包遇到的坑,以下列举几个典型的坑。
8.1 使用 electron-packager 打包报错
Generated checksum for"electron-v6.0.2-darwin-x64.zip"did not match expected checksum。
解决方法:node 版本升级到 8.x 以上就好。
8.2 打开打包生成的可执行文件报错
出现这种问题可能有以下几个原因。
1)项目中可能直接访问了本地路径, 浏览器为了安全考虑不允许访问。
2)package.json 中的 build 配置问题,假如 main.js 在一个很深的路径中,需要在下面单独添加 main.js 的路径:
"build": {
...
+ "public/main.js"
...
}
3)webpack 配置中的路径直接使用了 __dirname, 可以使用 remote 模块的 getAppPath 方法取得路径:
const remote = require('remote')
const app = remote.require('app')
console.log(app.getAppPath());
参考资料:https://github.com/electron/electron/issues/5107
8.3 dependencies & devDependencies
在 Electron 打包时,一定要分清哪些是生产环境依赖,哪些是开发环境依赖,避免出现此类错误:
8.4 关于打包慢的问题(npm & cnpm)
cnpm 装的各种 node_modules,这种方式下所有的包都是扁平化的安装,一下子 node_modules 展开就有非常多的文件,导致打包的过程非常慢。
但是如果该用 npm 来安装 node_modules 的话,所有的包都是树状结构,层级变深。但是打包速度会快很多(具体资料参见:electron打包过了2小时都没好?)。
9、Electron的优缺点
文章的最后,基于实践体会,总结一下Electron的优缺点。
Electron优点很明显:
- 1)上手较简单:HTML、CSS、JS、Node 、npm包、UI框架 ,方便高效,能很轻松的实现很好看的UI;
- 2)可多端运行:可以快速构建“跨平台”(Windows、MacOs、Linux)的桌面级应用;
- 3)开发时间短:相对其他跨平台方案(如:QT、GTK+ 等),更稳定、bug少,毕竟只要浏览器外壳跑起来了就可以了(当然坑是少不了的);
- 4)兼容性问题:再也不用兼容多浏览器(只针对谷歌,但要兼容mac、Linux)。
Electron缺点也同样显而易见:
- 1)安装包体积大:安装包体积略大(打包了Chromium),至少包含了一个浏览器的体积 ,每装一个app就相当于装一个chrome;
- 2)运行性能稍差:性能不如原生应用,Mac系统下丝滑一些,Window系统就有点丢帧;
- 3)内存占用较大:卡、启动慢,新开一个进程,起步价就是一个NodeJS的内存开销;
- 4)网页加载稍慢:loadURL加载远程页面白屏时间长(优化可采用 VSCode 骨架屏)。
10、参考资料
[1] Electron官方开发者手册
[2] Electron初体验(快速开始、跨进程通信、打包、踩坑等)
[3] Electron 基础入门 简单明了,看完啥都懂了
[4] 网易云信Web端IM的聊天消息全文检索技术实践
学习交流:
- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》
- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK(备用地址点此)
(本文已同步发布于:http://www.52im.net/thread-4039-1-1.html)
相关推荐
- 上位机程序如何保存配置信息
-
上位机程序通常都会需要保存一些用户的配置信息。比如目标PLC的IP地址、变量信息等。这些信息需要上位机程序在运行时将其保存。保存用户配置信息的方法有很多,比如设置文件、INI文件、XML文件和本地数据...
- #X5效果器回声调试教程
-
大家好,今天教大家调回声按键。·按一下是回声相位和回声效果音量。·按一下下键,下面是回声直达声相位和音量。直达声只直接体现话筒的声音和回声效果,根据现场环境边调边试合适就好。·按第二下显示回声预延迟,...
- 对象存储、文件存储和块存储
-
对象存储定义:以对象为单位来处理、存储和检索数据,每个对象包含数据本身、元数据以及一个全局唯一的标识符,通过API调用进行数据读写,通常基于HTTP或HTTPS协议。优点:...
- SINAMICS S200 常见问题(调试篇)
-
01概述...
- SQLSERVER:存储过程和函数
-
在SQLServer中,存储过程和函数是数据库编程的基础。它们允许开发者编写SQL脚本来执行复杂的操作,同时提供了代码重用和逻辑封装的能力。下面将通过一些实例来详细介绍存储过程和函数的使用。...
- PVE8.0连接并使用windows server 2019上的IPSAN存储
-
本文将演示如何在Windowsserver2019服务器中部署IP-SAN存储并在PVE8.0中正确连接IPSAN存储。如果这篇文章能为大家带来帮助,希望大家能慷慨点赞,并持续关注我的账号,未来我...
- 【Oracle】Package 存储过程编写以及其他实用技术
-
这篇文章是之前自己在公司的一篇技术分享,搬过来就不提供脚本了!...
- 数据库|数据库存储过程相关学习
-
哈喽,你好啊,我是雷工!前面学习记录了数据库中视图的相关内容...
- 轻松达成4K160帧,威联通NAS补帧教程丨调用第三方开启超分和补帧
-
前言大家好,我是加勒比考斯。...
- 群晖NAS(一)存储管理介绍
-
第1章前言加更一期SMARTX备份。近期群晖厂商那边借了一台群晖3621xs+的NAS存储测试,想着SMARTX里面带备份功能,然后做下实验,怎么把SMARTX备份到群晖存储上。以下此架构图其中19...
- mysql存储过程入门及基本用法总结
-
现在学习存储过程,有一种四九年入国军的感觉,之前看公司计费相关的业务上还在用,所以还是抽时间简单学习了一下,这里记录一下。说到存储过程,它的意义自不必提,各大老牌数据库都支持,而且经常以此来挤兑一些还...
- 存储过程与函数
-
存储过程与函数MySQL从5.0版本开始支持存储过程和函数。存储过程和函数能够将复杂的SQL逻辑封装在一起,应用程序无须关注存储过程和函数内部复杂的SQL逻辑,而只需要简单地调用存储过程和函数即可。...
- 【测试】JMeter调用存储过程
-
JMeter是可以直接调用SQL语句或者存储过程来完成测试的,这次就给大家讲一下如何通过调用MySQL存储过程完成测试。首先我们先创建一个数据库连接池的配置信息:如上图所示,已填写的参数描述如下:Na...
- ADO.NET调用带输入输出的存储过程
-
在ADO.NET中调用带输入和输出参数的存储过程,通常使用SqlCommand对象来执行存储过程,并通过其Parameters集合来设置和获取参数值。以下是一个示例,展示了如何调用一个带输入和输出参数...
- JAVA大厂面试题——String、StringBuffer 和 StringBuilder
-
一、类型String是只读字符串,它不是基本数据类型,是一个对象,是一个final类型的字符数组,所引用的字符串不能被改变,定义后,无法在增删改,而StringBuffffer和StringBuil...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)