作者: 钟离, 酷家乐 PC 客户端负责人
酷家乐客户端: 下载地址 https://www.kujiale.com/activity/136
文章背景: 在酷家乐客户端在 V12 改版成功后, 我们积累了许多的宝贵的经验和最佳实践. 前端社区里关于 Electron 知识相对较少, 因此希望将这些内容以系列文章的形式分享出来.
[Electron] 酷家乐客户端开发实践分享 - 软件自动更新
[Electron] 酷家乐客户端开发实践分享 - 浏览器启动客户端
不定期更新...
本文的初衷
Electron 所使用的技术栈 (JavaScript,Node.JS,html,CSS) 和 web 前端工程师完美契合. 于是, 越来越多的前端工程师, 用 Electron 来开发桌面客户端的开发, 我也是其中的一员.
虽然 Electron 技术栈对前端工程师比较友好, 但是概念较多, 和 Web 前端开发还是有很大差别的, 写个入坑指南希望能帮助读者快速上手 Electron.
了解客户端
首先抛出一个问题, Web 应用是桌面客户端吗? 显然不是. 那么, 问题来了, 什么样的软件才是桌面客户端呢? 我们既然要从 Web 前端转到客户端开发, 那么就需要了解客户端, 就像我们当初了解 Web 应用一样.
回到刚刚那个问题, 桌面客户端有两个重要的特点:
独立运行于操作系统上(桌面客户端只是 PC, 那么限定 Windows,MacOS,Linux 这几个主流 PC 操作系统)
有自己的 GUI(用户图形界面 graphical user interface)
Web 应用有自己的 GUI, 必须在浏览器中执行, 因此不是桌面客户端.
浏览器能直接运行在操作系统上, 而且有自己的 GUI, 因此浏览器是桌面客户端.
Electron 的能力
在刚刚接触 Electorn 的时候, 文档看的我是眼花缭乱. 在某个加班的深夜, 我不禁对天长叹: 这个东西到底能干啥?
这东西能干啥? 在经历了 Electron 的反复摩擦之后, 我总结了 Electron 的几个关键能力:
Node.JS 全部能力, 与操作系统交互
operation system 与操作系统相关的操作
HTTP(s),HTTP2
process,child process 进程相关
file system 文件系统
... 省略
Electron 提供的基础模块, 主要与操作系统交互
App 主进程声明周期管理, 控制 MacOS 任务栏 dock,Windows 任务栏 taskbar
BrowserWindow 控制窗口, 在 MacOS 和 Windows 中窗口非常重要!
screen 操作用户显示器
globalShortcut 系统级别快捷键
... 省略
Chromium 提供的能力, 主要提供 GUI 图形界面
解析 HTML,CSS,JS
Ajax 请求
cookie,localstorage
... 省略
能力越大, 责任越大
如果用户安装了我们的桌面客户端, 那么我们的软件在用户电脑上运行时, 就有了非常大的权利, 这是把双刃剑.
用户选择了我们的软件, 我们也要对用户的电脑负责. 能力越大, 责任也就越大:
1. 注意内存的占用, 特别是 chromium, 简直是内存怪兽. 可以通过 os 来获取用户电脑的配置, 然后根据电脑的配置和可用资源, 来制定合理的策略.
为软件增加代码签名, 提升安全性
谨慎操作注册表, 用户敏感目录
一旦被贴上[流氓软件] ,[不好用] 的标签, 就很难再改变用户的印象了.
主进程, 渲染进程
整体执行流程, 展示了主进程和渲染进程的关系
生命周期
主进程: 从整个应用启动到结束, 该进程一直存在. 主进程只有一个.
渲染进程: 主进程可用创建 / 销毁渲染进程, 因此渲染进程的生命周期是不固定的. 渲染进程可以有多个.
执行环境
渲染进程可用模块
主进程可用模块
在 Electron 的 API 文档中, 会在文档顶部标识该模块在哪个进程可用, 例如: ipcRenderer
职责划分
主进程 | 渲染进程 |
---|---|
控制 app 的生命周期,为 app 注册关键事件 | 解析 HTML,渲染窗口内容 |
阻止一些默认行为,例如 webContents 的跳转、download 事件的默认行为等等(在渲染进程无法做到) | 处理窗口的交互逻辑 |
创建 BrowserWindow,也就是渲染进程。合理设置窗口的参数,控制窗口的生命周期(例如何时销毁窗口),决定 BrowserWindow 加载何处的 HTML | 与主进程通信,实现高级交互 |
窗口, 前端资源
我们回顾一下刚刚讲到的执行流程, 其中有一个有趣的点, 就是 Electron 的窗口会加载一个 HTML 来渲染窗口的内容.
HTML, 以及 HTML 加载的 CSS,JS 文件, 统称为前端资源
如果不加载 HTML 的, 客户端还能用吗? 不妨来试试
- // main process
- const win1 = new BrowserWindow();
- const win2 = new BrowserWindow();
上述代码在主进程中执行, 创建了两个窗口, 窗口并没加载 HTML 文件. 但是窗口却是真实存在的, 带有系统标准的控制栏, 可拖动, 是货真价实的系统窗口!
两个空白窗口
我们可以发现, 前端资源和窗口是分离的. 由主进程创建的的窗口(BrowserWindow), 既是一个系统原生窗口, 同时也是一个加载 & 渲染前端资源的容器
窗口通常会通过 file 协议和 http(s)协议来加载前端资源, 接下来我们看看这两种方式的区别.
通过 file 协议加载 HTML
在 Electron 的官方入门例子中, 就是通过 file 协议来加载 HTML 的
通过 file 协议加载 HTML, 无论有没有网络, 都可以加载到 HTML 文件, 这是 file 协议核心优势. 缺点也比较明显:
如果页面资源要更新, 那么只能通过发版来解决(如果你用 webview, 那么 webview 的内容就可以自动更新, 不过 webview 也需要有网络才能加载)
在 file 协议下, 无法通过 Ajax 来请求数据 (协议不同), 只能通过 Node.JS 的 http(s) 模块来发起网络请求
通过 http 协议加载 HTML
通过 http 协议加载 HTML, 优点是可以随时通过 Web 页面的部署, 更新渲染进程的资源, 并且在 https 协议下, 你可以在页面中使用前端熟悉的 Ajax 请求来获取数据.
当然, 缺点也比较明显:
没有网络, 并且在你没有做 HTML 的缓存时, 你的窗口内容无法加载
必须通过 https 来加载, 保证页面内容的安全性
代码示例
方便读者更好理解上文的内容, 写了一个小 demo, 源代码地址 https://github.com/littlecold233/electron-demo, 例子有以下特点:
创建主窗口, 阻止关闭主窗口关闭的默认事件, 不销毁窗口.(大部分客户端的主窗口, 关闭主窗口的时候, 实际上是隐藏了该窗口, 例如 QQ, 微信)
应用退出时, 会尝试关闭所有窗口, 再退出应用. 如果主窗口的关闭行为默认事件被阻止, 那么会导致主窗口无法关闭, 整个应用无法退出. 因此使用 forceQuit 这个变量来控制.
使用 http 或者 file 协议加载窗口前端资源(例子中, 默认加载的是微信)
- const { App, BrowserWindow } = require('electron')
- async function main () {
- await App.whenReady();
- let forceQuit = false;
- const majorWindow = new BrowserWindow({
- title: '主窗口',
- width: 1000,
- height: 750,
- minWidth: 1000,
- minHeight: 750,
- backgroundColor: '#f2f2f2',
- }); // 主窗口
- // 阻止标题更新
- majorWindow.on('page-title-updated', (e) => {
- e.preventDefault();
- });
- majorWindow.on('close', (e) => {
- // 用户希望退出的时候, 不作处理, 默认会销毁这个窗口
- if (forceQuit) return;
- e.preventDefault();
- // macOS 全屏的处理
- if (majorWindow.isFullScreen()) {
- majorWindow.once('leave-full-screen', () => {
- majorWindow.hide();
- });
- majorWindow.setFullScreen(false);
- } else {
- majorWindow.hide(); // 隐藏窗口
- }
- });
- // 点击 dock 打开主窗口
- App.on('activate', () => {
- majorWindow.show();
- });
- // 用户使用 cmd + Q, 代码中调用 App.quit 等情况
- // 此时用户希望能够退出应用, 因此将 forceQuit 改为 true
- App.on('before-quit', () => {
- forceQuit = true;
- });
- App.dock.setIcon('./img/icon.png'); // 在 App 打包后, 这一句代码其实是不需要的
- majorWindow.loadURL('https://wx.qq.com'); // http 协议加载前端资源, 随便加载一个微信试试
- // majorWindow.loadURL('file://index.html'); // file 协议加载前端资源
- }
- main();
在本地跑一下这个例子
微信测试
最后
欢迎大家在评论区讨论, 技术交流 & 内推 -> zhongli@qunhemail.com
来源: http://www.jianshu.com/p/69649de51ae6