我是如何從零學(xué)習(xí)開發(fā)一款跨平臺桌面軟件的
原始沖動
最近一直在學(xué)習(xí) Electron 開發(fā)桌面應(yīng)用程序,目的是想做一個桌面編輯器,雖然一直在使用Typore這款神器,但無奈Typore太過國際化,在國內(nèi)水土不服,無法滿足我的一些需求。
比如實現(xiàn)本地圖片上傳到云端(mac版可以借助iPic),無法幫我把本地圖片和文章一起發(fā)布到博客園、CSDN、SegmentFault、掘金等國內(nèi)知名博客平臺,要么使用一些免費或付費的圖床,借助類似iPic的工具,把圖片一鍵上傳到云端。
我個人也嘗試過七牛云的免費10G存儲空間,但是說實話,這些免費的空間到最后一定是為了讓你成為付費用戶,各種限制各種吐槽在網(wǎng)上很容易可以搜索到。
免費的圖床如新浪微博等,還算是比較好的圖床工具,相比一些網(wǎng)絡(luò)上的壓根不知道啥公司甚至是歸屬個人的免費圖床,新浪應(yīng)該是比較靠譜的,相對來說可以保證圖片的存活時間,我個人用過一些免費的圖床網(wǎng)站,記得印象深刻的就是服務(wù)器出問題,網(wǎng)站掛個公告,曾經(jīng)的圖片再去訪問就是默認(rèn)的404。
雖然新浪家大業(yè)大不是說倒閉就倒閉的,圖片相對穩(wěn)定可靠,不過新浪的圖片服務(wù)器會檢測訪問來源Referer來防止外部網(wǎng)站引用,造成訪問403。
總結(jié)起來就是一句話,圖片還是隨著文章一鍵發(fā)布到博客平臺比較好。要丟一起丟~
心理掙扎
緣起這個動機(jī),但是下定決心依舊是困難重重。
我個人是一個Java工程師,雖說搞過Andorid、HTML前端,但對前端深感不適的我果斷放棄了。對于桌面程序開發(fā),我連Swing都不會,造一個Markdown編輯器有點難,何況還要加上這些定制功能。
猶猶豫豫,還是決定去嘗試一下。于是調(diào)研寫跨平臺的一些途徑。
先嘗試Swing,不過Swing不好實現(xiàn)我期望的一些功能,改成JavaFX倒是可以,不過說實話,寫起來很累,太過繁瑣,就放棄了。最后把目光瞄向electron,就它了,HTML+Js+Css,聽起來就很簡單,事實證明,無論是測試還是打包都很方便。
決定之后,便開始進(jìn)行 Electron 的系統(tǒng)學(xué)習(xí)。
邁出第一步
第一步就是安裝 Electron 的本地開發(fā)環(huán)境,這也是大多數(shù)應(yīng)用開發(fā)的第一步。
你需要安裝 Node.js 在你的本地電腦,Electron 也是依賴于 Node.js 的環(huán)境,嚴(yán)格來說, Electron 通過將 Chromium 和 Node.js 合并到同一個運行時環(huán)境中,并將其打包為Mac,Windows和Linux系統(tǒng)下的應(yīng)用來實現(xiàn)這一目的。
關(guān)于 Electron 的具體開發(fā)流程,這里不再贅述,你完全可以在開發(fā)中使用Web前端開發(fā)的思維,除了在處理多個窗口之間交互的時候,就不得不了解Eelctron的進(jìn)程機(jī)制。
主進(jìn)程和渲染進(jìn)程
Electron 運行 package.json 的 main 腳本的進(jìn)程被稱為主進(jìn)程。在主進(jìn)程中運行的腳本通過創(chuàng)建web頁面來展示用戶界面。一個 Electron 應(yīng)用總是有且只有一個主進(jìn)程。
由于 Electron 使用了 Chromium 來展示 web 頁面,所以 Chromium 的多進(jìn)程架構(gòu)也被使用到。每個 Electron 中的 web 頁面運行在它自己的渲染進(jìn)程中。
在普通的瀏覽器中,web頁面通常在沙盒環(huán)境中運行,并且無法訪問操作系統(tǒng)的原生資源。然而 Electron 的用戶在 Node.js 的 API 支持下可以在頁面中和操作系統(tǒng)進(jìn)行一些底層交互。
主進(jìn)程與渲染進(jìn)程的區(qū)別
主進(jìn)程使用 BrowserWindow 實例創(chuàng)建頁面。每個 BrowserWindow 實例都在自己的渲染進(jìn)程里運行頁面。當(dāng)一個 BrowserWindow 實例被銷毀后,相應(yīng)的渲染進(jìn)程也會被終止。
主進(jìn)程管理所有的web頁面和它們對應(yīng)的渲染進(jìn)程。每個渲染進(jìn)程都是獨立的,它只關(guān)心它所運行的 web 頁面。
在頁面中調(diào)用與 GUI 相關(guān)的原生 API 是不被允許的,因為在 web 頁面里操作原生的 GUI 資源是非常危險的,而且容易造成資源泄露。如果你想在 web 頁面里使用 GUI 操作,其對應(yīng)的渲染進(jìn)程必須與主進(jìn)程進(jìn)行通訊,請求主進(jìn)程進(jìn)行相關(guān)的 GUI 操作。
主進(jìn)程與渲染進(jìn)程通信
那么進(jìn)程間如何通訊?
Electron為主進(jìn)程( main process)和渲染器進(jìn)程(renderer processes)通信提供了多種實現(xiàn)方式,如可以使用ipcRenderer 和 ipcMain模塊發(fā)送消息,使用 remote模塊進(jìn)行RPC方式通信。
你還可以用 Electron 內(nèi)的 IPC 機(jī)制實現(xiàn)。將數(shù)據(jù)存在主進(jìn)程的某個全局變量中,然后在多個渲染進(jìn)程中使用 remote 模塊來訪問它。
- // 在主進(jìn)程中
- global.sharedObject = {
- someProperty: 'default value'
- }
- // 在第一個頁面中
- require('electron').remote.getGlobal('sharedObject').someProperty = 'new value'
- // 在第二個頁面中
- console.log(require('electron').remote.getGlobal('sharedObject').someProperty)
使用Electron的API
Electron在主進(jìn)程和渲染進(jìn)程中提供了大量API去幫助開發(fā)桌面應(yīng)用程序, 在主進(jìn)程和渲染進(jìn)程中,你可以通過require的方式將其包含在模塊中以此,獲取Electron的API
- const electron = require('electron')
所有Electron的API都被指派給一種進(jìn)程類型。許多API只能被用于主進(jìn)程或渲染進(jìn)程中,但其中一些API可以同時在上述兩種進(jìn)程中使用。每一個API的文檔都將聲明你可以在哪種進(jìn)程中使用該API。
Electron中的窗口是使用BrowserWindow類型創(chuàng)建的一個實例, 它只能在主進(jìn)程中使用。
- // 這樣寫在主進(jìn)程會有用,但是在渲染進(jìn)程中會提示'未定義'
- const { BrowserWindow } = require('electron')
- const win = new BrowserWindow()
因為進(jìn)程之間的通信是被允許的, 所以渲染進(jìn)程可以調(diào)用主進(jìn)程來執(zhí)行任務(wù)。Electron通過remote模塊暴露一些通常只能在主進(jìn)程中獲取到的API。為了在渲染進(jìn)程中創(chuàng)建一個BrowserWindow的實例,我們通常使用remote模塊為中間件:
- // 這樣寫在渲染進(jìn)程中時行得通的,但是在主進(jìn)程中是'未定義'
- const { remote } = require('electron')
- const { BrowserWindow } = remote
- const win = new BrowserWindow()
使用Node.js的API
Electron同時在主進(jìn)程和渲染進(jìn)程中對Node.js 暴露了所有的接口。這里有兩個重要的定義:
1)所有在Node.js可以使用的API,在Electron中同樣可以使用。在Electron中調(diào)用如下代碼是有用的:
- const fs = require('fs')
- const root = fs.readdirSync('/')
- // 這會打印出磁盤根級別的所有文件
- // 同時包含'/'和'C:\'。
- console.log(root)
正如您可能已經(jīng)猜到的那樣,如果您嘗試加載遠(yuǎn)程內(nèi)容, 這會帶來重要的安全隱患。您可以在我們的 安全文檔 中找到更多有關(guān)加載遠(yuǎn)程內(nèi)容的信息和指南。
2)你可以在你的應(yīng)用程序中使用Node.js的模塊。選擇您最喜歡的 npm 模塊。npm 提供了目前世界上最大的開源代碼庫,那里包含良好的維護(hù)、經(jīng)過測試的代碼,提供給服務(wù)器應(yīng)用程序的特色功能也提供給Electron。
例如,在你的應(yīng)用程序中要使用官方的AWS SDK,你需要首先安裝它的依賴:
- npm install --save aws-sdk
然后在你的Electron應(yīng)用中,通過require引入并使用該模塊,就像構(gòu)建Node.js應(yīng)用程序那樣:
- // 準(zhǔn)備好被使用的S3 client模塊
- const S3 = require('aws-sdk/clients/s3')
有一個非常重要的提示: 原生Node.js模塊 (即指,需要編譯源碼過后才能被使用的模塊) 需要在編譯后才能和Electron一起使用。
絕大多數(shù)的Node.js模塊都不是原生的, 在650000個模塊中只有400是原生的。當(dāng)然了,如果你的確需要原生模塊,可以查詢?nèi)绾沃匦聻镋lectron編譯原生模塊(很簡單)。
最終產(chǎn)品殺青落地
終于搞明白了 Electron 的應(yīng)用架構(gòu),那么接著就要進(jìn)入產(chǎn)品的開發(fā)階段。比較慶幸的是,ELectron 的UI完全由CSS+HTML組成,這部分可用的框架太多了,我選擇了又老又知名的 BootStarp 框架搭建界面UI,還引用了JS框架JQuery。選擇了 electron-store 作為本地存儲文件,至于最關(guān)鍵的Markdown語法解析,對比了一番主流解析框架,最終選擇了 markdown-it。貼一下效果圖:
這款軟件我給他起名為 JustWrite,意思就是現(xiàn)在就寫,也是在督促自己吧,畢竟猶豫徘徊,等于白來。
現(xiàn)在軟件的功能除了包含一鍵發(fā)布本地文章加本地圖片到博客園、CSDN、SegmentFault、掘金、開源中國等平臺,我還打算將他打造為一個體驗不錯的Markdown寫作軟件?,F(xiàn)在你閱讀的這篇文章,就是我使用 JustWrite 書寫的,使用的字體是我個人喜歡的幼圓體,除此之外,還有六款風(fēng)格迥異的字體可以切換使用。字號也是可以動態(tài)放大或者縮小,還可以關(guān)閉右側(cè)預(yù)覽,專注于寫作,如下圖所示:
這些截圖是我截屏后使用快捷鍵Ctrl+V一鍵粘貼的,圖片會自動放到當(dāng)前md文件所在目錄下的picture文件夾內(nèi)。
關(guān)于 JustWrite 從構(gòu)思到實踐的心路歷程大致就以上這些了,這次開發(fā) JustWrite 也讓我過了一把產(chǎn)品經(jīng)理的癮,基本已經(jīng)滿足了我的日常需求。如果你有更好的想法和創(chuàng)意也可以告訴我,說不定第二天就會實現(xiàn)了。
Github:https://github.com/yueshutong/JustWrite