開發

使用Electron構建跨平臺的桌面應用

作者:李曉健。蘇寧視頻云前端部門經理。擁有7年前端業務研發和架構經驗,目前負責蘇寧云視頻前端研發和架構工作。

Electron簡介

Electron 是一個使用 JavaScript,HTML 和 CSS 等 Web 技術創建原生桌面程序的框架。官網是https://electronjs.org/

Electron就是使用Web技術來開發,然后打包成桌面客戶端的一個框架。它渲染Web內容使用的是Chromium,Chromium基本上和大家常用的chrome瀏覽器差不多,并且Electron在打包時是將Chromium嵌入到了應用里,所以它并不依賴用戶機器上安裝的瀏覽器,大家都知道做Web開發,最大的一個問題就是兼容性的問題,經常相同的代碼在不同操作系統中不同的瀏覽器里,甚至不同版本的瀏覽器里都可能會出現不同的效果。Electron相當于是把一個固定版本的瀏覽器嵌入到應用中,所以他就不存在兼容性問題,只要我們開發時寫的代碼可以正常運行,就不存在打包后在用戶的機器上因為兼容性問題無法使用的情況。

Electron支持Node.js的原生模塊,也就是說可以在Electron應用中直接使用Node.js的模塊。Web應用為了安全等一系列的問題考慮,限制了很多的功能,比如本地文件的讀寫、操作系統中硬件設備的調用等等,但是Node.js并沒有這些限制。也就是說在Electron應用中,我們不光可以使用Web技術的特點,還能夠突破Web技術的限制,使用Node.js的模塊功能,使應用功能更加豐富。

Electron開發出來的應用是跨平臺的,當應用開發完成后,我們可以將同一套代碼打包成Linux、macOS、Windows平臺上的可執行程序或安裝包。

Electron中比較重要的概念

學習任何一門框架,里面或多或少都會有一些新的概念,只有把這些新的概念都弄懂了,才能更好的掌握和運用這門框架,Electron中新的概念并不是很多,接下來我就就來說說最重要的3個概念。

1. 主進程:Electron應用中運行package.json的main腳本的進程被稱為主進程,在主進程中運行的腳本通過創建web頁面來展示用戶界面。一個 Electron 應用總是有且只有一個主進程。

2. 渲染進程:Electron應用中是通過在主進程里創建web頁面來展示用戶界面的,在主進程中可以通過BrowserWindow 實例創建頁面,這里的一個web頁面就是運行在一個渲染進程里的,一個Electron應用可以包含多個web頁面,所以一個Electron 應用中是可以有多個渲染進程的,當一個 BrowserWindow 實例被銷毀后,相應的渲染進程也會被終止。

3. IPC:既然有主進程和渲染進程,而主進程管控的是一個應用全局的設置和方法,而渲染進程只在一個web頁面中存在,那他們肯定不會始終完全孤立的存在,有時也是需要有通信的,比如我在頁面做了一個操作,整個應用需要去做一個響應,就是就需要用到主進程和渲染進程的通信。就里就有一個概念IPC(Inter-Proess Communication),進程中通信。IPC就提供了相應的方法來供主進程和渲染進程間進行通信。

所以他們大概的關系就是一個應用中有一個主進程,然后主進程可以創建多個渲染進程,主進程和渲染進程間通過IPC來通信。如下圖:

Electron的安裝

Electron是以Node.js中的模塊的方式進行發布的,所以他的安裝也非常簡單,通過npm來進行安裝就可以了。既然Electron是通過Node.js的模塊起先發布的,那它的安裝就需要依賴Node.js的環境,也就是需要在我們的電腦上安裝Node.js,安裝Node.js直接去其官網( https://nodejs.org)下載相應的安裝包,然后按照默認的設置一路安裝就行了,在安裝Node.js的同時也會安裝上npm,安裝完成后檢測Node.js和npm是否安裝成功,只需要在命令行工具攔執行相應的版本查看命令就行了,如果能看到有版本號輸出,則說明安裝成功。如下圖:

接下來我們就可以直接使用npm來安裝我們所需要的模塊了。首先進入到我們的項目開發目錄,然后初始化好項目(創建好package.json文件),然后在該目錄執行

npm install electron –save

然后等待安裝完成就行了。

在這個安裝過程中,可能由于網絡問題會安裝失敗,我們可以修改npm的鏡像源,將npm的鏡像源修改成國內的鏡像源:在命令行執行 npm config set registry http://registry.npm.taobao.org/ 或者安裝cnpm,然后通過cnpm來安裝Node.js的模塊。

安裝完成后,在命令行執行

electron -v

可以看到electron的版本號,就說明Electron安裝成功了。

Electron項目的搭建

Electron安裝好就,就可以搭建項目了。官網給我們提供了一個空的種子項目,這個項目里面非常的干凈,所以可以直接使用,首先我們通過命令行進入我們工作目錄,克隆下這個項目(前提是我們的電腦需要安裝git),執行

git clonehttps://github.com/electron/electron-quick-start.git

克隆完成后,我們的工作目錄中就多了一個 electron-quick-start 目錄,這個目錄就是

我們的項目目錄,這里面有幾個文件是我們項目需要用到的文件

1. package.json:項目的一些依賴和配置文件,里面有一些我們項目的基本信息,我們可以根據自己項目的情況來修改這里的值 bash

{
  "name": "electron-quick-start", //項目的名稱
  "version": "1.0.0", //項目的版本號
  "description": "A minimal Electron application", //項目的描述
  "main": "main.js", //主入口文件
  "scripts": { //快捷腳本
    "start": "electron ."
  },
  "repository": "https://github.com/electron/electron-quick-start", //代碼倉庫
  "keywords": [ //項目的關鍵詞
    "Electron",
    "quick",
    "start",
    "tutorial",
    "demo"
  ],
  "author": "GitHub", //項目的作者
  "license": "CC0-1.0", //項目的許可協議
  "devDependencies": { //項目的依賴
    "electron": "^2.0.0"
  }
}

2. main.js:這個文件是項目的啟動文件,也是項目的入口文件,該文件就是運行在主進程中的。

// 引入electron中的API

const {app, BrowserWindow} = require(‘electron’)

//定義一個web窗口 這個窗口就運行在一個一個渲染進程中

let mainWindow

//創建web窗口的方法

function createWindow () {

  // 通過BrowserWindow來創建一個窗口

  mainWindow = new BrowserWindow({width: 800, height: 600})

  // 在web窗口中加載html頁面

  mainWindow.loadFile(‘index.html’)

  // 監聽窗口關閉的事件

  mainWindow.on(‘closed’, function () {

    mainWindow = null

  })

}

// 當應用準備好了  就調用創建web窗口的方法

app.on(‘ready’, createWindow)

// 當所有的窗口都關閉后,就退出應用

app.on(‘window-all-closed’, function () {

    //在非MAC平臺下退出應用

    //在MAC平臺下所有窗口關閉 應用并不需要退出,它還可以通過dock來重新激活應用

  if (process.platform !== ‘darwin’) {

    app.quit()

  }

})

//當應用被激活事件 

app.on(‘activate’, function () {

  //如果窗口不存在 就創建一個窗口

  if (mainWindow === null) {

    createWindow()

  }

})

3. index.html:這是web窗口里展示需要展示的內容,當然這里也可以是一個遠程html的url

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    <!-- All of the Node.js APIs are available in this renderer process. -->
    We are using Node.js <script>document.write(process.versions.node)</script>,
    Chromium <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.

    <script>
      // You can also require other files to run in this process
      require('./renderer.js')
    </script>
  </body>
</html>

這里就是一個普通的html,但是這里面也有和普通html不同之處,我們可以看到html的script標簽里有用到process.xxx.xxx;并且前面沒沒有定義process,這里確可以直接訪問,html里引入script用了require(‘./renderer.js’)。這些寫法在我們平常的script里都會報錯的,但是這里確可以正常運行的,原因就是我們前面說過的Electron應用中可以直接使用Node.js的Api,這里的process就是Node.js里的變量,require就是Node.js中引入外部文件的關鍵字。

renderer.js:這個文件是被引入到index.html中的,所以它就是用來寫html中邏輯的文件,所以這個文件也是運行在渲染進程中的。

我們上面說到的4個文件的文件名和路徑并不是固定的,也可以根據項目需要進行修改,他們的關系是main.js 就是 package.json中main屬性對應的值,如果需要修改main.js的名稱,這兩處需要同時修改。index.html 就是在main.js中 mainWindow.loadFile(‘index.html’) 引入的文件,如果需要修改這兩處也需要同時修改。renderer.js就是在index.html中require(‘./renderer.js’) 引入的js文件,當然這里也可以引入多個js誰的,和我們普通的html一樣。

文件作用和關系都理清楚了,我們就可以安裝依賴了。在命令行執行

npm install

等待安裝完成之后,就可以運行項目了,在命令行執行

npm run start

這里的start 就是我們在package.json中scripts屬性中配置的值 “start”: “electron .”,我們在命令行運行 npmrun start 就相當于我們運行 npm run electron . ;因為有時有些命令比較長或帶了比較多的參數,所以我們在package.json中配置一些別名會大大減少我們在命令行的輸入。等命令運行完成我們就可以看到我們的桌面上打開了一個客戶端,如下圖:

這樣我們的一個初始項目就算搭建完成了。

Electron中常用的功能模塊

electron不光是可以將我們的頁面打包成客戶端應用,它也為我們提供了大量的功能模塊,方便我們的開發,下面我們就簡單介紹一下用的比較多的功能模塊。

1. app: 控制應用程序的事件生命周期,用于監聽應用的的事件,該模塊在主進程中。

2. BrowserWindow:創建和控制瀏覽器窗口,在創建窗口時可以傳入很多的參數,來控制窗口的樣式和行為該模塊在主進程中。

3. ipcMain:從主進程到渲染進程的異步通信,該模塊在主進程中。

4. ipcRenderer:從渲染進程到主進程的異步通信,該模塊在渲染進程中。

5. remote:在渲染進程中引入主進程模塊,該模塊在渲染進程。

6. dialog:顯示用于打開和保存文件、警報等的本機系統對話框,該模塊在主進程中。

7. crashReporter:將崩潰日志提交給遠程服務器

8. globalShortcut:當應用程序沒有鍵盤焦點時監聽全局鍵盤事件,該模塊在主進程中。

9. Menu:創建原生應用菜單和上下文菜單,該模塊在主進程中。

10. desktopCapturer:可以訪問那些用于從桌面上捕獲音頻和視頻的媒體源信息,該模塊在渲染進程中。

11. net:使用Chromium的原生網絡庫發出HTTP/ HTTPS請求,該模塊在主進程中。

12. autoUpdater:使應用程序能夠自動更新,該模塊在主進程中。

以上的這些模塊都是比較常用的模塊,所有的功能模塊并不是在任何地方都可以調用,有些模塊在主進程中可以直接引入,有些模塊只能在渲染進程中引入,當然也有些模塊可以直接主進程和渲染進程中引入使用。這些在主進程中的模塊也是可以在渲染進程中引入的,不過不能直接引入,需要通過remote模塊來間接引入,所以在主進程中的模塊也是可以在渲染進程中使用,例如我們想在渲染進程中使用BrowserWindow:

//我們需要先引入remote模塊
const {remote} = require('electron');
//再從remote模塊中引入BrowserWindow
const {BrowserWindow} = remote
let win = new BrowserWindow({width: 800, height: 600})
win.loadURL('http://www,pptvyun.com')

更多的功能模塊和使用方法可以參考Electron的文檔:https://electronjs.org/docs

Electron的Demo

Electron的大概內容已經介紹的差不多了,接下來我們就使用Electron來做一個拍照保存的小demo,能正常運行需要您的電腦上有攝像頭。我們的這個demo就基于前面搭建好的空項目來做。前面我們說過,項目的文件名和路徑并不是固定的,接下來我們就來調整一下前面那個項目的目錄結構,我們將我們需要寫代碼都放到src目錄下,調整個目錄如下:

我們將html代碼放到view目錄下,將樣式文件放到style目錄下,將圖片放入到images目錄下,將我們的js代碼放到scripts目錄下,將main.js改名為index.js(這里改文件只是為了說明這個文件名是可以修改的,并不是固定的),然后直接放到src目錄下。

package.json 修改如下

{
  "name": "take-photo",
  "version": "0.0.1",
  "description": "A take photo Electron application",
  "main": "src/index.js",
  "scripts": {
    "start": "electron ."
  },
  "repository": "",
  "keywords": ["Electron","demo"],
  "author": "pptvyun",
  "license": "MIT",
  "devDependencies": {
    "electron": "^2.0.0"
  }
}

index.js 修改如下

// 引入electron中的API

const {app, BrowserWindow} = require(‘electron’)

//定義一個web窗口 這個窗口就運行在一個一個渲染進程中

let mainWindow

//創建web窗口的方法

function createWindow () {

    // 通過BrowserWindow來創建一個窗口

    mainWindow = new BrowserWindow({width: 800, height: 600})

    // 在web窗口中加載html頁面

    mainWindow.loadFile(`${__dirname}/view/index.html`)

    // 監聽窗口關閉的事件

    mainWindow.on(‘closed’, function () {

        mainWindow = null

    })

}

// 當應用準備好了  就調用創建web窗口的方法

app.on(‘ready’, createWindow)

// 當所有的窗口都關閉后,就退出應用

app.on(‘window-all-closed’, function () {

    //在非MAC平臺下退出應用

    //在MAC平臺下所有窗口關閉 應用并不需要退出,它還可以通過dock來重新激活應用

    if (process.platform !== ‘darwin’) {

        app.quit()

    }

})

//當應用被激活事件

app.on(‘activate’, function () {

    //如果窗口不存在 就創建一個窗口

    if (mainWindow === null) {

        createWindow()

    }

})

index.html 修改如下

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    <!-- All of the Node.js APIs are available in this renderer process. -->
    We are using Node.js <script>document.write(process.versions.node)</script>,
    Chromium <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.

    <script>
       //這里修改了renderer.js的引入路徑
      require('../scripts/renderer.js')
    </script>
  </body>
</html>

接下來我們就根據我們的業務來寫具體的代碼,首先我們的代碼肯定是需要調試的,Electron使用的是Chromium來做渲染的,所以它也給我們提供了chrome的開發者工具供我們使用。開發完成后的代碼如下

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>take photo</title>
    <link rel="stylesheet" href="../style/index.css">
</head>
<body>
<header>拍照保存</header>
<div class="content">
    <video src="" id="video"></video>
    <canvas width="250" height="187" id="canvas"></canvas>
    <div class="button-box">
        <a href="javascript:;" id="openCamera" class="button">打開攝像頭</a>
        <a href="javascript:;" id="takePhoto" class="button">拍攝照片</a>
        <a href="javascript:;" id="savePhoto" class="button">保存照片</a>
    </div>
</div>

<script>
    require('../scripts/renderer.js')
</script>
</body>
</html>

src/style/index.css

src/index.js

// 引入electron中的API
const {app, BrowserWindow,globalShortcut} = require('electron')

//定義一個web窗口 這個窗口就運行在一個一個渲染進程中
let mainWindow

//創建web窗口的方法
function createWindow () {
    // 通過BrowserWindow來創建一個窗口
    mainWindow = new BrowserWindow({width: 800, height: 600})

    // 在web窗口中加載html頁面
    mainWindow.loadFile(`${__dirname}/view/index.html`)

    // 監聽窗口關閉的事件
    mainWindow.on('closed', function () {
        mainWindow = null
    })
    //這里我們注冊一個打開 開發者工具有快捷鍵 win下是Ctrl+F12 macOS下是command+f12
    globalShortcut.register('CommandOrControl+F12', () => {
        mainWindow.webContents.openDevTools()
    })
}

// 當應用準備好了  就調用創建web窗口的方法
app.on('ready', createWindow)

// 當所有的窗口都關閉后,就退出應用
app.on('window-all-closed', function () {
    //在非MAC平臺下退出應用
    //在MAC平臺下所有窗口關閉 應用并不需要退出,它還可以通過dock來重新激活應用
    if (process.platform !== 'darwin') {
        app.quit()
    }
})

//當應用被激活事件
app.on('activate', function () {
    //如果窗口不存在 就創建一個窗口
    if (mainWindow === null) {
        createWindow()
    }
})

src/sctipts/renderer.js

//引入Electron中的dialog 用來顯示消息提示
const {dialog} = require('electron').remote
//引入Node.js的fs模塊  用來寫文件
const fs = require('fs');
//頁面打開攝像頭的按鈕
const button = document.querySelector('#openCamera');
//頁面拍攝照片的按鈕
const takephoto = document.querySelector('#takePhoto');
//頁面保存照片的按鈕
const saveImage = document.querySelector('#savePhoto');
//頁面的video標簽,用來展示攝像頭拍攝的畫面
const video = document.querySelector('#video');
//頁面的canvas標簽,用來展示拍攝的照片
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');

//獲取攝像頭的參數
const mediaConfig = {
    video: true
}
//點擊打開攝像頭的按鈕
button.addEventListener('click',e=>{
    //調用電腦的攝像頭
    navigator.mediaDevices.getUserMedia(mediaConfig)
        .then(function(stream) {
            //將攝像頭拍攝的內容給到video標簽,并從video標簽上播放
            video.srcObject = stream;
            video.onloadedmetadata = function(e) {
                video.play();
            };
        })
        .catch(function(err) {
            alert('打開挺像頭失敗')
        });
})

//點擊拍照按鈕
takephoto.addEventListener('click',e=>{
    //將video上的畫面畫到canvas上
    ctx.drawImage(video,0,0,250,187);
})
//點擊保存圖片按鈕
saveImage.addEventListener('click',e=>{
    //選擇圖片的保存路徑
    dialog.showOpenDialog({properties:['openDirectory']},filePaths=>{
        if(filePaths && filePaths[0]){
            //保存圖片的目錄
            const floder = filePaths[0]+'/';
            //獎canvas上的圖片轉成base64編碼
            let dataURL = canvas.toDataURL('image/png');
            var regex = /^data:.+\/(.+);base64,(.*)$/;
            if(dataURL){
                var matches = dataURL.match(regex);
                var buffer = new Buffer(matches[2], 'base64');
                //將文件流寫入選擇的目錄里
                const file = floder+Date.now()+'.' + matches[1];
                fs.writeFile(file, buffer,err=>{
                    if(err){
                        dialog.showErrorBox("錯誤提示", '圖片保存失敗')
                    }else{
                        //圖片保存成功的提示
                        dialog.showMessageBox({
                            type:'info',
                            title:'提示信息',
                            message:'圖片保存成功,文件保存在'+file,
                        })
                    }
                });
            }
        }
    })
})

代碼開發完成后,運行項目,在命令行輸入

npm run start

就可以看到項目結果了,頁面會出現3個按鈕,點擊打開攝頭,客戶端上就會出現攝像頭拍攝的內容,然后點擊保存照片按鈕,客戶端就會出現一張靜態的圖片,然后點擊保存按鈕,這張圖片就會保存在我們選擇的目錄下。效果如下圖:

Electron項目的構建

這個demo開發到這里就算完成了,但是我們的客戶端還是需要給別人來使用,總不能讓別人把我們的代碼拷過去,然后安裝依賴,在命令行運行npm run start吧,我們常見的客戶端都是有一個安裝程序,安裝之后就可以直接雙擊運行的,或者是一個綠色版,有一個可執行程序,我們雙擊就可以運行的。接下來我們就也把我們的demo打包成一個安裝程序,可供用戶安裝使用。

將Electron打包成安裝程序有多種方式,今天我們說的是通過 electron-builder 模塊來打包,electron-builder也是一個Node.js的模塊,所以我們可以直接通過npm來安裝,在命令行運行

npm install electron-builder –save-dev

等待安裝完成就可以了,它的文檔在https://github.com/electron-userland/electron-builder ,它可以打包出window、macOS、Linux上的可執行程序。然后我們參照文檔修改配置文件,這里只需要修改package.json就好。修改后如下

{
  "name": "take-photo",
  "version": "0.0.1",
  "description": "A take photo Electron application",
  "main": "src/index.js",
  "scripts": {
    "start": "electron .",
    "build-all": "electron-builder -mwl",
    "build-win": "electron-builder --win --x64",
    "build-mac": "electron-builder --mac --x64",
    "build-linux": "electron-builder --linux --x64"
  },
  "build": {
    "appId": "com.pptvyun.test",
    "copyright": "pptvyun",
    "productName": "demo",
    "mac": {
      "target": "dmg"
    },
    "dmg": {
      "window": {
        "x": 200,
        "y": 200,
        "width": 800,
        "height": 600
      }
    },
    "win": {
      "icon": "logo.png",
      "target": "nsis"
    },
    "nsis": {
      "oneClick": false,
      "allowToChangeInstallationDirectory": true,
      "artifactName": "Setup-${productName}-${version}-${arch}.${ext}"
    },
    "snap": {}
  },
  "repository": "",
  "keywords": [
    "Electron",
    "demo"
  ],
  "author": "pptvyun",
  "license": "MIT",
  "devDependencies": {
    "electron": "^2.0.0",
    "electron-builder": "^20.19.2"
  }
}

在scripts中添加了在各個平臺上的打包腳本,添加了build屬性,用來配置在各個平臺上的打包參數。我們用打包出window平臺的安裝程序為例,在命令行運行

npm run build-win

等待打包完成,第一次可能會有點慢,因為它需要下載一些依賴,打包完成后我們的項目里就會多出來一個build的目錄,當然這個目錄是我們在package.json中配置的,這個目錄里就是打包之后的文件,里面有一個win-unpacked的目錄,這個目錄里有一個demo.exe的文件,這個文件就是可執行程序,這個win-unpacked目錄里的文件就相當于是我們平常見的綠色免安裝版,直接雙擊demo.exe就可以運行這個demo。還有一個Setup-demo-0.0.1.exe文件,這個文件就是我們需要的安裝程序,雙擊它就可以運行安裝,安裝完成后也會在我們的桌面創建快捷方式,不同平臺的打包命令可能需要到相應的平臺上去執行,比如在window上執行macOS的打包命令可能會失敗。

至此,我們的demo就算開發完成了,當然這里還有一些細節上的東西需要注意,比如我們的應用需要在各大平臺上發布,供其他用戶來下載安裝,就需要對代碼進行簽名,代碼簽名具體可參考文檔https://electronjs.org/docs/tutorial/code-signing ,這里的客戶端界面我們用的是默認的,當然我們也可以做一個無邊框的應用,然后自定義客戶端的界面,還有自動升級等等功能,Electron都是可以支持的,可以查看它的官方文檔做出功能豐富的客戶端面應用。

我還沒有學會寫個人說明!

2019年度IT168技術卓越獎名單:數據庫類

上一篇

2019年度IT168技術卓越獎名單:云計算類

下一篇

你也可能喜歡

使用Electron構建跨平臺的桌面應用

長按儲存圖像,分享給朋友

ITPUB 每周精要將以郵件的形式發放至您的郵箱


微信掃一掃

微信掃一掃
30岁的男人干啥赚钱快赚钱多 澳洲幸运8 qq麻将游戏下载 十大期货配资公司排名 河南22开奖结果今天 怎样网赚 分分彩漏洞获利2000万 黑龙江p62最新开奖 股票为什么会涨或跌 网赚兼职网 哈灵上海麻将官方下载