再探 Inter-App Communication (IAC)
先前 EragonJ 跟大家介紹過 Inter App Communication 的使用情境以及使用範例程式碼,本篇將繼續討論 Inter-App Communcation(文章後面將縮寫為 IAC )其他較少為人知的面向。
雙向通訊連線
使用 IAC 建立連線的程式碼通常如下:
navigator.mozApps.getSelf().onsuccess = function(evt) { var app = evt.target.result; app.connect('some-iac-keyword').then(function onConnAccepted(ports) { // send messages }, function onConnRejected(reason) { // error handling }); }
但其實 IAC 建立出來的連線是雙向連線,連線兩端的 app 只要拿得到 port 就可以傳送訊息給另一端 app 。因此在某些情況下, app 可以借助 IACHandler 的幫助,不需要事先透過 mozApps API 建立連線而直接使用下方的程式碼與其他 app 以 IAC 進行溝通。
var port; try { port = IACHandler.getPort('some-iac-keyword'); port.postMessage(message); } catch (e) { // report error }
這樣做的假設前提是:另一端的 app 一定會先建立連線,因此這時透過 keyword 一定可以取得 port 。
什麼情況適用呢?訊息傳遞如果是 request 和 response 成對一來一回進行的(如下圖),就非常適用這種寫法,這個情境是由 system app 先主動對其他 app 建立連線(藍色箭頭),則其他 app 在回應訊息時,就可以利用上述方式回傳訊息,而不必自行建立連線。
限制訊息傳送來源或訊息接收對象
在 manifest.webapp 定義 IAC 連線的 connection 時,可以使用 rules
這個參數定義我們只接受哪些條件的訊息傳入。如果我們撰寫的 app ,只接受 system app 送來的訊息,不希望聽到來路不明的 app 所送來的訊息,可以在宣告 manifest.webapp 時加上 rules 的設置如下:
"some-iac-keyword": { "description": "Some Inter-app Communication description", "rules": { "manifestURLs": ["app://system.gaiamobile.org"], } }
同理,如果我們透過 IAC 傳送訊息給特定 app 時,如果不希望來路不明的 app 偷聽我們所傳送的內容,在建立連線時也可以加以限制如下:
var port = (location.port) ? ':' + location.port : ''; var systemManifestURLs = []; // we don't want any app receive our message except system app systemManifestURLs.push(location.protocol + '//system.gaiamobile.org' + port + '/manifest.webapp'); navigator.mozApps.getSelf().onsuccess = function(evt) { var app = evt.target.result; app.connect('some-iac-keyword', { manifestURLs: systemManifestURLs }).then(function onConnAccepted(ports) { // send messages }, function onConnRejected(reason) { // error handling }); }
詳細使用方式可以參考 IAC wiki 的 Publisher 和 Subscriber 章節。
App 物件快取
常見的 IAC 建立連線以及傳送訊息的方式如下:
navigator.mozApps.getSelf().onsuccess = function(evt) { var app = evt.target.result; app.connect('some-iac-keyword').then(function onConnAccepted(ports) { ports.forEach(function(port) { port.postMessage(message); }); // send messages }, function onConnRejected(reason) { // error handling }); }
但這樣寫有一個效能上的缺點,就是每次傳送訊息都必須要透過 mozApps API 的 geSelf()
取得自身的 app 物件,然而 getSelf()
是非同步的 DOMRequest ,事實上在同一個 app 內部每次呼叫 getSelf()
取得的 app 物件都是同一個,我們何不直接把它 cache 起來以避免不必要的 DOMRequest 呢?
var SomeClass = { ... _realConnect: function realConnect() { var port = (location.port) ? ':' + location.port : ''; this.app.connect('some-iac-keyword').then(function onConnAccepted(ports) { // do something when connected }, function onConnRejected(reason) { // report error }); }, connect: function connect() { var that = this; if (this.app) { // if we have app object already, then we do things directly // without firing getSelf() DOMRequest again this._realConnect.apply(this); return; } navigator.mozApps.getSelf().onsuccess = function(evt) { // cache app object that.app = evt.target.result; that._realConnect.apply(that); }; ... }, ... };
如上,我們在 SomeClass 內部有一個 _realConnect()
負責真正建立 IAC 連線, connect()
則是提供外部元件呼叫的函式。當外部元件第一次呼叫 connect()
時, this.app 不存在,因此我們透過 mozApps.getSelf()
取得 app 物件,並把它存在 that.app ,之後當外部元件再次呼叫 connect()
時,就不必再多花一次 DOMRequest 的成本,可以直接呼叫 _realConnect()
建立 IAC 連線。
希望以上三項資訊可以幫助開發者朋友們更加深入了解 IAC ,並更加妥善地利用 IAC 寫出更好用的 web app 。