前言
本方案比较适用于 Windows 下的 Electron 应用,对于 Mac 下的 Electron 应用,其本身创建及加载页面的速度比较快,本方案带来收益并不是特别的大,当然对于一些特殊的场景,还是有一定的借鉴意义。
背景
在团队桌面应用的开发和维护的过程中,有同学提出了这样一个问题,“为什么运行一个桌面程序需要启动那么多的进程”。
首先,从 Electron 的架构来讲,这是一个必然的结果,Electron 继承了来自 Chromium 的多进程架构,这使得此框架在架构上非常相似于一个现代的网页浏览器,每打开一个新的界面都会创建一个新的渲染进程,即使你只需要有一个主窗口界面,它最少也得需要启动 4 个进程。
其次,大家看下下面这个表格,这个是基于 Electron-Fiddle 创建的 Electron 程序,只有一个窗口加载一个没有任何内容的 html 文件,其加载完成耗时如下所示:
| 平台 | 设备型号 | 窗口加载完成时间 (ms) |
|---|---|---|
| Windows | ||
| Mac |
在列表中,Windows 端 Electron 创建并完成窗口加载大约需要 6s 左右(生产环境可能会好点,但是也不会特别的快,也得 2~4s 左右,且随着你的文件越大,加载的越久),由于我们的交互设计需要尽量保持原生应用程序的交互方式,导致我们很多可以用 HTML DOM 模拟的窗口必须用真实窗口代替,为了更好的用户体验(降低用户白屏等待时间),决定在程序启动后就开始按照优先级创建后台窗口,当用户使用窗口时,就把该窗口从后台显示到前台。
随着业务的复杂度越来越高,需要提前渲染的窗口也逐渐增多,导致用户后台任务管理器中的进程数量越来越多,但是用户真正能够使用到的窗口只是我们预加载窗口的一部分,导致很多窗口虽然提前加载了,但是用户基本上都没有使用,导致程序占用了大量的内存,造成了资源的浪费,可能会对系统中其他的应用带来不好的影响。为了更好的平衡用户的操作体验及电脑资源利用,有没有更好的优化的方案可以解决上述的问题呢?答案是否定的,目前确实没有特别好的方案能够解决这个问题,唯一一个比较不错的、折衷的解决方案就是采用“窗口池”来权衡上述问题。
窗口池
窗口池其实就是可用窗口的集合,类似操作线程中的线程池,操作数据库连接的连接池,可以理解为是窗口的“缓存策略”。
在实践窗口池的时候,我的思想“魔怔了”,总是觉得进程的数量越少越好(当然这如果可以实现肯定非常不错),于是便有以下两种窗口池实现思路,其中思路二参考自《深入浅出 Electron 原理、工程与实践》这本书。
思路一
在手机操作系统中,应用程序可以在后台运行,应用在后台按照其活跃时间排序,比如你先后打开了 3 个 App 程序:微信、如流、网易云音乐,那么它们在任务台的排列顺序为 【微信】->【如流】->【网易云音乐】,如果此时你的后台可运行程序总量限制为 3,那么你要打开一个新的程序,就需要把其中一个程序给替换掉,那么关闭那个比较合适呢?答案肯定是关闭最久没有使用的那个程序,因为它可能被使用的机率较其他的应用较低(理想推测)。
根据上述栗子,我们可以简单的得出一个结论,如果我们在“有限”的空间中,需要合理的运用空间及保证用户的体验,我们需要主动淘汰一个最久,权重的最低的应用,这种操作方式有一个专有的名词——LRU(缓存淘汰)。
那么,对应到窗口池实现上,其思路如下,在应用创建的时候提前渲染 n 个空白的窗口并将其放入到一个集合中,当我们操作窗口时,从窗口池中取出一个空闲窗口(如果当前空闲窗口为 0,需要基于 LRU 释放窗口,并将释放的窗口返回),执行路由跳转,当窗口关闭时,将窗口放到窗口池子中。
思路二
在应用创建的时候提前渲染 n 个空白的窗口并将其放入到一个集合中,当我们操作窗口时,从窗口池中获取一个空闲窗口(取出窗口后直接创建一个新的窗口,将其补充到窗口池子中),执行路由跳转,当窗口关闭时,将其从窗口池中移除。
对于思路二,我们为什么在消费一个窗口后,就立即添加一个新窗口,而不是在关闭的时候往窗口池中补充一个新的窗口,主要原因是当你的应用程序有快速获取空闲窗口的操作时,窗口池很容易被消耗完毕,等你消耗完毕时你再去创建,时间就有点晚了,对于用户而言就是白色的屏幕(当然,如果用户愿意等上点时间,那也是没有任何问题的,但是在创建窗口的阶段,是没有骨架屏的,你会看到 白屏 ~ 骨架屏 ~ 业务场景),当然这样做的后果就是你有的后台会有 n 个不用的空闲进程,这也造成了一定的资源浪费。
以上就是实现窗口池的两种思路了,对于目前我们的应用程序而言,上述两个实现窗口池的思路都可以满足我们的需要,下面让我们来列个表来对比一下两者的优点和缺点。
优缺点对比
| 思路 | 优点 | 缺点 |
| 思路一 | 创建一次后无需再创建窗口,不需要再额外的申请 CPU 资源 | 1. 当无复用窗口时,会将已有窗口进行回收,需要维护回收逻辑; 2. 窗口复用时,会将最早使用的窗口回收掉,需要确保回收的窗口不会对你的业务造成影响。 |
| 思路二 | 维护起来相对简单,没有太多心智负担 | 1. 临界情况下,创建的窗口池可能会显示的很慢,极差的情况下会走一遍完整的创建到显示流程; 2. 每次消费一个窗口的时候,都需要在窗口池中补充一个窗口,需要申请内存资源,对CPU操作的频率会比方案一高; 3. 永远会有 n 个空闲的进程,会造成额外的资源浪费。 |
具体实现
相信你通过上述的思路,就可以写出符合自己需求的窗口池代码了,因为它的原理真的非常简单,在这里我就不贴大量的实现代码了,详细代码内容可以查看后续附录内容。
思考
窗口池方案其实并不是特别的好,它只是帮我们简单的应了应急(权衡用户体验和系统资源使用),但是它相对于我们目前的方案(进程常驻)已经好了很多。
总之,关于 Electron 的应用程序在 Windows 系统中运行比较慢的问题已经有好几年了,其中对于这块的 issue 也非常多,但是 Electron 官方表示其自身也无能为力,我们只能期待微软能帮我们解决这个问题了。
对于这个问题,前段时间出现了一个根本性的解决方案,只需要给 Windows Defender 病毒扫描项加一个白名单即可,我迫不及待进行了尝试,发现并不可以,可能是我电脑的问题,也有可能是我的操作问题,总之我没有得到和文章中相同的效果。
注意:对于上面描述的给 Windows Defender 病毒扫描项需要用户主动操作,如果你的电脑上安装了电脑管家,360 等杀毒软件,那么 Windows Defender 就会被这些软件所代替,理论上你需要让用户在这些软件信任区中添加白名单目录。
大家如果感兴趣可以参考这个可能的解决方案(注:我这边暂时没有验证通过,如果大家得到预期的效果后,可以来我的文章评论区下留言),希望对你们能有帮助,如果可以的话,上述的这些迂回方案完全不需要(此时你需要考虑的是如何让你的应用添加到用户杀毒软件的排除项当中),你的 Windows 用户也拥有将和 Mac 用户一样的高级体验。