Electron应用开发总结
这次又是工作总结,不过是关于Electron
的,之前工作花了差不多10个月做的产品,一直想写个填坑记录的,现在闲下来可以写写了。我们的产品是互联网化的,所以有很多内容也是没做过,一路也是遇到了很多坑。
1.0版本
之前虽然有做过Electron
的经历,但之前做的功能相对简单,前端一套东西就可以搞完,但这次不行了。第一个版本当时是用electron-vue
模板生成的,在完成产品基本功能后发行的1.0版本中,我们发现了大量的问题,比如
- 内存占用过大,软件用的时间越长越大,越来越卡。
- 从主界面打开子页面窗口时会出现很长时间的白屏,用户可能接受不了。
- 数据库(一开始用的是RxDB)一旦存储大量数据,读取起来就会很卡。
- 总之给用户的感受就是慢卡白屏。
然后就是开始填坑啊,一个一个来。
- 内存占用大,没办法
Electron
应用本身就比较耗内存,Windows版一上来开4个线程,再加上业务代码用到一些类库,所以内存消耗肯定不小。问题的关键是软件用时越长越卡,这个足足消耗了我几个星期去处理。一开始就发现是因为内存泄露的问题,而我自己也没太接触过内存泄露排查这块(自己之前写的代码印象中就没遇到过泄露,再加上自己对常见的泄露的可能性都做了规避),所以光排查问题就花了很长时间。对照网上提供的一些常见可能性都做了排除,学着如何去排查,感觉那段时间真的学到了不少东西啊。最后在别人的指导下找到问题是出在RxDB
身上,过程的艰辛就不提了,这是我提的issue地址。
问题是找到了,但是怎么解决呢?等待RxDB
修复需要时间,而自己找到的手动gc()
的方法似乎也不那么奏效,后来没办法,只有和其它一些问题一起重新开发1.1版本。 - 打开子页面会白屏这是必然的,因为新开一个窗口加载也需要时间,完成
Vue
的初始化、业务初始化也需要一段时间,那么这段时间注定是白屏,即使我可以在一开始加一个动画,但肯定还是会有一段白屏时间。而且由于打开多窗口,所以资源的消耗肯定也会增加,内存消耗也会上去。由于当时子窗口和主窗口用的是一个前端模板,所以要加初始化动画还不是那么简单,所以这个问题也就搁置了,加入到1.1版本中一起修复,而修复的方法就是主功能的页面不再单独打开子窗口,只有部分额外功能才会去新开且子窗口和主窗口使用2个不同的模板,减少子窗口渲染时间。 - 鉴于之前的开发经验,前端使用文档性数据库是主流,且更符合前端知识栈,但如果文档中有的字段内容特别多,那么读取大量数据比如会占用大量内存造成卡顿的情况。如果要解决这个问题需要使用支持只筛选某些字段的文档性数据库或者使用关系型数据库。和上一条结论一样,在1.1版本中更换数据库。
1.1版本
鉴于之前种种问题,用户抱怨不断,老板跟后催促修改,所以只得重新开发一套,在选型的时候就针对上面的问题重新定义开发栈。
- 项目结构模板,之前使用
electron-vue
项目生成,但是有些内容相对较老且那时vue-cli 3
刚好稳定,意识到这以后必然是趋势,所以就决定用它去初始化了。但cli生成的只是前端项目,所以又找到了vue-cli-plugin-electron-builder
插件去完成剩下的初始化内容。后来想想还挺冒险的,毕竟当时vue-cli-plugin-electron-builder
还只是alpha
版本。 - 前端还是
Vue
一套,后端还是NodeJs
一套,这个肯定不会改变。 - 数据库调研了很多,基本上文档性数据库都被pass,而关系型数据库只有
sqlite
可选,其它非嵌入式的数据库也都不考虑了。但我自己对于关系型数据库的查询已经忘得差不多了,可好在我们开发组有后端同学,所以后续的很多查询都是找他们帮忙的。另外关于sqlite
数据库其实应该做一层加密,sqlite
本身不支持加密,但有插件可以实现,然后后来因为懒
还是没做。还有一点,如果使用关系型数据库,那么数据库初始化是在main
进程做呢还是在renderer
进程做呢?从业务角度来说最终我肯定是要在renderer
进程里调用db
方法的。我一开始是打算在main
进程做的,这样从逻辑上更符合数据库的定位,然后试过通过global
暴露给renderer
进程调用,但是这样在序列化的过程中会破坏db
原有方法,也考虑过通过ipc
,但是如此大量的通讯代价肯定也是很高。一开始没考虑在renderer
进程做的另一个考虑是如果在子窗口初始化那么子窗口如何获取这个db实例呢?不过后来发现自己想多了,数据库可以多连接啊,而且我们的应用基本很少会出现多窗口同时写的问题,怕个啥,renderer
进程搞起。sqlite
确定后我们还需要一个orm
库,我们使用的是sequelize
,后来发现还有typeorm
,但我们的项目不是基于ts
的,所以相对来说也不太适合。但sequelize
搭配上webpack
有个坑就是你必须得把它的所有驱动都装上,很坑(测试使用window.require()
方式引入也不行,也许有更好的方案?)。 - 关于多窗口问题,和设计师保持沟通,改掉以前的交互模式,尽量在一个窗口完成常用操作,部分少见操作可以以子窗口打开,这样内存的消耗也会有所改善。
- 其它优化包括按需加载,延时加载等。
组件开发
拿到UI给出的界面,在完成基础架构后就开始准备基础组件开发了,我不喜欢用现成的UI框架,一来Electron
中使用的是Chromium
内核渲染,不存在兼容性问题,可以放心地使用各种新特性,二来自己有洁癖,希望组件层级尽量少,很多UI为了实现更复杂的功能,DOM层级一般较多,而DOM多对渲染本身也是有影响的。组件开发大致有3种:
- 基础组件,即与业务没有关联性,通过
props
和event
提供各种接口,常见的有按钮、输入框、单选框等表单相关、弹框、警告框、确认框等。这些组件放在components
或者ui
目录下,组件统一以Ui
或者自定义的单词开头,方便识别。 - 业务组件。一般由基础组件构成,可以使用
vuex
种的数据。比如窗体按钮(由基础窗体按钮和业务关来)、个人信息组件(由头像和用户名等信息组成)、用户详情信息(由弹框和一些交互按钮组成)等。这些组件放在modules
或者widgets
目录下,组件统一由M
或者W
开头。 - 页面组件,即单个UI页面,可以拆分成多个小组件,最后合并成一个大组件。这些组件放在
views
或者pages
目录下,子组件放在对应业务的子目录下,组件统一由V
或者P
开头。
严格来说组件开发完都要写测试用例,但我们没时间和精力弄这个,只能在开发的过程种测试功能。组件尽量使用原生标签,尽量减少DOM层级,props
可以多开放些,方便后续业务需要。
除了组件完,其它常有的directives
、filters
、utils
也可以在初期就加上,有时间的可以把这些通用的单独出来放到npm里方便以后继续使用。
业务开发
在我们完成组件开发后,页面实现起来就很快了,就像拼乐高玩具一样。当时我开发1.1版本时APP端已经在做新功能了,而我除了要把以前的功能都实现了,还要赶上他们的进度把新功能也给做上,这确实给了我很大压力。不过好在我们这种开发模式相对原生来说会快上很多,花了大概1个半月的时间完成了1.0版本的大部分内容,再花差不多1个月多一点的时间赶上了他们,后续的业务开发相比原生来说就轻松些。这得益于前端混合开发的便利性以及Vue
组件化开发的快速性。
原生模块
我想很多人被拦截在Electron
之外的一个原因是各种报错,我想装个sqlite3
存下数据,结果各种错误,按网上找的各种教程和方法都没用。问题还是出在原生模块上,像sqlite3
这种模块是需要在本地进行编译的,它们依赖当前的系统环境,而大多数人在编译这块就过不去了。
我的方法比较简单,首先得安装各个系统必备的编译工具,比如windows需要安装windows-build-tools。直接跳过文档中说的electron-rebuild
而是使用electron-builder
在postinstall
中执行electron-builder install-app-deps
,然后你的命令行最好是FQ的,可以直接访问谷歌的那种,后面就正常安装原生模块就可以了,也不需要设置什么npmrc
。
原生模块多了每次安装新依赖都需要重新build一遍(也许是我设置问题,可以跳过?)也挺麻烦的,所以有些功能能用js去做的都会用js去实现。
主题系统
我们的应用支持主题切换,在前端更换主题应该是个常见的需求,但这次我想做点不同的,既然我都是Chromium
了,那就试下css的新特性:root
吧。把我们需要和主题关联的颜色、字体、文字大小等都放到:root
中,然后在使用的地方直接写成prop: var(--someVariable)
即可。这么做的好处一来原生支持,二来可以统一变量,重复利用,甚至以后可以用cssnext
的特性去做颜色值计算等。
随着业务的增多,新的页面也越来越多,需要增加的颜色值也越来越多,因而我们的主题文件中定义的:root
里的变量也越来越多,到后来发现新增一个主题需要更改大量的颜色,而且还要调到各种页面各种业务状态下去看页面颜色是否正常。当时就想,这也不是个事啊,这以后要是加主题还不得累死我啊,能不能搞个工具,然后直接让设计师去调色,调好之后直接生成主题包文件呢?当然可以,说做就做,搞个子窗口,显示当前主题中的各种变量,显示它们的颜色并可以手动修改或者选择关联某个变量,并且要实现子窗口修改主窗口实时预览的效果。再加上可以设置主题图片,主题名,主题预览图等信息就OK拉,最后页面大致长这样:
因为主题中要设置的变量特别多,而且每次调整可能都要看下效果,所以设计师让我加了中途保存,下次打开自动应用的功能防止做了一半丢了。加了一个变量搜索,因为实在太多了。
不足的点及可优化的方向
- 变量仍然很多,有些特殊业务还需要特定条件才能显示,而这种页面调试起来就很麻烦了,理想的方法应该是可以模拟各种环境直接查看某些页面或者输出页面渲染图到文件。
- 子窗口的实时预览有点麻烦,需要在主窗口变量变动时通知所有的子窗口也去改变,而且自定义主题这个子窗口还不能受影响,否则整个调色过程会很难受,目前还没有做这块。
- 自定义主题应该以插件的形式导入到应用中,这样真实用户就没有该模块而且不会打包到项目中去,现在只是单纯的通过
if (process.env.NODE_ENV !== 'production') {}
来做区分。
自动更新
其它
数据存储
非npm得第三方库
本地备份
在线判断
快捷键
api签名
本地字体
webview获取信息
数据库升级
埋点统计
worker线程
数据格式转换
数据同步
自定义窗体
bug跟踪
证书签名
现有问题
数据统计
内容丢失
Electron应用开发总结