Jack Jiang

          我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
          posts - 506, comments - 13, trackbacks - 0, articles - 1

          本文由蘑菇街前端技術(shù)團(tuán)隊(duì)分享,原題“Electron 從零到一”,有修訂和改動(dòng)。

          1、引言

          在上篇《快速了解新一代跨平臺(tái)桌面技術(shù)——Electron》,我們已經(jīng)對(duì)Electron跨端框架有了基本的認(rèn)識(shí)。

          本篇將帶你簡(jiǎn)單上手Electron框架開(kāi)發(fā)跨平臺(tái)桌面端,內(nèi)容包括一個(gè)快速開(kāi)始例子、跨進(jìn)程通信原理、打包和分發(fā)、以及一些典型的技術(shù)踩坑等。希望能帶給你啟發(fā)。

          學(xué)習(xí)交流:

          - 移動(dòng)端IM開(kāi)發(fā)入門文章:《新手入門一篇就夠:從零開(kāi)發(fā)移動(dòng)端IM

          - 開(kāi)源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK備用地址點(diǎn)此

          本文已同步發(fā)布于:http://www.52im.net/thread-4039-1-1.html

          2、系列文章

          本文是系列文章中的第2篇,本系列總目錄如下:

          3、Electron簡(jiǎn)介

          Electron 是一個(gè)賦力前端進(jìn)行跨平臺(tái)開(kāi)發(fā)的框架,讓開(kāi)發(fā)人員使用 JavaScript、HTML 和 CSS 等前端技術(shù)構(gòu)建跨平臺(tái)的桌面應(yīng)用。

          Electron 通過(guò)將 Chromium(所有類Chrome的瀏覽器都是基于這個(gè)開(kāi)源工程而來(lái)) 和 Node.js 合并到同一個(gè)運(yùn)行時(shí)環(huán)境中(見(jiàn)下圖),并將其打包為 Mac,Windows 和 Linux 系統(tǒng)下的應(yīng)用,而開(kāi)發(fā)人員只需關(guān)注前端代碼的開(kāi)發(fā)。

          ▲ 上圖引用自《快速了解新一代跨平臺(tái)桌面技術(shù)——Electron

          Chromium、Node.js、Native API這三者的作用分別是:

          • 1)Chromium :為Electron提供了強(qiáng)大的UI能力,可以不考慮傳統(tǒng)瀏覽器兼容性的情況下,利用強(qiáng)大的Web生態(tài)來(lái)開(kāi)發(fā)界面;
          • 2)Node.js:讓Electron有了底層的操作能力(比如文件的讀寫,甚至是集成C++等等操作),并可以使用大量開(kāi)源的npm包來(lái)完成開(kāi)發(fā)需求。
          • 3)Native API:Native API讓Electron有了跨平臺(tái)和桌面端的原生能力(比如說(shuō)它有統(tǒng)一的原生界面,窗口、托盤、消息通知這些)。

          Electron就是通過(guò)這三者的巧妙組合,讓我們開(kāi)發(fā)跨平臺(tái)應(yīng)用變的十分高效。

          本質(zhì)上就是chromium(chrome開(kāi)源版本)瀏覽器,有最新的東西都會(huì)在chromium測(cè)試,所以electron可以體驗(yàn)最新的api,這也是好處之一。

          有關(guān)Electron的基本介紹等,這里就不再贅述,如果您還不曾了解,可以先閱讀本文的上篇

          4、快速開(kāi)始

          4.1 資料準(zhǔn)備

          Electron 官方提供了一個(gè)名為electron-quick-start 的項(xiàng)目,可以 clone 下來(lái)當(dāng)成模版使用,本文使用 create-react-app 來(lái)一步一步學(xué)習(xí)。

          其它重要的Electron開(kāi)發(fā)資源:

          4.2 創(chuàng)建一個(gè) react 項(xiàng)目

          # 安裝 create-react-app 命令,如果已將安裝請(qǐng)忽略

          npm install -g create-react-app

          # 創(chuàng)建 electron-react 項(xiàng)目

          create-react-app electron-react

          # 啟動(dòng)項(xiàng)目

          cd electron-react && npm start

          4.3 配置 Electron 環(huán)境

          1)在 public 文件夾下新建 index.html,隨便寫點(diǎn)內(nèi)容:

          ...

          <div>hello world</div>

          ...

          2)接下來(lái)創(chuàng)建 electron 主線程文件(public/main.js),建議寫在 public 路徑下面:

          const{app, BrowserWindow} = require('electron')

          // 創(chuàng)建全局變量并在下面引用,避免被GC

          let win

          function createWindow () {

              // 創(chuàng)建瀏覽器窗口并設(shè)置寬高

              win = newBrowserWindow({ width: 800, height: 600 })

              // 加載頁(yè)面

              win.loadFile('./index.html')

              // 打開(kāi)開(kāi)發(fā)者工具

              win.webContents.openDevTools()

              // 添加window關(guān)閉觸發(fā)事件

              win.on('closed', () => {

                  win = null// 取消引用

              })

          }

          // 初始化后 調(diào)用函數(shù)

          app.on('ready', createWindow) 

          // 當(dāng)全部窗口關(guān)閉時(shí)退出。

          app.on('window-all-closed', () => {

             // 在 macOS 上,除非用戶用 Cmd + Q 確定地退出,

             // 否則絕大部分應(yīng)用及其菜單欄會(huì)保持激活。

             if(process.platform !== 'darwin') {

                  app.quit()

             }

          })

          app.on('activate', () => {

          // 在macOS上,當(dāng)單擊dock圖標(biāo)并且沒(méi)有其他窗口打開(kāi)時(shí),

          // 通常在應(yīng)用程序中重新創(chuàng)建一個(gè)窗口。

              if(win === null) {

                createWindow()

              }

          })

          3)接著再修改 package.json 中的 main 字段對(duì)應(yīng)的路徑, 并添加 start 命令:

          {

              ...

              "main": "main.js",

              "scripts": "electron ."

          }

          4)執(zhí)行 npm start,就會(huì)彈出如下運(yùn)行界面:

          以上就是我簡(jiǎn)單寫的一個(gè)頁(yè)面,大家也可以寫一寫自己感興趣的東西。

          真如上面演示的這樣,一個(gè)簡(jiǎn)單的Electron跨平臺(tái)桌面應(yīng)用就開(kāi)發(fā)好了,真的 so easy!

          5、進(jìn)程詳解

          5.1 基本認(rèn)知

          Electron 架構(gòu)和 Chromium 架構(gòu)類似,也是具有1個(gè)主進(jìn)程和多個(gè)渲染進(jìn)程(如下圖所示)。

          但是也有區(qū)別:

          • 1)在各個(gè)進(jìn)程中暴露了 Native API ,提供了 Native 能力;
          • 2)引入了 Node.js,所以可以使用 Node 的能力;
          • 3)但是渲染進(jìn)程使用node 需要配置。

          可以簡(jiǎn)單的理解為:Electron為web項(xiàng)目套上了Node.js環(huán)境的殼,使得我們可以調(diào)用Node.js的豐富的API。這樣我們可以用JavaScript來(lái)寫桌面應(yīng)用,拓展很多我們?cè)趙eb端不能做的事情。

          下面這張圖,技術(shù)原理更容易理解一點(diǎn):

          5.2 主進(jìn)程的主要特點(diǎn)

          Electron 運(yùn)行 package.json 的 main 腳本的進(jìn)程被稱為主進(jìn)程 (主進(jìn)程只有一個(gè))。涉及到具體代碼的講解,將在下一節(jié)中展開(kāi),本節(jié)就不作過(guò)多闡述了。

          Electron主進(jìn)程的具體職責(zé):

          • 1)主進(jìn)程連接著操作系統(tǒng)和渲染進(jìn)程,可以把她看做頁(yè)面和計(jì)算機(jī)溝通的橋梁;
          • 2)進(jìn)程間通信、窗口管理
          • 3)全局通用服務(wù);
          • 4)一些只能或適合在主進(jìn)程做的事情(例如瀏覽器下載、全局快捷鍵處理、托盤、session);
          • 5)維護(hù)一些必要的全局狀態(tài)。

          5.3 渲染進(jìn)程的主要特點(diǎn)

          渲染進(jìn)程就是我們所熟悉前端環(huán)境了,只是載體改變了,從瀏覽器變成了window.

          注意:出于安全考慮,渲染進(jìn)程是不能直接訪問(wèn)本地資源的,因此都需要在主進(jìn)程完成。

          Electron渲染進(jìn)程主要特點(diǎn):

          • 1)Electron 使用了 Chromium 來(lái)展示 web 頁(yè)面,所以 Chromium 的多進(jìn)程架構(gòu)也被使用到;
          • 2)每個(gè)web頁(yè)面運(yùn)行在它自己的渲染進(jìn)程中(每個(gè)渲染進(jìn)程都是相互獨(dú)立的,并且只關(guān)心他們自己的網(wǎng)頁(yè));
          • 3)使用BrowserWindow類開(kāi)啟一個(gè)渲染進(jìn)程并將這個(gè)實(shí)例運(yùn)行在該進(jìn)程中,當(dāng)一個(gè)BrowserWindow實(shí)例被銷毀后,相應(yīng)的渲染進(jìn)程也會(huì)被終止;
          • 4)渲染進(jìn)程中不能調(diào)用原生資源,但是渲染進(jìn)程中同樣包含Node.js環(huán)境,所以可以引入Node.js。

          5.4 主進(jìn)程與渲染進(jìn)程的關(guān)系

          主進(jìn)程與渲染進(jìn)程的關(guān)系主要是這樣:

          • 1)主進(jìn)程使用 BrowserWindow 實(shí)例創(chuàng)建網(wǎng)頁(yè);
          • 2)每個(gè) BrowserWindow 實(shí)例都在自己的渲染進(jìn)程里運(yùn)行著一個(gè)網(wǎng)頁(yè)(當(dāng)一個(gè) BrowserWindow 實(shí)例被銷毀后,相應(yīng)的渲染進(jìn)程也會(huì)被終止);
          • 3)主進(jìn)程管理所有頁(yè)面和與之對(duì)應(yīng)的渲染進(jìn)程;
          • 4)由于在網(wǎng)頁(yè)里管理原生 GUI 資源是非常危險(xiǎn)而且容易造成資源泄露,所以在網(wǎng)頁(yè)面調(diào)用 GUI 相關(guān)的 APIs 是不被允許的(如果你想在網(wǎng)頁(yè)里使用 GUI 操作,其對(duì)應(yīng)的渲染進(jìn)程必須與主進(jìn)程進(jìn)行通訊,請(qǐng)求主進(jìn)程進(jìn)行相關(guān)的 GUI 操作)。

          具體關(guān)系如下圖所示:

          把它們想象成這樣:

          即Chrome(或其他瀏覽器)的每個(gè)標(biāo)簽頁(yè)(tab)及其頁(yè)面,就好比 Electron 中的一個(gè)單獨(dú)渲染進(jìn)程。即使關(guān)閉所有標(biāo)簽頁(yè),Chrome 依然存在。這好比 Electron 的主進(jìn)程,能打開(kāi)新的窗口或關(guān)閉這個(gè)應(yīng)用。就像下圖這樣。

          6、從代碼角度理解進(jìn)程

          6.1 主進(jìn)程和渲染進(jìn)程

          先來(lái)看看 electron 項(xiàng)目基本目錄結(jié)構(gòu):

          app

          └─public

              └─index.html---------------入口文件

          ├─main.js----------------------程序啟動(dòng)入口,主進(jìn)程

          ├─ipc--------------------------進(jìn)程間模塊

          ├─appNetwork-------------------應(yīng)用通信模塊

          └─src--------------------------窗口管理,渲染進(jìn)程

              ├─components---------------通用組件模塊

              ├─store--------------------數(shù)據(jù)共享模塊

              ├─statics------------------靜態(tài)資源模塊

              └─pages----------------------窗口業(yè)務(wù)模塊

                ├─窗口A----------------窗口

                └─窗口B----------------窗口

          如上所示:package.json 中的 main 字段對(duì)應(yīng)的文件的進(jìn)程是主進(jìn)程。Electron集成了Chromium來(lái)展示窗口界面,窗口中所看到的內(nèi)容使用的都是HTML渲染出來(lái)的。

          Chromium本身是多進(jìn)程渲染頁(yè)面的架構(gòu)(在默認(rèn)情況下,Chromium的默認(rèn)策略是對(duì)每一個(gè)tab新開(kāi)一個(gè)進(jìn)程,以確保每個(gè)頁(yè)面是獨(dú)立且互不影響的,避免一個(gè)頁(yè)面的崩潰導(dǎo)致全部頁(yè)面無(wú)法使用),所以Electron在展示窗口時(shí),也會(huì)使用到Chromium的多進(jìn)程架構(gòu)。而這種多進(jìn)程渲染架構(gòu)在Electron中,也就被是渲染進(jìn)程(render process)啦。

          6.2 進(jìn)程間通信

          在 Electron 中,GUI 相關(guān)的模塊(如 dialog,menu 等)僅在主進(jìn)程可用,在渲染進(jìn)程中不可用。

          為了在渲染進(jìn)程中使用它們,需要使用 ipc 模塊向主進(jìn)程發(fā)送消息,下面是幾種進(jìn)程間通訊的方法。

          1)ipcMain & ipcRenderer:

          從主進(jìn)程到渲染進(jìn)程的異步通信,也可以將消息從主進(jìn)程發(fā)送到渲染進(jìn)程(參考文檔)。

          發(fā)送消息時(shí),事件名稱為 channel。回復(fù)同步消息時(shí),需要設(shè)置 event.returnValue。

          將異步消息發(fā)送回發(fā)送方,可以使用 event.reply(...),這個(gè)輔助方法將自動(dòng)處理來(lái)自渲染進(jìn)程的消息,然而 event.sender.send(...) 這個(gè)方法則始終將消息發(fā)送給主進(jìn)程。

          下面是在渲染和主進(jìn)程之間發(fā)送和處理消息的一個(gè)例子:

          // 在主進(jìn)程中

          const { ipcMain } = require('electron')

          ipcMain.on('asynchronous-message', (event, arg) => {

              console.log(arg); // 輸出 'ping'

              event.reply('asynchronous-reply', 'pong');

          })

          ipcMain.on('synchronous-message', (event, arg) => {

              console.log(arg) // 輸出 ‘ping’

              event.returnValue = 'pong'

          })

          // 在渲染進(jìn)程(網(wǎng)頁(yè))中

          const { ipcRenderer } = require('electron')

          console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // 輸出 'pong'

          ipcRenderer.on('asynchronous-reply', (event, arg) => {

              console.log(arg); // 輸出 'pong'

          })

          ipcRenderer.send('asynchronous-message', 'ping')

          2)remote 模塊:

          remote 為渲染進(jìn)程和主進(jìn)程通信提供了一種簡(jiǎn)單的方法。你可以調(diào)用 main 進(jìn)程對(duì)象的方法,而不必顯式發(fā)送進(jìn)程間消息。

          例如:從渲染進(jìn)程創(chuàng)建瀏覽器窗口

          const { BrowserWindow } = require('electron').remote

          let win = newBrowserWindow({ width: 800, height: 600 })

          win.loadUrl('https://www.mogu.com')

          注意: 反過(guò)來(lái),如果需要從主進(jìn)程訪問(wèn)渲染進(jìn)程,可以使用 webContents.executeJavascript

          3)webContents:

          即通過(guò) channel 向渲染進(jìn)程發(fā)送異步消息,可以發(fā)送任意參數(shù)。在內(nèi)部,參數(shù)會(huì)被序列化為 JSON,因此參數(shù)對(duì)象傷的函數(shù)和原型鏈不會(huì)被發(fā)送。

          除了以上這些方法,也可以使用 localStorage、sessionStorage 等。

          7、打包發(fā)布

          開(kāi)發(fā)完成后,還需要將應(yīng)用打包成可執(zhí)行文件,這一環(huán)節(jié)的坑還是學(xué)習(xí) electron 到現(xiàn)在踩的最多的。

          目前主流的打包工具有 electron-packager和 electron-builder

          7.1 electron-packager

          1)安裝依賴:

          npm i electron-packager --save-dev

          2)打包:

          electron-packager --platform= --arch= [optional flags...]

          也可以直接運(yùn)行 npm run electron-packager 打包。

          7.2 electron-builder

          官方解釋:

          A complete solution to package and build a ready for distribution Electron, Proton Native or Muon app for macOS, Windows and Linux with “auto update” support out of the box.

          簡(jiǎn)單的說(shuō):electron-builder 有比 electron-packager 更豐富的功能,支持更多的平臺(tái),同時(shí)也支持了自動(dòng)更新。除了這幾點(diǎn)外,electron-builder 打出的包更為輕量,并且可以打包出不暴露源碼的 setup 安裝程序。

          另外使用下來(lái)感覺(jué)比 electron-packager 的坑要少一點(diǎn)。

          1)安裝依賴:

          npm i electron-builder --save-dev

          2)打包(在項(xiàng)目的 package.json 文件中定義 build 字段):

          {

              "build": {

                  "appId": "com.xxx.app",

                  "extends": null,

                  "files": [

                      "build/**/*"

                  ],

                  "mac": {

                      "icon": "icons/icon.icns"

                  },

                  "win": {

                      "target": "nsis",

                      "icon": "icons/icon.png"

                  }

              }

          }

          這是最基礎(chǔ)的配置,當(dāng)然打包過(guò)程中可能會(huì)碰到其他的問(wèn)題需要修改配置。通常 files 配置只寫一個(gè) build 文件夾是不夠的,要根據(jù)項(xiàng)目結(jié)構(gòu)和打包情況添加其他路徑。

          添加 scripts 命令

          {

              "scripts": {

                  "pack": "electron-builder"

              }

          }

          運(yùn)行 npm run pack 打包。

          打包完成后在 dist 目錄下有可執(zhí)行文件,打開(kāi)后如果沒(méi)有報(bào)錯(cuò),則說(shuō)明打包成功。

          8、踩坑總結(jié)

          我所遇到的大部分都是打包遇到的坑,以下列舉幾個(gè)典型的坑。

          8.1 使用 electron-packager 打包報(bào)錯(cuò)

          Generated checksum for"electron-v6.0.2-darwin-x64.zip"did not match expected checksum。

          解決方法:node 版本升級(jí)到 8.x 以上就好。

          8.2 打開(kāi)打包生成的可執(zhí)行文件報(bào)錯(cuò)

          出現(xiàn)這種問(wèn)題可能有以下幾個(gè)原因。

          1)項(xiàng)目中可能直接訪問(wèn)了本地路徑, 瀏覽器為了安全考慮不允許訪問(wèn)。

          2)package.json 中的 build 配置問(wèn)題,假如 main.js 在一個(gè)很深的路徑中,需要在下面單獨(dú)添加 main.js 的路徑:

          "build": {

              ...

          +   "public/main.js"

              ...

          }

          3)webpack 配置中的路徑直接使用了 __dirname, 可以使用 remote 模塊的 getAppPath 方法取得路徑:

          const remote = require('remote')

          const app = remote.require('app')

          console.log(app.getAppPath());

          參考資料:https://github.com/electron/electron/issues/5107

          8.3 dependencies & devDependencies

          在 Electron 打包時(shí),一定要分清哪些是生產(chǎn)環(huán)境依賴,哪些是開(kāi)發(fā)環(huán)境依賴,避免出現(xiàn)此類錯(cuò)誤:

           

          8.4 關(guān)于打包慢的問(wèn)題(npm & cnpm)

          cnpm 裝的各種 node_modules,這種方式下所有的包都是扁平化的安裝,一下子 node_modules 展開(kāi)就有非常多的文件,導(dǎo)致打包的過(guò)程非常慢。

          但是如果該用 npm 來(lái)安裝 node_modules 的話,所有的包都是樹(shù)狀結(jié)構(gòu),層級(jí)變深。但是打包速度會(huì)快很多(具體資料參見(jiàn):electron打包過(guò)了2小時(shí)都沒(méi)好?)。

          9、Electron的優(yōu)缺點(diǎn)

          文章的最后,基于實(shí)踐體會(huì),總結(jié)一下Electron的優(yōu)缺點(diǎn)。

          Electron優(yōu)點(diǎn)很明顯:

          • 1)上手較簡(jiǎn)單:HTML、CSS、JS、Node 、npm包、UI框架 ,方便高效,能很輕松的實(shí)現(xiàn)很好看的UI;
          • 2)可多端運(yùn)行:可以快速構(gòu)建“跨平臺(tái)”(Windows、MacOs、Linux)的桌面級(jí)應(yīng)用;
          • 3)開(kāi)發(fā)時(shí)間短:相對(duì)其他跨平臺(tái)方案(如:QT、GTK+ 等),更穩(wěn)定、bug少,畢竟只要瀏覽器外殼跑起來(lái)了就可以了(當(dāng)然坑是少不了的);
          • 4)兼容性問(wèn)題:再也不用兼容多瀏覽器(只針對(duì)谷歌,但要兼容mac、Linux)。

          Electron缺點(diǎn)也同樣顯而易見(jiàn):

          • 1)安裝包體積大:安裝包體積略大(打包了Chromium),至少包含了一個(gè)瀏覽器的體積 ,每裝一個(gè)app就相當(dāng)于裝一個(gè)chrome;
          • 2)運(yùn)行性能稍差:性能不如原生應(yīng)用,Mac系統(tǒng)下絲滑一些,Window系統(tǒng)就有點(diǎn)丟幀;
          • 3)內(nèi)存占用較大:卡、啟動(dòng)慢,新開(kāi)一個(gè)進(jìn)程,起步價(jià)就是一個(gè)NodeJS的內(nèi)存開(kāi)銷;
          • 4)網(wǎng)頁(yè)加載稍慢:loadURL加載遠(yuǎn)程頁(yè)面白屏?xí)r間長(zhǎng)(優(yōu)化可采用 VSCode 骨架屏)。

          10、參考資料

          [1] Electron官方開(kāi)發(fā)者手冊(cè)

          [2] Electron初體驗(yàn)(快速開(kāi)始、跨進(jìn)程通信、打包、踩坑等)

          [3] Electron 基礎(chǔ)入門 簡(jiǎn)單明了,看完啥都懂了

          [4] 網(wǎng)易云信Web端IM的聊天消息全文檢索技術(shù)實(shí)踐

          本文已同步發(fā)布于:http://www.52im.net/thread-4039-1-1.html)



          作者:Jack Jiang (點(diǎn)擊作者姓名進(jìn)入Github)
          出處:http://www.52im.net/space-uid-1.html
          交流:歡迎加入即時(shí)通訊開(kāi)發(fā)交流群 215891622
          討論:http://www.52im.net/
          Jack Jiang同時(shí)是【原創(chuàng)Java Swing外觀工程BeautyEye】【輕量級(jí)移動(dòng)端即時(shí)通訊框架MobileIMSDK】的作者,可前往下載交流。
          本博文 歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明出處(也可前往 我的52im.net 找到我)。


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          Jack Jiang的 Mail: jb2011@163.com, 聯(lián)系QQ: 413980957, 微信: hellojackjiang
          主站蜘蛛池模板: 阿勒泰市| 石台县| 成安县| 牙克石市| 广饶县| 阳春市| 拉萨市| 仁化县| 平谷区| 双桥区| 军事| 宣城市| 湘乡市| 龙胜| 榆社县| 威海市| 通城县| 华蓥市| 井研县| 米脂县| 灵寿县| 延边| 阿克| 鄯善县| 无极县| 大方县| 太康县| 武汉市| 定兴县| 安泽县| 汤阴县| 扬州市| 伊宁市| 武山县| 津市市| 石家庄市| 广安市| 辽宁省| 静宁县| 扎兰屯市| 化州市|