Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
選擇
優質文章,及時送達
本文原載于 SegmentFault 社區
作者:薛勤
原始沖動
最近一直在學習 Electron 開發桌面應用程序,目的是想做一個桌面編輯器,雖然一直在使用 Typora 這款神器,但無奈 Typora 太過國際化,在國內水土不服,無法滿足我的一些需求。
比如實現本地圖片上傳到云端(mac 版可以借助 iPic),無法幫我把本地圖片和文章一起發布到像 SegmentFault 等國內知名博客平臺,要么使用一些免費或付費的圖床,借助類似 iPic 的工具,把圖片一鍵上傳到云端。
我個人也嘗試過七牛云的免費 10G 存儲空間,但是說實話,這些免費的空間到最后一定是為了讓你成為付費用戶,各種限制各種吐槽在網上很容易可以搜索到。
免費的圖床如新浪微博等,還算是比較好的圖床工具,相比一些網絡上的壓根不知道啥公司甚至是歸屬個人的免費圖床,新浪應該是比較靠譜的,相對來說可以保證圖片的存活時間,我個人用過一些免費的圖床網站,記得印象深刻的就是服務器出問題,網站掛個公告,曾經的圖片再去訪問就是默認的 404。
雖然新浪家大業大不是說倒閉就倒閉的,圖片相對穩定可靠,不過新浪的圖片服務器會檢測訪問來源 Referer 來防止外部網站引用,造成訪問 403。
總結起來就是一句話,圖片還是隨著文章一鍵發布到博客平臺比較好。要丟一起丟~
心理掙扎
緣起這個動機,但是下定決心依舊是困難重重。
我個人是一個 Java 工程師,雖說搞過 Andorid、HTML 前端,但對前端深感不適的我果斷放棄了。對于桌面程序開發,我連 Swing 都不會,造一個 Markdown 編輯器有點難,何況還要加上這些定制功能。
猶猶豫豫,還是決定去嘗試一下。于是調研寫跨平臺的一些途徑。
先嘗試 Swing,不過 Swing 不好實現我期望的一些功能,改成 JavaFX 倒是可以,不過說實話,寫起來很累,太過繁瑣,就放棄了。最后把目光瞄向 electron,就它了,HTML+Js+Css,聽起來就很簡單,事實證明,無論是測試還是打包都很方便。
決定之后,便開始進行 Electron 的系統學習。
邁出第一步
第一步就是安裝 Electron 的本地開發環境,這也是大多數應用開發的第一步。
你需要安裝 Node.js 在你的本地電腦,Electron 也是依賴于 Node.js 的環境,嚴格來說, Electron 通過將 Chromium 和 Node.js 合并到同一個運行時環境中,并將其打包為Mac,Windows 和 Linux 系統下的應用來實現這一目的。
關于 Electron 的具體開發流程,這里不再贅述,你完全可以在開發中使用Web前端開發的思維,除了在處理多個窗口之間交互的時候,就不得不了解Eelctron的進程機制。
主進程和渲染進程
Electron 運行 package.json 的 main 腳本的進程被稱為主進程。在主進程中運行的腳本通過創建 web 頁面來展示用戶界面。一個 Electron 應用總是有且只有一個主進程。
由于 Electron 使用了 Chromium 來展示 web 頁面,所以 Chromium 的多進程架構也被使用到。每個 Electron 中的 web 頁面運行在它自己的渲染進程中。
在普通的瀏覽器中,web 頁面通常在沙盒環境中運行,并且無法訪問操作系統的原生資源。然而 Electron 的用戶在 Node.js 的 API 支持下可以在頁面中和操作系統進行一些底層交互。
主進程與渲染進程的區別
主進程使用 BrowserWindow 實例創建頁面。每個 BrowserWindow 實例都在自己的渲染進程里運行頁面。當一個 BrowserWindow 實例被銷毀后,相應的渲染進程也會被終止。
主進程管理所有的 web 頁面和它們對應的渲染進程。每個渲染進程都是獨立的,它只關心它所運行的 web 頁面。
在頁面中調用與 GUI 相關的原生 API 是不被允許的,因為在 web 頁面里操作原生的 GUI 資源是非常危險的,而且容易造成資源泄露。如果你想在 web 頁面里使用 GUI 操作,其對應的渲染進程必須與主進程進行通訊,請求主進程進行相關的 GUI 操作。
主進程與渲染進程通信
那么進程間如何通訊?
Electron 為主進程( main process)和渲染器進程(renderer processes)通信提供了多種實現方式,如可以使用 ipcRenderer 和 ipcMain 模塊發送消息,使用 remote 模塊進行 RPC 方式通信。
你還可以用 Electron 內的 IPC 機制實現。將數據存在主進程的某個全局變量中,然后在多個渲染進程中使用 remote 模塊來訪問它。
示例代碼:
// 在主進程中
global.sharedObject = {
someProperty: 'default value'
}
// 在第一個頁面中
require('electron').remote.getGlobal('sharedObject').someProperty = 'new value'
// 在第二個頁面中
console.log(require('electron').remote.getGlobal('sharedObject').someProperty)
使用 Electron 的 API
Electron 在主進程和渲染進程中提供了大量 API 去幫助開發桌面應用程序, 在主進程和渲染進程中,你可以通過require的方式將其包含在模塊中以此,獲取 Electron 的 API
const electron = require('electron')
所有 Electron 的 API 都被指派給一種進程類型。許多 API 只能被用于主進程或渲染進程中,但其中一些 API 可以同時在上述兩種進程中使用。每一個 API 的文檔都將聲明你可以在哪種進程中使用該 API。
Electron 中的窗口是使用 BrowserWindow 類型創建的一個實例, 它只能在主進程中使用。
// 這樣寫在主進程會有用,但是在渲染進程中會提示'未定義'
const { BrowserWindow } = require('electron')
const win = new BrowserWindow
因為進程之間的通信是被允許的, 所以渲染進程可以調用主進程來執行任務。Electron 通過 remote 模塊暴露一些通常只能在主進程中獲取到的 API。為了在渲染進程中創建一個 BrowserWindow 的實例,通常使用 remote 模塊為中間件:
// 這樣寫在渲染進程中時行得通的,但是在主進程中是'未定義'
const { remote } = require('electron')
const { BrowserWindow } = remote
const win = new BrowserWindow
Tips:關注微信公眾號:Java后端,每日技術博文推送。
使用 Node.js 的 API
Electron 同時在主進程和渲染進程中對 Node.js 暴露了所有的接口。這里有兩個重要的定義:
1) 所有在 Node.js 可以使用的 API,在 Electron 中同樣可以使用。在 Electron 中調用如下代碼是有用的:
const fs = require('fs')
const root = fs.readdirSync('/')
// 這會打印出磁盤根級別的所有文件
// 同時包含'/'和'C:\'。
console.log(root)
2) 你可以在你的應用程序中使用 Node.js 的模塊。選擇您最喜歡的 npm 模塊。npm 提供了目前世界上最大的開源代碼庫,那里包含良好的維護、經過測試的代碼,提供給服務器應用程序的特色功能也提供給 Electron。
例如,在你的應用程序中要使用官方的 AWS SDK,你需要首先安裝它的依賴:
npm install --save aws-sdk
然后在你的 Electron 應用中,通過 require 引入并使用該模塊,就像構建 Node.js 應用程序那樣:
// 準備好被使用的S3 client模塊
const S3 = require('aws-sdk/clients/s3')
有一個非常重要的提示: 原生 Node.js 模塊 (即指,需要編譯源碼過后才能被使用的模塊) 需要在編譯后才能和 Electron 一起使用。
最終產品殺青落地
終于搞明白了 Electron 的應用架構,那么接著就要進入產品的開發階段。比較慶幸的是,ELectron 的 UI 完全由 CSS+HTML 組成,這部分可用的框架太多了,我選擇了又老又知名的 BootStarp 框架搭建界面 UI,還引用了 JS 框架 JQuery。選擇了 electron-store 作為本地存儲文件,至于最關鍵的 Markdown 語法解析,對比了一番主流解析框架,最終選擇了 markdown-it。貼一下效果圖:
這款軟件我給他起名為 JustWrite,意思就是現在就寫,也是在督促自己吧,畢竟猶豫徘徊,等于白來。
現在軟件的功能除了包含一鍵發布本地文章加本地圖片到 SegmentFault 等平臺,我還打算將他打造為一個體驗不錯的 Markdown 寫作軟件。現在你閱讀的這篇文章,就是我使用 JustWrite 書寫的,使用的字體是我個人喜歡的幼圓體,除此之外,還有六款風格迥異的字體可以切換使用。字號也是可以動態放大或者縮小,還可以關閉右側預覽,專注于寫作,如下圖所示:
這些截圖是我截屏后使用快捷鍵 Ctrl+V 一鍵粘貼的,圖片會自動放到當前 md文件所在目錄下的 picture 文件夾內。
關于 JustWrite 從構思到實踐的心路歷程大致就以上這些了,這次開發 JustWrite 也讓我過了一把產品經理的癮,基本已經滿足了我的日常需求。如果你有更好的想法和創意也可以告訴我,說不定第二天就會實現了。
GitHub:
https://github.com/yueshutong/JustWrite
前我發了一篇文章講述跨平臺的GUI技術,其中提到了javafx。對此很多人表示疑惑,認為javafx是落伍的開發技術。對此,我想專門寫一篇文章來做個介紹。
其實很多人并沒有聽說過javafx。現在最新的java教材,在講到GUI技術的時候,還是以swing和awt為主。swing和awt是上一代的Java GUI技術,現在很多銀行、國企的嵌入式設備還跑著swing和awt寫出來的程序。javafx是為了替代swing而產生的庫,它同時支持Windows, MacOS, Linux三種平臺的客戶端程序開發。對于移動端(主要是IOS和Android),開源的javafxports以及相關的商業軟件歸于Gluon旗下,對于手機端做了額外的適配,使得javafx也可以在移動設備上運行。所以,javafx是真正的跨平臺客戶端開發技術。縱觀其他客戶端技術,Electron只支持Windows, MacOS和Linux,Qt對于移動端的支持尚不完善,C#開發GUI程序就更不必說了。
和傳統的java GUI技術相比,javafx在美觀度和運行效率上都有大幅度的提升。javafx對各平臺的GPU圖形API做了封裝,支持在各種平臺上的硬件加速,因此開發者完全可以使用javafx開發大型的三維系統軟件。下面是javafx兩個案例程序,分別展示了圖表和三維圖形。
javafx圖表
javafx 3D
javafx是典型的前后端分離的開發模式。通過fxml繪制界面,css修飾頁面的樣式,java程序則用作后端控制。這種模式與web開發是極為相似的,也提高了程序的可維護性。
有的同學可能會認為用java做客戶端程序不合理。但是Matlab、IDEA、Eclipse等軟件都是java寫出來的,而且它們都取得了重大的成功。其實技術是用來實現目的的,只要能完成需求,客戶不會管你用的什么技術實現的。而且由于javafx是真正跨平臺的GUI技術,開發軟件的時候,不需要給windows, Mac, Linux, Android, IOS分別配團隊,只需要一份代碼就可以了(或者只需要做少量平臺適配相關的修改)。Office、visual studio這種大型軟件,因為沒有使用跨平臺的技術開發,微軟現在想把它們遷移到新的平臺就非常困難。
2018年JDK11發布之后,Oracle將javafx歸于openjdk項目之中,目的是為了加速javafx的發展速度。目前,jdk8中集成了javafx,而后續版本的jdk則移除了javafx,目的是實現模塊化。如果想在jdk11中使用javafx,可以通過maven導入javafx的依賴,也可以自行下載javafx并放到jdk中,總體而言并不麻煩。不過初學者還是使用jdk8比較好,熟悉之后再升級到jdk11。
除了使用javafx庫中的組件進行開發以外,開發者還可以使用javafx中的webview開發程序,這也是很多微信小程序和移動端程序常用的開發模式。雖然這種開發方式降低了運行速度,但是開發速度則大大提高,因為前端的工具鏈對于開發GUI程序實在是過于友好。javafx的webview加載和運行速度其實還算比較快的,筆者曾經對不同框架下的webview性能做過測試,發現2012年發布的javafx webview比2020年的Qt、安卓的webview速度還快,僅次于Electron(畢竟Electron是正經的套殼瀏覽器)。javafx的webview對HTML5的支持非常好,對css的支持稍微差點,但是基本不影響使用。
使用IDEA + scenebuilder是目前最好的開發javafx程序的方案。其中IDEA支持css和fxml的語法提示和高亮,scenebuilder是所見即所得的fxml生成器,可以通過拖拽的方式繪制界面。不過我不推薦拖拽組件,因為這種方式構建界面是有限制的,不能做出復雜的界面效果,而且自適應屏幕大小的時候也比較麻煩。繪制網頁的時候也是這樣,一般都不用編輯器,手寫HTML + CSS是最好的。
如果想要學習javafx的話,可以看我上面一篇關于跨平臺GUI技術對比的文章。后面我也會陸續更新javafx相關的技術指導。
到目前為止,我們編寫的程序都是通過鍵盤接收輸入,在控制臺屏幕上顯示結果。絕大多數用戶并不喜歡這種交互方式。現代的程序早已不采用這種操作方法,網絡程序更是如此。
從本章開始,我們將介紹如何編寫使用圖形用戶界面(GUI)的Java程序。其中,主要講述如何編寫定義屏幕上的窗口大小和位置的程序;如何在窗口中采用多種字體顯示文本;如何顯示圖像等等。這些都是需要掌握的編程技能,在后續章節中,將會使用這些技術編寫一些很有趣味的程序。
在Java 1.0剛剛出現的時候,包含了一個用于基本GUI程序設計的類庫,Sun將它稱為抽象窗
口工具箱(Abstract Window Toolkit,AWT)。基本AWT庫采用將處理用戶界面元素的任務委派給每個目標平臺(Windows、Solaris、Macintosh等等)的本地GUI工具箱的方式,由本地GUI工具箱負責用戶界面元素的創建和動作。例如,如果使用最初的AWT在Java窗口中放置一個文本框,就會有一個低層的“對等體”文本框,用它來實際地處理文本輸入。從理論上說,結果程序可以運行在任何平臺上,但觀感(look and feel)的效果卻依賴于目標平臺,因此,Sun公司的口號是“一次編寫,隨處使用”。
對于簡單的應用程序來說,基于對等體方法的效果還是不錯的,但是,要想編寫依賴于本地用戶界面元素的高質量、可移植的圖形庫就會顯現出缺陷了。例如,菜單、滾動條和文本域這些用戶界面元素,在不同的平臺上,操作行為存在著一些微妙的差別。因此,要想給予用戶一致的、可預見性的界面操作方式是相當困難的。而且,有些圖形環境(如X11/Motif)并沒有像Windows或Macintosh這樣豐富的用戶界面組件集合。這也就將基于對等體的可移植庫限制在了“最小公分母”的范圍內。其結果使AWT構建的GUI應用程序看起來沒有Windows或Macintosh應用程序顯示的那么漂亮,也沒有提供那些平臺用戶所認知的功能。更加糟糕的是,在不同平臺上的AWT用戶界面庫中存在著不同的bug。研發人員必須在每一個平臺上測試他們的應用程序,因此人們嘲弄地將AWT稱為“一次編寫,到處調試”。
在1996年,Netscape創建了一種稱為IFC(Internet Foundation Class)的GUI庫,它采用了與AWT完全不同的工作方式。它將按鈕、菜單這樣的用戶界面元素繪制在空白窗口上,而對等體只需要創建和繪制窗口。因此,Netscape的IFC部件在程序運行的所有平臺上的外觀和動作都一樣。Sun與Netscape合作完善了這種方式,創建了一個名為Swing(有時稱為Swing集)的用戶界面庫。Swing可作為Java 1.1的擴展部分使用,現已成為JDK 1.2標準庫的一部分,
就像Duke Ellington所說的那樣:“如果沒有Swing,Java圖形界面就沒有任何意義”。現在,Swing是不對等基于GUI工具箱的正式名字。它已是Java基礎類庫(Java Foundation Class,JFC)的一部分。完整的JFC十分龐大,其中包含的內容遠遠大于Swing GUI工具箱。JFC特性不僅僅包含了Swing組件,而且還包含了一個可訪問的API、一個2D API和一個可拖拽的API。
注意:Swing沒有完全替代AWT,而是基于AWT架構之上。Swing僅僅提供了能力更加強大的用戶界面組件。尤其在采用Swing編寫的程序中,還需要使用基本的AWT處理事件。從現在開始,“Swing”是指“被繪制的”非對等體用戶界面類;“AWT”是指像事件處理這樣的窗口工具箱的低層機制。
當然,在用戶屏幕上顯示基于Swing用戶界面的元素要比顯示AWT的基于對等體組件的速度慢一些。鑒于以往的經驗,對于任何一臺現代的計算機來說,微小的速度差別無妨大礙。另外,由于下列幾點無法抗拒的原因,驅使人們選擇Swing:
? Swing擁有一個豐富、便捷的用戶界面元素集合。
? Swing對低層平臺依賴的很少,因此與平臺相關的bug很少。
? Swing給予不同平臺的用戶一致的感觀效果。
所有這些意味著Swing擁有履行Sun提出的“一次編寫,到處運行”承諾的能力。
不過,上面第三點存在著一個潛在的問題:如果在所有平臺上用戶界面元素看起來都一樣,那么,它們就有可能與本地控件不一樣,而這些平臺的用戶對此可能并不熟悉。
Swing采用了一種很巧妙的方式來解決這個問題。在程序員編寫Swing程序時,可以為程序指定專門的“觀感”。
例如,圖7-1和圖7-2展示了同一個程序在Windows 和Motif平臺下運行的觀感。
注意:盡管本書并沒有打算介紹有關設定“觀感”的方式,但Java程序員可以對已存在的觀感進行擴展,甚至還可以設計全新的觀感。設計Swing組件的繪制方式是一個很繁瑣的過程。有些程序員已經做過一些這樣的工作,尤其是將Java移植到信息亭(kiosk)終端和手持設備這樣的非傳統平臺上。請參閱 http://www.javooto.com,其中包含了一系列有趣的觀感實現。
JDK 5.0引入了一種被稱為Synth的新觀感方式,使用它處理比較容易。在Synth中,可以通過指定圖像文件和XML描述符定義一種新觀感,而不需要編寫任何代碼。
此外,Sun開發了一種被稱為“Metal”的獨立于平臺的觀感。現在,市場上人們將它稱為“Java觀感”。不過,絕大多數程序員還繼續沿用著“Metal”這個術語,在本書中也將這樣稱呼。
有些人批評Metal有點笨重,而在版本5.0中看起來卻煥然一新(請看圖7-3)。
現在,Metal外觀支持多種主題,每一種主題的顏色和字體都有微小的變化。
默認的主題叫做“Ocean”。在本文中,所有的圖形程序都將采用Swing的Metal觀感和Ocean主題。
注意:絕大多數Java用戶界面程序設計都采用Swing,但有一個特別的例外。Eclipse集成開發環境使用了一種與AWT類似,且被稱為SWT的圖形工具箱,它可以映射到不同平臺的本地組件上。
有關SWT的描述可以在網站http://www.eclipse.org/ articles/找到。
最后,給大家一個忠告,如果使用過Visual Baisc或C# 編寫Microsoft Windows應用程序,就應該了解這些產品提供的圖形布局工具和資源編輯器帶來的便利。這些工具可以用來設計應用程序的外觀,然后生成大部分(有時是全部)GUI代碼。盡管也有一些Java程序設計的GUI構造器,但它們與相應的Windows工具相比較起來還很不成熟。不管怎樣,要想完全地掌握圖形用戶界面程序(乃至有效地使用這些工具),就需要知道如何手工地創建用戶界面。當然,通常需要編寫大量的代碼。
在Java中,頂層窗口(就是沒有包含在其他窗口中的窗口)被稱為框架(frame)。在AWT庫中有一個稱為Frame的類,用于描述頂層窗口。這個類的Swing版本名為JFrame,它擴展于Frame類。JFrame是極少數幾個不繪制在畫布上的Swing組件之一。因此,它的修飾部件(按鈕、標題欄、圖標等)由用戶的窗口系統繪制,而不是由Swing繪制。
警告:大多數的Swing組件類都以“J”開頭,例如,JButton、JFrame等等。在Java中有Button和Frame這樣的類,但它們屬于AWT組件。如果偶然地忘記了書寫“J”,程序仍然可以進行編譯和運行,但是將Swing和AWT組件混合在一起使用將會導致視覺和行為的不一致。
在本節中,將介紹有關Swing的JFrame的常用方法。例7-1給出了一個在屏幕中顯示一個空框架的簡單程序。如圖7-4所示。
例7-1 SimpleFrameTest.java
下面逐行地討論一下這個程序。
Swing類位于javax.swing包中。包名javax表示這是一個Java擴展包,而不是核心包。Swing類實際上是對Java 1.1的擴展。由于Swing類不是核心層次的一部分,所以盡可能地將Swing類加載到Java 1.1兼容的瀏覽器中(瀏覽器的安全管理器不允許添加任何以“java.”開頭的包)。在Java 2平臺上,Swing包不再是擴展部分,而是核心層的一部分。任何與Java 2兼容的Java實現都必須提供Swing類。不過,為了與Java 1.1代碼兼容,保留了javax名字。(實際上,Swing包最早是com.sun.java.swing,后來在Java 2的beta版本中簡化為java.awt.swing,最后在Java 2后期的beta版本中又改回為com.java.swing,在Java程序員的抗議呼聲下,最終改為javax.swing。)
在默認情況下,框架的大小為0×0像素,這種框架沒有什么實際意義。我們定義了一個子類SimpleFrame,它的構造器將框架大小設置為300×200像素。在SimpleFrameTest類的main方法中,程序將由創建一個SmpleFrame對象開始運行。
接下來,我們定義了用戶關閉這個框架時的響應動作。對于這個程序而言,我們只是讓程序退出。選擇這個響應動作的語句是:
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
在包含多個框架的程序中,不能因為用戶關閉了其中的一個框架就讓程序退出。在默認情況下,用戶關閉窗口時只是將框架隱藏了起來,而程序并沒有終止。
簡單地構造一個框架并不自動顯示出來,框架起初是不可見的。這就給程序員了一個機會,可以在框架第一次顯示之前往其中添加組件。為了顯示框架,main方法需要調用框架的setVisible方法。
然后,main方法退出。需要注意,退出main并沒有終止程序,終止的只是主線程。目前顯示的框架激活了用戶界面線程,以保持程序處于激活狀態。
注意:在JDK 5.0以前的版本中,可以使用JFrame類從超類Window繼承show方法。Window類的超類是Component,其中也有一個show方法。在JDK 1.2中不提倡使用Component.show。如果想要顯示一個組件,建議調用setVisible(true)。然而,JDK 1.4以前的版本,并沒有反對使用Window.show方法。事實上,這個方法很實用,它可以讓窗口可見,且置于其他窗口的前面。遺憾的是,由于不提倡使用它,隨之也失去了這一好處,JDK 5.0也不贊成使用show顯示窗口。
圖7-4中顯示的是運行例7-1程序的結果,它只是一個很乏味的頂層窗口。在這個圖中看到的標題欄和外框裝飾(比如,重置窗口大小的拐角)都是由操作系統繪制的,而不是Swing庫。如果在X Windows下運行同樣的程序,對框架的裝飾是不一樣的。Swing庫負責繪制框架內的所有內容。在這個程序中,只用默認的背景色填充了框架。
注意:在JDK 1.4中,可以調用frame.setUndecorated(true) 關閉所有框架裝飾。
注意:在前面的例子中編寫了兩個類,一個用于定義框架類,另一個包含了創建和顯示框架對象的main方法。在很多程序中,經常會發現main方法被包裝成一個很簡捷的類,如下所示:
從某種意義上說,調用框架類中的main方法的代碼啟動程序是比較簡單的。這樣不必引入其他的輔助類。然而,有相當多的程序員感覺這種程序風格有點混亂。
因此,更愿意將啟動程序的類與定義用戶界面的類分開。
明天講框架定位和在面板中顯示信息~~
*請認真填寫需求信息,我們會在24小時內與您取得聯系。