
網際網路出現之前,C/S 架構是軟體産品的主流,後面漸漸地被 B/S 架構所取代(因為不需要配置用戶端),但由于浏覽器有重新整理機制,伺服器的負載等因素,C/S 架構的響應速度和流暢性是好于 B/S 架構的,是以現在軟體開發的趨勢是兩者的融合,一般是 B/S 架構開發的産品可以非常友善地轉移到 C/S 架構下。用戶端(client)是 C/S 架構軟體産品中重要的一部分,除了和使用者互動、本地處理資料的強大功能,順暢的體驗和美觀的樣式也是用戶端技術追求的目标。這裡和大家介紹桌面版應用程式的一些曆史和現在比較流行的 electron 技術。
桌面版應用程式曆史
桌面應用程式,又稱為 GUI 程式。可以分為以下幾個階段:
VB,上古程式員的開發工具,曾經全球第一的開發語言,拖拽式的圖形化開發讓它成為極佳的桌面開發工具。微軟依靠其作業系統的優勢,一直壓制同時期的競争對手 delphi。微政策早期應用該技術,開發了管理智能商務平台的大殺器 developer。
C++、win32API 的 MFC 方案是基于視窗中組合控件和消息傳遞機制。這也是 20 多年前的技術,是以 API 設計的不是很友好。幾年前微軟已經停止維護,簡單來說它已經過時了。
Winform 微政策幾年前基于該技術研發第一代的 Desktop 版本,但是從開發體驗角度來說自定義、美化控件會比較麻煩。
C# .net framework,代表就是 WPF,它的原生特性是其他類庫無法比拟的:High DPI、Split Screen 以及對 DirectX 的天然優勢。但是并不開源,需要依賴.net 架構,還有就是啟動會比較慢。Workstation Windows 的新用戶端就是基于該技術研發。
Java swing/javaFx,這是一類比較大的陣營,優勢是跨平台和流行開發語言 Java 的天然結合,但開發出來的界面作者個人認為并不美觀。
C++ Qt,這是很多用戶端跨平台的首選,因為開源、UI 庫和各種功能的類庫非常豐富,但是學習成本比較高。
C++ duilib,這是 windows 下開源的 directUI(微軟提出的分離 UI 和邏輯的思想)庫,它是迎合網際網路桌面軟體小而美的趨勢發展起來的,可能大家對它的關注度比較少。但是用它開發出的産品大名鼎鼎,比如 QQ、微信、愛奇藝等很多知名度高的軟體。
Objective-c/swift cocoa,這是 mac 平台下的方案。可以友善調用底層的 API,缺點是不跨平台,文檔不友好,UI 庫并不豐富。現在這種方式開發的越來越少了。
基于 Web 技術的桌面應用開發
從 B/S 和 C/S 架構逐漸融合的角度來說,基于 Web 技術進行桌面程式的開發漸漸變成了主流。因為對界面的代碼部分可以做到複用。
這類技術早期的方案是用 vb 内嵌 webBrowser 控件,基于 IE 核心,正好很多網頁開發也有用 activeX 的需求,但這種方式具有明顯的缺陷——非常依賴于使用者的環境,會因為元件缺失導緻程式各種崩潰。第二類是嵌入式網頁架構,這類技術主要是基于浏覽器引擎實作 UI 渲染。比較典型的就是 appkit 上面 UIWebView 和 CEF(chro-mium embeded framework)。這種方法可以使用網頁 HTML5+CSS 實作各種酷炫的效果,但是缺點也比較明顯,就是桌面程式裡面嵌入了一個類似 Chrome 的浏覽器,記憶體的開銷會比較大。
後面出現了 nwjs 和 electron,electron 相比 CEF 有了單獨執行 js 的 v8 引擎,可以運作 NodeJS 來完成伺服器端功能,通過和内部浏覽器的 v8 引擎互動可以實作一個獨立的用戶端,這不同于 CEF 需要寄宿在其他程式内部。
Electron
用 Electron 來做桌面程式開發的優勢明顯,相當于是完全的網頁程式設計,有 Web 開發經驗的前端開發上手非常容易。Web 開發生态廣泛,開發成本低,可擴充性強,一些流行的前端架構例如 React、Angular、Vue 都可以和 electron 結合進行開發。另外它也具備和 Qt 一樣跨平台的優良特性。對性能要求不高的桌面版程式來說,一份代碼同時得到網頁版和各個平台的桌面版,開發的效率是其他方案無法比的。可以說,這是大部分人看好的趨勢。
和 Web 開發的差別
前端開發的一個痛點就是經常需要考慮多種浏覽器之間的相容,但是使用 electron 開發則不存在這樣的問題,它隻需要考慮 electron 中對應 Chrome 的版本。另一個使用 electron 解決的痛點是跨域,它可以繞過用戶端直接通過 NodeJS 裡的 request 通信子產品送出請求,這樣就無需被跨域所困擾。
擴充能力
node-ffi 可以在 NodeJS 環境中調用動态連結庫接口。通過這種方式,我們可以把 JavaScript 方法映射到動态連結庫接口,進而實作 C++ 類庫的接入。
Electron 運作原理
Electron 的運作機制可以從兩種程序說起:主程序和渲染程序。運作 package.json 的稱為主程序,它可以負責建立渲染程序(多個)。下圖是一段主程序代碼,主程序負責建立一個浏覽器執行個體來加載網頁。每個建立的浏覽器執行個體都在它自己的渲染程序内傳回一個 Web 頁面。當 BrowserWindow 執行個體銷毀時,相應的渲染程序也會終止。主程序負責掌管所有的 Web 頁面和它們相應的渲染程序。每個渲染程序都是互相獨立的,它們隻關心自己所運作的 Web 頁面。
<code>const electron = require('electron');const app = electron.app;const BrowserWindow = electron.BrowserWindow;var window = null;app.on('ready', function() { window = new BrowserWindow({width: 800, height: 600}); window.loadURL('<https://microstrategy.com>');});</code>
複制代碼
主程序還負責應用程式的生命周期(app 打開退出)和一些 app 事件的監聽,同時負責系統底層 API 的調用。建立的渲染程序用來展示 HTML + CSS 技術編寫的 Web 頁面, 同時可以運作 JavaScript 實作互動,可以把渲染程序簡單了解為 Chrome 上打開的一個浏覽器程序。
<code><html> <body> <script> const remote = require('electron').remote; console.log(remote.app.getVersion()); </script> </body></html></code>
在渲染程序中是不允許調用原生 GUI 相關的 API,那是因為在網頁中掌管原生 GUI 很危險,易造成記憶體洩露。如果你想在網頁中進行 GUI 的操作,渲染程序必須向主程序傳達請求,然後在主程序中完成操作。
Electron 封裝了一系列自己的 API 可供主程序和渲染程序調用。可以通過如下方式擷取:
<code>const {app, BrowserWindow, ipcMain, ... } = require('electron'); // 僅主程序可用const {ipcRender, remote, ... } = require('electron'); // 僅渲染程序可用</code>
主程序和渲染程序均可直接調用 NodeJS 的 API:
<code>node1.addEventListener('click', () => { console.log(os.path.basename('xxxx');})</code>
程序通信
通信本質上基于 nodeJS 的事件基礎類 Event-Emitter,ipcMain 和 ipcRender 都是該類的執行個體,通過事件模型需要的接口方法,采用了釋出 / 訂閱的方式來發送和監聽消息。
異步方式:
<code>import { ipcRender } from 'electron'; // 在渲染程序引入 ipcRender,它是 EventEmitter 的一個執行個體ipcRender.send('async', 'I am from webview'); // 可以異步發送</code>
同步方式:
<code>import { ipcRender } from 'electron'; // 在渲染程序引入 ipcRenderipcRender.sendSync('sync', 'I am sync sent from webview'); // 同步方式</code>
通過同步方式發送會 block 整個渲染程序,直到主程序響應。
主程序會加載 ipcMain 來進行監聽。
<code>import { ipcMain } from 'electron'; // 在主程序引入 ipcMainipcMain.on('async', (event, info) { console.log(info);});</code>
除此以外, electron 還提供了一種簡單的方案(remote 子產品)來實作渲染程序和主程序的通信。這樣就可以不必顯示地發送消息。
<code>import { remote } from 'electron'; // 在渲染程序引入 remoteremote.mainProcessObject.invokeMethod(); // 調用主程序才有的方法});</code>
主程序通過找到渲染程序對應的 browserWindow 就可以發送消息。
<code>constant window = BrowserWindow.fromId(global.id); // 通過 id 來進行對應window.webContents.send('msg', 'hello world');</code>
渲染程序監聽:
<code>ipcRender.on('msg', (event, info) => { console.log(info)});</code>
一種可行的方案是在主程序實作消息的轉發。還有比較簡單的方案是通過浏覽器實作的 HTML API,比如 localStorage, sessionStorage 或者 IndexedDB,或者通過主程序建立全局變量 sharedObject 來實作渲染程序的資料共享。
以上是一些 electron 技術的初步介紹,有興趣的讀者可以繼續閱讀詳細的官方文檔來深入學習。目前來看 electron 不能算一個年輕的開源項目, 還有不少新的桌面替代技術在湧現(比如 miniblink)。
作者介紹:
徐瑞青,進階軟體工程師,畢業于魯汶大學電子工程系。2014 年加入微政策,目前在資料 gateway 部門參與資料模組化、清洗的開發工作,也參與過 Workstation 資料導入的早期開發。
本文轉載自微信公衆号:微政策 商業智能