PC 端多端融合方案

天天都要寫次日的 todoList。有一天在寫的時候忽然想到,爲了讓本身清楚知道本身須要作啥、作了多少、還剩多少沒作,想寫一個電腦端程序,在技術選型的時候就選了 electron。

1、 方案選型

3天時間寫了個 PC 端應用程序。先看看結果吧javascript

Todo1

Todo1

Todo1

爲何要選 electron 做爲 pc 端開發方案?
史前時代,以 MFC 爲表明的技術棧,開發效率較低,維護成本高。
後來使用 QT 技術,特色是使用 DirectUI + 面向對象 + XML 定義 UI,適用於小型軟件、性能要求、包大小、UI 複雜度叫高的需求。
再到後來,以 QT Quick 爲表明的技術,特色是框架自己提供子控件,基於子控件組合來建立新的控件。相似於 ActionScript 的腳本化界面邏輯代碼。
新時代主要是以 electronCef 爲 表明。特色是界面開發以 Web 技術爲主,部分邏輯須要 Native 代碼實現。你們都熟悉的 VS Code 就是使用 electron 開發的。適用於 UI 變化較多、體積限制不大、開發效率高的場景。css

拿 C 系列寫應用程序的體驗很差,累到奔潰。再加上有 Hybrid、React Native、iOS、Vue、React 等開發經驗,electron 是不二選擇。html

2、 Quick start

執行下面命令快速體驗 Hello world,也是官方給的一個 Demo。前端

git clone https://github.com/electron/electron-quick-start
cd electron-quick-start
npm install && npm start

簡單介紹下 Demo 工程,工程目錄以下所示
vue

在終端執行 npm start 執行的是 package.json 中的 scripts 節點下的 start 命令,也就是 electron .. 表明執行 main.js 中的邏輯。java

// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron')
const path = require('path')

function createWindow () {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  // and load the index.html of the app.
  mainWindow.loadFile('index.html')

  // Open the DevTools.
  mainWindow.webContents.openDevTools()
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(createWindow)

// Quit when all windows are closed.
app.on('window-all-closed', function () {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') app.quit()
})

app.on('activate', function () {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (BrowserWindow.getAllWindows().length === 0) createWindow()
})

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

寫過 Vue、React、Native 的人看代碼很容易,由於應用程序的生命週期鉤子函數是很重要的,開發者根據需求在鉤子函數裏面作相應的視圖建立、初始化、銷燬對象等等。好比 electron 中的 activate、window-all-closed 等。node

app 對象在 whenReady 的時候執行 createWindow 方法。內部建立了一個 BrowserWindow 對象,指定了大小和功能設置(webPreferences Object (可選) - 網頁功能的設置。其中 preload String (可選) - 在頁面運行其餘腳本以前預先加載指定的腳本 不管頁面是否集成 Node, 此腳本均可以訪問全部 Node API 腳本路徑爲文件的絕對路徑。 當 node integration 關閉時, 預加載的腳本將從全局範圍從新引入 node 的全局引用標誌)。git

mainWindow.loadFile('index.html') 加載了同級目錄下的 index.html 文件。也能夠加載服務器資源(部署好的網頁),好比 win.loadURL('https://github.com/FantasticLBP')github

// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
window.addEventListener('DOMContentLoaded', () => {
  const replaceText = (selector, text) => {
    const element = document.getElementById(selector)
    if (element) element.innerText = text
  }
  console.table(process)
  console.info(process.versions)
  for (const type of ['chrome', 'node', 'electron']) {
    replaceText(`${type}-version`, process.versions[type])
  }
})

接下去看看 preload.js。在頁面運行其餘腳本以前預先加載指定的腳本,不管頁面是否集成 Node, 此腳本均可以訪問全部 Node API 腳本路徑爲文件的絕對路徑。Demo 中的邏輯很簡單,就是讀取 process.versions 對象中的 node、chrome、electron 的版本信息並展現出來。web

index.html 中的內容就是主頁面顯示的內容。

3、 實現原理

electron 分爲渲染進程和主進程。 😂 和 Native 中的概念不同的是 electron 中主進程只有一個,渲染進程(也就是 UI 進程) 有多個。主進程在後臺運行,每次打開一個界面,會新開一個新的渲染進程。

  • 渲染進程: 用戶看到的 web 界面就是由渲染進程繪製出來的,包括 html、css、js。
  • 主進程:electron 運行 package.json 中的 main.js 腳本的進程被稱爲主進程。在主進程中運行的腳本經過建立 web 頁面來展現用戶界面。一個 electron 應用程序老是隻有一個主進程。

1. Chromium 架構

Chromium 架構

這張圖是 chromium 多進程架構圖。早在2007年以前,市面上的瀏覽器都是單進程架構。單進程瀏覽器指的是瀏覽器的全部功能模塊都是運行在同一個進程裏的,這些模塊包括網絡、插件、Javascript 運行環境、渲染引擎和頁面等。如此複雜的功能都在一個進程內運行,因此致使瀏覽器出現不穩定、不安全、不流暢等問題。

多進程架構的瀏覽器解決了上述問題,至於如何解決的之後的文章會專門講解,不是本文的主要內容。

簡單描述下。

  • 主進程中的 RenderProcessHost 和 render 進程中的 RenderProcess 是用來處理進程間通訊的(IPC)。
  • Render 進程中的 RenderView 內容基於 WebKit 排版展現出來的
  • Render 進程中的 ResourceDispatcher 是用來處理資源請求的。Render 進程中若是有請求則建立一個請求 ID,轉發到 IPC,由 Browser 進程中處理後返回
  • Chromium 是多進程架構,包括一個主進程,多個渲染進程

對於 chromium 多進程架構感興趣的能夠點擊這個連接查看更多資料-Multi-process Architecture

2. Electron 架構

Electron 架構和 Chromium 架構相似,也是具備1個主進程和多個渲染進程。可是也有區別

  • 在各個進行中暴露了 Native API ,提供了 Native 能力。
  • 引入了 Node.js,因此可使用 Node 的能力

技術難點:因爲 Electron 內部整合了 Chromium 和 Node.js,主線程在某個時刻只能夠執行一個事件循環,可是2者的事件循環機制不同,Node.js 的事件循環基於 libuv,可是 Chromium 基於 message bump

因此 Electron 原理的重點就是「如何整合事件循環」。2種思路

  • Chromium 集成到 Node.js 中:用 libuv 實現 messagebump(Node-Webkit 就是這麼幹的,缺點挺多)
  • Node.js 集成到 Chromium 中(Electron 所採用的方式)

後來隨着 libuv 引入 backend_fd 的概念,至關因而 libuv 輪詢事件的文件描述符。經過輪訓 backend_fd 能夠知道 libuv 的新事件。因此 Electron 採起的作法就是將 Node.js 集成到 Chromium 中。

Node.js與Chromium通訊

上圖描述了 Node.js 如何融入到 Chromium 中。描述下原理

  • Electron 新起一個安全線程去輪訓 backend_fd
  • 當檢測到一個新的 backend_fd,也就是一個新的 Node.js 事件以後,經過 PostTask 轉發到 Chromium 的事件循環中

上述2個步驟完成了 Electron 的事件循環。

4、 如何調試

調試分爲主進程調試和渲染進程調試。

1. 渲染進程調試

看到 Demo 工程中執行 npm start 以後能夠看到主界面,Mac 端快捷鍵 comand + option + i,喚出調試界面,相似於 chrome 下的 devtools。其實就是無頭瀏覽器的那些東西。或者在代碼裏打開調試模式 mainWindow.webContents.openDevTools()

工程採用 Electron + Vue 技術,下面截圖 Vue-devtools 很方便查看 Vue 組件層級等 Vue 相關的調試

渲染進程調試

2. 主進程調試方式

主進程調試有2種方法

方法一:利用 chrome inspect 功能進行調試

  • 須要在啓動的時候給 package.json 中的 scripts 節點下的 start 命令加上調試開關
--inspect=[port]
// electrom --inspect=8001 yourApp
  • 而後打開瀏覽器,在地址欄輸入 chrome://inspect
  • 點擊 configure,在彈出的面板中填寫須要調試的端口信息
    chrome inspect
  • 從新開啓服務 npm start,在 chrome inspect 面板的 Target 節點中選擇須要調試的頁面
  • 在面板中能夠看到主進程執行的 main.js。能夠加斷點進行調試
    chrome inspect

方法二:利用 VS Code 調試 electron 主進程。

  • 在 VS Code 的左側菜單欄,第四個功能模塊就是調試,點擊調試,彈出對話框讓你添加調試配置文件 launch.json
  • 編輯 launch.json 的文件內容。以下

    {
        "version": "0.2.0",
        "configurations": [
            {
                "type": "node",
                "request": "launch",
                "name": "Debug main process",
                "cwd": "${workspaceRoot}",
                "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
                "windows": {
                    "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
                },
                "args": ["."],
                "outputCapture": "std"
            }
        ]
    }
  • 在調試模塊點擊綠色小三角,會運行程序,能夠添加斷點信息。總體界面以下所示。能夠單步調試、能夠暫停、鼠標移上去能夠看到對象的各類信息。

    VS Code 調試功能

3. 主進程調試之 hot reload

Electron 的渲染進程中的代碼改變了,使用 Command + R 能夠刷新,可是修改主進程中的代碼則必須從新啓動 yarn run dev 。效率低下,因此爲了提高開發效率,有必要解決這個問題

Webpack 有一個 api: watch-run,能夠針對代碼文件檢測,有變化則 Restart

main Process reload

5、 開發 tips

  1. 或許會爲網頁添加事件代碼,可是頁面看到的內容是渲染進程,因此事件相關的邏輯代碼應該寫在 html 引入的 render.js 中。
  2. render.js 中寫 Node 代碼的時候須要在 main.js 初始化 BrowserWindow 的時候,在 webPreferences 節點下添加 nodeIntegration: true 。否則會報錯:renderer.js:9 Uncaught ReferenceError: process is not defined。
  3. 從 Chrome Extenstion V2 開始,不容許執行任何 inline javascript 代碼在 html 中。不支持之內聯方式寫事件綁定代碼。好比 <button onclick="handleCPU">查看</button>

    Refused to execute inline event handler because it violates the following Content Security Policy directive:
  4. 利用 electron 進行開發的時候,能夠當作是 NodeJS + chromium + Web 前端開發技術。NodeJS 擁有文件訪問等後端能力,chromium 提供展現功能,以及網絡能力(electron 網絡能力不是 NodeJS 提供的,而是 chromium 的 net 模塊提供的)。web 前端開發技術方案均可以應用在 electron 中,好比 Vue、React、Bootstrap、sass 等。
  5. 在工程化角度看,使用 yarn 比 npm 好一些,由於 yarn 會緩存已經安裝過的依賴,其餘項目只要發現存在緩存,則讀取本地的包依賴,會更加快速。
  6. 在使用 Vue、React 開發 electron 應用時,可使用 npm 或 yarn install 包,也可使用 electron-vue 腳手架工具。

    vue init simulatedgreg/electron-vue my-project
    cd my-project
    npm install
    npm run dev
  7. 開發完畢後須要設置應用程序的圖標信息、版本號等,打包須要指定不一樣的平臺。
  8. 新開項目建立後會報錯.

    初始化工程後會報錯 ERROR in Template execution failed: ReferenceError: process is not defined。解決辦法是使用 nvm 將 node 版本將爲 10。

    繼續運行仍是報錯,以下

    ┏ Electron -------------------
    
    [11000:0615/095124.922:ERROR:CONSOLE(7574)] "Extension server error: Object not found: <top>", source: chrome-devtools://devtools/bundled/inspector.js (7574)
    
    ┗ ----------------------------

    解決辦法是在 main/index.dev.js 修改代碼

    - require('electron-debug')({ showDevTools: true });
    + // NB: Don't open dev tools with this, it is causing the error
    + require('electron-debug')();

    在 In main/index.js in the createWindow() function:

    mainWindow.loadURL(winURL);
      // Open dev tools initially when in development mode
    if (process.env.NODE_ENV === 'development"') {
      mainWindow.webContents.on('did-frame-finish-load', () => {
        mainWindow.webContents.once('devtools-opened', () => {
          mainWindow.focus()
        })
        mainWindow.webContents.openDevTools()
      })
    }
  9. Electron 多窗口與單窗口應用區別

    用途

  10. 知道 Electron 開發原理,因此大部分時間是在寫前端代碼。因此根據團隊技術沉澱、選擇對應的前端框架,好比 Vue、React、Angular。
  11. 也許開發並不難,難在視覺和 UX。不少寫過網頁的開發者或者之前端的視覺去寫 Electron App 不免會寫出網頁版的桌面應用程序,說難聽點,就是四不像 😂。因此須要轉變想法,這是在開發桌面應用程序。
  12. Electron 和 Web 開發相比,各自有側重點

    electronAndWeb

6、 技術體系搭建

其實一個技術自己的難易程度並非可否在本身企業、公司、團隊內順利使用的惟一標尺,其配套的 CI/CD、APM、埋點系統、發佈更新、灰度測試等可否與現有的系統以較小成本融合纔是很大的決定要素。由於某個技術並非很是難,要是大多數開發者以爲很難,那它設計上就是失敗的。

1. 構建

構建

2. 工程解耦

工程解耦

3. 問題定位

Electron 提供的 crash 信息進行包裝。

crash 分析

引伸資料

相關文章
相關標籤/搜索