2本提升Electron技能水平的好书!赠4本

共 7471字,需浏览 15分钟

 ·

2022-04-27 18:02


导读:这篇文章是写给那些已经可以娴熟的使用前端技术(HTML,CSS,JavaScript,Node.js)开发Electron桌面应用的开发者的。


参与评论留言,点赞前4名赠送技术图书(2选1)

  1. 深入浅出Electron:原理、工程与实践

  2. Electron实战:入门、进阶与性能优化

作者:刘晓伦

来源:华章计算机(hzbook_jsj)



最近陆续收到一些网友的反馈:



Electron上手才能知道,各路问题多如牛毛,想用的话,自己慢慢填坑吧。

我感觉这个库挺难用的……[飙泪笑]。



我感觉Electron是典型的上手容易进阶难。

开发Electron项目经常会碰到各种疑难杂症,真的非常沮丧。


其实网友反馈的这些问题,绝大部分并不是Electron的问题,而是下面两个问题:

  • 开发者写的代码有问题

  • 桌面应用开发没那么容易

 

接下来我们就聊聊这两个问题。



1

Electron很稳,大多数问题是开发者代码的问题



首先Electron是一个集成项目,它集成了Node.js、Chromium两个知名项目,自己也提供了一些API。如下图所示:


身为一个前端开发者,我们一定不会质疑Chromium和Node.js的稳定性,不然的话真的可以直接改行去做其他领域的程序员了,假设你不是前端开发者,看看现在Chrome的市场占有率,再看看从2009年一路走到现在并且构建了一个完整生态的Node.js项目,我想你也不会质疑它们。

 

回头再看Electron自己提供的一系列API,诸如访问硬件设备(屏幕、电源)、访问剪切板、访问系统通知、访问系统菜单之类的,这些API实在不会不会让你的应用出什么幺蛾子问题,而且一旦有问题,绝大多数情况下也能被及时的发现,及时的修复。

 

有些质疑者会说,那问题一定是出在Electron粘合Chromium和Node.js身上了。实际上Electron在这方面的实现方案是很谨慎的,选用的技术也都是成熟稳定的技术,Electron在跨进程通信方面,使用的就是Chromium的跨进程通信技术:命名管道。你可以在PowerShell里键入如下指令,看看你系统中有多少应用在用这个技术做进程间通信:


1get-childitem \\.\pipe\


Electron加载Node.js也非常简单粗暴,初始化好Node.js的执行环境后,就把自己的代码和用户的代码交给Node.js执行了。Electron提供的底层API,也是遵循Node.js的底层模块开发规范开发的。

 

笔者的《深入浅出Electron》第二章深入讲解Electron的内部原理:

所以,从原理上看,Electron是不会出太大问题的,而且Electron从2013年诞生(2014年正式开源)至今,马上也要10岁了,拥趸众多,阿里、腾讯、京东、网易、美团、拼多多、都在使用它开发桌面应用,有什么问题也会很快被发现,很快被修复。


既然Electron很稳,那么为什么我们基于Electron开发的应用不稳呢?



2

Electron开发进阶难,原生应用开发更难


如果你要开发一个桌面应用,无论你用什么技术,你都会面临一系列难题,下面我举几个例子:

  • 应用运行时间久了之后,内存消耗会不断增加,怎么办?

  • 应用会无缘无故崩溃怎么办?如何调试分析崩溃报告?

  • 服务端公开了TCP长链接接口,客户端该如何与之通信?

  • 如何让我的客户端与另一个客户端进行异构跨进程通信?

  • 如何在客户端渲染展示海量数据?

  • 如何分析并优化客户端的性能短板?

  • 如何灰度发布?如何升级应用,线上产品如何热更新?如何多版本共存?

 

想想看,这些问题和你是不是选了Electron有关系吗?就算你用Qt、GTK、FLTK之类的原生框架开发桌面应用,这些问题就会变少或者不存在吗?

 

我可以负责任的告诉你,不会的,只会变的更多,而且处理这些问题的方法只会变得更复杂,更难以控制。非但这些问题不会变少,那些原生框架还会带来更多的新问题,比如Qt在高分屏下缩放显示上的问题:https://www.zhihu.com/question/451021591/answer/1896642195。

 

反观Electron,在处理这些问题上就好的多,崩溃报告有专门的崩溃报告记录支持,分析崩溃报告也有相应的工具。性能分析有Electron的contentTracing和Chromium的开发者工具。渲染海量数据是纯前端的问题,异构进程通信和服务端通信都是Node.js手到擒来的小Case。所有这些问题处理起来虽然也不容易,但毕竟比原生应用开发要简单多了,笔者的《深入浅出Electron》会有详细讲解。



3

Electron开发者进阶该学啥



1、底层原理


对一些小微型应用或者自用的小工具来说,不了解底层原理,没什么问题,功能实现了就好。但对于一些大型应用来说,开发者如果不了解底层原理,就很难持久维护你的产品。

 

比如,如果你不知道Chromium的多进程架构,你就很难理解Electron的主进程和渲染进程机制,如果你不了解asar文件的捆扎原理,你就很难理解Electron是如何读取并执行asar文件内的脚本资源的。

 

了解了Electron的原理,你就有能力对Electron的源码做修改,比如你想要更大限度的保护你的源码,那么你就可以修改Electron解析asar文件的逻辑,自己编译一个私有的Electron可执行文件。

 

这里说的底层原理,除了Chromium、Node.js和Electron的原理外,还应该了解Electron生态的重要库的原理,比如electron-builder,electron-updater等(前提是你打算使用它们),因为只有懂了他们的原理之后,你才能更从容的控制他们。

 

比如,你想在生成安装包的时候给应用程序的可执行文件签名,如果不了解electron-builder的原理的话,很难做这类控制。你不了解electron-updater的原理的话,你就很难排查为什么你的用户一直升级不成功(有可能是一个叫pending的目录没有执行权限哦)。

类似这种案例还有很多,只有掌握原理,你才可以无惧产品经理提出的各种奇葩需求。这方面相关的内容可以参考《深入浅出Electron》第3-4章。



2、原生模块开发


Electron虽然提供了很多操作系统底层的支持,但它终究不是万能的,很多很多应用的需求还是需要系统底层API的支持才能实现,打个比方:你需要遍历当前用户桌面上所有窗口的窗口句柄,如何做呢?

 

Electron是没有这个API的,这不怪Electron,因为它没办法把操作系统所有底层的API都封装一遍,要想完成这个工作,就只能开发一个原生模块。

 

这里需要特别说明一下,开发一个Node.js的原生模块由很多坑,以前Node.js原生模块的开发者都是使用NAN框架(NativeAbstractions for Node.js)来完成工作的,后来Node.js推出了Node-API(以前叫N-API,由于冒犯了有色人种而改名为此),Node-API专门用于构建原生模块,它独立于底层JavaScript运行时,并作为Node.js的一部分进行维护。

 

基于Node-API开发原生模块仍存在两种方式(可选路径如此之多,真是劝退初学者),一种方法就是使用C语言开发,由于Node-API就是用C语言封装的,所以这种方法更为直接,但由于C语言过于简单直接,语言特性较少,所以开发起来显得非常麻烦。

 

另一种方式是基于node-addon-api项目(https://github.com/nodejs/node-addon-api)使用C++语言开发,node-addon-api项目是对Node-API的C++再包装,这种方式可以精简很多代码,下面我们通过创建一个JavaScript对象为例来对比一下这两种写法的不同。

 

先来看使用C语言完成这项工作的代码:


 1napi_statusstatus;
2
3napi_valueobject, string;
4
5status= napi_create_object(env, &object);
6
7if(status != napi_ok) {
8
9  napi_throw_error(env, ...);
10
11  return;
12
13}
14
15status= napi_create_string_utf8(env, "bar", NAPI_AUTO_LENGTH, &string);
16
17if(status != napi_ok) {
18
19  napi_throw_error(env, ...);
20
21  return;
22
23}
24
25status= napi_set_named_property(env, object, "foo"string);
26
27if(status != napi_ok) {
28
29  napi_throw_error(env, ...);
30
31  return;
32
33}


在这段代码中,首先通过napi_create_object接口创建对象,然后通过napi_create_string_utf8接口创建字符串,最后通过napi_set_named_property接口为对象附加一个属性,并把这个属性的值设置为字符串的值,这段逻辑不但包括复杂的API使用还包括好几处错误检查,代码非常冗长拖沓。

 

再来看一下C++语言完成此项工作的代码:


1Objectobj = Object::New(env);
2
3obj["foo"]= String::New(env, "bar");


仅此这两句话,就把需求实现了,第一句创建对象,第二句为对象属性赋值,非常精炼直接。更多信息请参考《深入浅出Electron》第16章。




3、分析和调试


当我们把应用分发给用户后,经常会收到这样那样的问题反馈,最常见的就是应用程序无缘无故的崩溃了,那么这种时候该如何排查定位问题呢?这就需要分析Electron的崩溃报告了。

 

开发者可以通过如下代码让Electron框架追踪并记录崩溃报告,


 1import{ app, protocol, crashReporter } from 'electron'
2
3importos from 'os'
4
5import{MainWindow} from './MainWindow'  //自定义的主窗口类
6
7letmainWin
8
9app.on('ready',() => {
10
11  crashReporter.start({ submitURL: '',uploadToServer: false })
12
13  //.....
14
15})


在上面的代码中我们调用了crashReporter对象的start方法,开始追踪崩溃异常,一旦应用程序崩溃,将会在如下目录下生成崩溃报告文件:


1C:\Users\[yourOSUserName]\AppData\Roaming\[yourAppName]\Crashpad\reports


崩溃报告是一个以.dmp扩展名结尾的文件。分析这个崩溃报告的方法请参阅《深入浅出Electron》。

 

开发者使用Electron提供的开机自启动API,为应用程序设置了开机自启动功能,那么在Windows操作系统下,用户注册表此路径(计算机\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run)下会增加如下键值对:

键:electron.app.[yourAppName]

值:C:\Program Files(x86)\[yourAppName]\[yourAppName].exe

 

知道了这个,你就知道为什么你用如下代码设置了开机自启动,而应用程序没有开机自启动的原因了:


1import{ app } from "electron";
2
3app.setLoginItemSettings({
4
5      openAtLogin: true
6
7})


除此之外,卸载程序在注册表里的键值是什么呢?如何逆向调试一个Electron应用呢?如何使用开发者调试工具分析应用程序的内存消耗呢?如何分析首屏加载时间呢?请参阅《深入浅出Electron》。


4、大型工程控制


了解了底层原理,能应对各种特殊的需求和问题,那么我想你距离成为一个Electron开发高手仅剩一步之遥了。当然这也是最难的一步。这一步就是学会如何驾驭大型Electron应用。

 

项目规模一大,很容易代码变成“屎山”,各种业务逻辑交织在一起,牵一发而动全身,谁也不敢改前人写的东西,都是以打补丁的心态开发,补丁叠着补丁,最终成为“屎山”。

 

其实后端分层、分模块的思路可以借鉴到客户端来改善客户端的架构方式,比如:

也可以按职责来进行拆分,比如:


 1projectName
2
3├─resource(需要打包到安装包内的资源,这些资源不会被编译)
4
5│ │ ├─ release (会被原封不动复制到客户端电脑上的资源)
6
7│ │ │ ├─ img(图形,字体图标等)
8
9│ │ │ ├─ dll(可执行文件和二进制资源)
10
11│ │ └─└─ other(其他资源文件)
12
13│ └─└─ unrelease (会被内嵌到安装包内的资源,如应用图标、安装界面图片等)
14
15├─ script(工程调试、编译过程中需要使用的脚本文件)
16
17│ ├─ common(各种环境公用的工具脚本)
18
19│ ├─ dev(开发环境的启动脚本与环境变量)
20
21│ ├─ test(测试环境的启动脚本与环境变量)
22
23│ └─ release(生产环境的启动脚本与环境变量、签名脚本、nsis脚本等)
24
25├─ node_modules/(依赖包目录)
26
27├─ test/(单元测试代码存放目录)
28
29├─ release(应用打包生成的安装包、应用升级文件、打包中间过程文件存放目录)
30
31│ ├─ bundled(应用打包前所有脚本与静态资源编译捆绑后的文件存放目录)
32
33│ ├─ win-unpacked(绿色版可执行程序及相关资源存放目录)
34
35│ └─(打包后的安装包文件、应用升级文件等)
36
37├─ src(源码)
38
39│ ├─ common(主进程与渲染进程公用的源码)
40
41│ │ ├─(事件发射接收器、字符串处理、日期处理、加解密等)
42
43│ ├─ main(主进程源码)
44
45│ │ ├─ utils(工具类:协议注册、剪切板、日志等)
46
47│ │ ├─ widgets(界面辅助部件:托盘图标、系统菜单等)
48
49│ │ ├─ diaologs(所有弹窗类存放目录,文件保存、目录打开等弹窗)
50
51│ │ ├─ windows(所有窗口类存放目录)
52
53│ └─└─ app.ts(主进程入口文件)
54
55│ ├─ renderer(渲染进程源码)
56
57│ │ ├─ assets(随Vue一起编译打包的静态资源)
58
59│ │ ├─ components(全局公共组件)
60
61│ │ ├─ pages(整个应用的所有页面,包含子页面或子控件则以页面名设置子目录)
62
63│ │ ├─ store(放置公共模块,如vuex)
64
65│ │ ├─ utils (工具类:toast、alert、i18n等)
66
67│ │ ├─ main.vue(渲染进程入口界面)
68
69│ └─└─ main.ts(渲染进程入口文件)
70
71├─ .vscode(vscode配置文件)
72
73├─ static(静态文件)
74
75├─ test(端到端测试逻辑)
76
77├─ index.html(渲染进程容器页面)
78
79├─ .prettier(beautify的配置文件,用于团队源码风格一致)
80
81├─ .npmrc(项目环境变量,主要是一些镜像源地址的配置)
82
83├─ .gitignore(git排除文件)
84
85└─ package.json


如果项目规模还在变大,我建议你去学习VSCode的源码,它的实现涉及到了很多设计模式的东西。


 

4

立足桌面应用开发,盯紧各个技术方向


可以说桌面应用开发包罗万象,有的应用里涉及到音视频编解码技术,有的应用里涉及到人工智能、大数据分析技术,有的应用则涉及到图像学、VR、AR等技术,所以掌握了桌面应用开发技术,还不是成为桌面应用开发高手的终点,应该立足桌面应用开发,展望各个技术领域,让自己始终保持核心竞争力。



5

推荐阅读



《深入浅出Electron:原理、工程与实践》


这是一本能帮助读者夯实Electron基础进而开发出稳定、健壮的Electron应用的著作。基于作者丰富且真实的 Electron 产品研发经历,给出了交付可靠Electron软件的宝贵经验,书中以“如何基于Electron开发桌面应用”为主线,对Electron的工作原理、大型工程构建、常见技术方案、周边生态工具等进行了细致、深入地讲解。如果你是真正将产品交付到用户手中的技术从业人员,这本书是你不可或缺的。


《Electron实战:入门、进阶与性能优化》

本书以实战为导向,讲解了如何用Electron结合现代前端技术来开发桌面应用。不仅全面介绍了Electron入门需要掌握的功能和原理,而且还针对Electron开发中的重点和难点进行了重点讲解,旨在帮助读者快速从入门到进阶。

 


关于作者:

刘晓伦,《Electron实战》、《深入浅出Electron》作者。Electron及其相关技术在企业应用领域的早期实践者,有十余年前端及C++(Qt)的开发经验,深入研究过Chromium的源码及相关的协议(DevTools Protocol和V8Debugger Protocol),其主导研发的产品为数家世界五百强企业提供服务。



参与评论留言,分享心得与收获!
获得点赞前4的伙伴
赠送图书1本(二选一)
《Electron实战》&《深入浅出Electron》
活动截止日期
2022年4月24 12:00 星期日

浏览 16
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报