electron開發入門(二)程式通訊

weixin_34208185發表於2018-02-24

目錄

  1. 主程式與渲染程式
  2. 主程式與渲染程式之間的通訊

1. 主程式與渲染程式

在 electron 中,最重要的一個概念就是主程式和渲染程式。

1.1 主程式

main.js在啟動應用後就建立了一個主程式-main process,它可以通過electron中的一些模組直接與原生GUI(在你的應用視窗)互動。

1.2 渲染程式

僅啟動主程式並不能給你的應用建立應用視窗。視窗是通過main檔案裡的主程式呼叫叫BrowserWindow的模組建立的。

上述示例中的index.html頁面,是主程式建立了一個渲染程式視窗所載入的Web頁面,每個頁面都是執行在自己的程式裡,這些程式我們稱之為渲染程式。

渲染程式會在視窗中渲染出web頁面(引用了CSS,JavaScript,圖片等的HTML檔案)。web頁面是Chromium渲染的,因為各系統下標準是統一的的,所以相容性很好。

1.3 主程式與渲染程式的關係

主程式通過構造BrowserWindow例項來建立頁面。每個 BrowserWindow例項都在自己的渲染程式裡執行頁面。當一個 BrowserWindow 例項被銷燬後,相應的渲染程式也會被終止。

主程式管理所有頁面和與之對應的渲染程式。每個渲染程式都是相互隔離的,並且只知道執行在該程式裡的頁面。

在頁面裡呼叫本地GUI是不允許的,因為在Web頁面裡管理本地GUI資源是非常危險而且容易造成資源洩露。如果你想在網頁裡進行GUI操作,該頁面的渲染程式必須與主程式進行通訊,請求主程式進行相關的 GUI 操作。

2. 主程式與渲染程式之間的通訊

在electron中,主程式與渲染程式有很多通訊的方法。比如ipcRenderer和ipcMain,還可以在渲染程式使用remote模組。

2.1 ipcMain & ipcRenderer

  • 主程式:ipcMain
  • 渲染程式:ipcRenderer

ipcMain模組和ipcRenderer是類EventEmitter的例項。

在主程式中使用ipcMain接收渲染執行緒傳送過來的非同步或同步訊息,傳送過來的訊息將觸發事件。

在渲染程式中使用ipcRenderer向主程式傳送同步或非同步訊息,也可以接收到主程式的訊息。

  • 傳送訊息,事件名為 channel .
  • 迴應同步訊息, 你可以設定 event.returnValue .
  • 迴應非同步訊息, 你可以使用 event.sender.send(...)

下面給出一個簡單例子:

// In main process.
const ipcMain = require('electron').ipcMain;
ipcMain.on('asynchronous-message', function(event, arg) {
console.log(arg); // prints "ping"
event.sender.send('asynchronous-reply', 'pong');
});
ipcMain.on('synchronous-message', function(event, arg) {
console.log(arg); // prints "ping"
event.returnValue = 'pong';
});

// In renderer process (web page).
const ipcRenderer = require('electron').ipcRenderer;
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')); // prints "pong"
ipcRenderer.on('asynchronous-reply', function(event, arg) {
console.log(arg); // prints "pong"
});
ipcRenderer.send('asynchronous-message', 'ping');

同樣也可以從主程式向渲染程式傳送訊息,使用的是 webContents.send方法,下面是具體的例子:

// 主程式.main.js
var window = null;
app.on('ready', function() {
window = new BrowserWindow({width: 800, height: 600});
window.loadURL('file://' + __dirname + '/index.html');
window.webContents.on('did-finish-load', function() {
window.webContents.send('ping', 'whoooooooh!');
});
});
<!-- index.html -->
<html>
<body>
<script>
require('electron').ipcRenderer.on('ping', function(event, message) {
console.log(message); // Prints "whoooooooh!"
});
</script>
</body>
</html>

2.2 remote模組

remote模組支援RPC風格的通訊,在渲染程式中獲取主程式建立的一些全域性物件和應用資訊,還可以呼叫主程式所提供的一些方法,如重啟應用、操作渲染程式等。

remote 模組提供了一種在渲染程式( 網頁) 和主程式之間進行程式間通訊( IPC) 的簡便途徑。使用remote 模組,可以呼叫主程式物件的方法,而無需顯式地傳送程式間訊息,這類似於 Java
的 RMI。

下面是從渲染程式建立一個瀏覽器視窗的例子:

const remote = require('electron').remote;
const BrowserWindow = remote.BrowserWindow;
var win = new BrowserWindow({ width: 800, height: 600 });
win.loadURL('https://github.com');

注意: 反向操作( 從主程式訪問渲染程式) ,可以使用webContents.executeJavascript.

遠端物件

remote 模組返回的每個物件( 包括函式) 都代表了主程式中的一個物件(我們稱之為遠端物件或者遠端函式)。當呼叫遠端物件的方法、執行遠端函式或者使用遠端構造器( 函式) 建立新物件時,其實就是在傳送同步的程式間訊息。

在上面的例子中, BrowserWindow 和 win 都是遠端物件,然而 new BrowserWindow 並沒有在渲染程式中建立 BrowserWindow 物件。 而是在主程式中建立了BrowserWindow 物件,並在渲染程式中返回了對應的遠端物件,即 win 物件。

請注意只有可列舉屬性才能通過 remote 進行訪問.

遠端物件的生命週期

Electron 確保在渲染程式中的遠端物件存在(換句話說,沒有被垃圾收集),那主程式中的對應物件也不會被釋放。當遠端物件被垃圾收集之後,主程式中的對應物件才會被取消關聯。如果遠端物件在渲染程式洩露了(即,存在某個表中但永遠不會釋放),那麼主程式中的對應物件也一樣會洩露,所以你必須小心不要洩露了遠端物件。

不過,主要的值型別如字串和數字,是傳遞的副本。

給主程式傳遞迴調函式

在主程式中的程式碼可以從渲染程式——remote模組——中接受回撥函式,但是使用這個功能的時候必須非常非常小心。

首先,為了避免死鎖,傳遞給主程式的回撥函式會進行非同步呼叫。所以不能期望主程式來獲得傳遞過去的回撥函式的返回值。

比如,你不能主程式中給 Array.map 傳遞來自渲染程式的函式。

// 主程式 mapNumbers.js
exports.withRendererCallback = function(mapper) {
    return [1,2,3].map(mapper);
} 

exports.withLocalCallback = function() {
    return exports.mapNumbers(function(x) {
        return x + 1;
    });
} 

// 渲染程式
var mapNumbers = require("remote").require("./mapNumbers");

var withRendererCb = mapNumbers.withRendererCallback(function(x) {
    return x + 1;
})

var withLocalCb = mapNumbers.withLocalCallback()

console.log(withRendererCb, withLocalCb) // [true, true, true], [2, 3, 4]

如你所見,渲染器回撥函式的同步返回值沒有按預期產生,與主程式中的一模一樣的回撥函
數的返回值不同。

其次,傳遞給主程式的函式會持續到主程式對他們進行垃圾回收。

例如,下面的程式碼第一眼看上去毫無問題。給遠端物件的 close 事件繫結了一個回撥函式:

remote.getCurrentWindow().on('close', function() {
// blabla...
});

但記住主程式會一直保持對這個回撥函式的引用,除非明確的解除安裝它。如果不解除安裝,每次重新載入視窗都會再次繫結,這樣每次重啟就會洩露一個回撥函式。

更嚴重的是,由於前面安裝了回撥函式的上下文已經被釋放,所以當主程式的 close 事件觸
發的時候,會丟擲異常。

為了避免這個問題,要確保對傳遞給主程式的渲染器的回撥函式進行清理。可以清理事件處
理器,或者明確告訴主進行取消來自已經退出的渲染器程式中的回撥函式。

訪問主程式中的內建模組

在主程式中的內建模組已經被新增為 remote 模組中的屬性,所以可以直接像使
用 electron 模組一樣直接使用它們。

const app = remote.app;

方法

remote 模組有以下方法:

  • remote.require(module)
    module String
    返回在主程式中執行 require(module) 所返回的物件。
  • remote.getCurrentWindow()
    返回該網頁所屬的 BrowserWindow 物件。
  • remote.getCurrentWebContents()
    返回該網頁的 WebContents 物件
  • remote.getGlobal(name)
    name String
    返回在主程式中名為 name 的全域性變數(即 global[name] ) 。
  • remote.process
    返回主程式中的 process 物件。等同於 remote.getGlobal('process') 但是有快取

相關文章