前言
前段時間由於要實現 H5 移動端拉取微信卡包並同步卡包資料的功能,於是在專案中引入了 微信 JS-SDK(jweixin)
相關包實現功能,但也由此讓我對其產生了好奇心,於是打算好好了解下相關的內容,透過查閱相關資料發現這其實屬於 JSBridge
的一種實現方式。
因此,只要瞭解 JSBridge
就能明白 微信 JS-SDK
是怎麼一回事。
為什麼需要 JSBridge?
相信大多數人都有相同的經歷,第一次瞭解到關於 JSBridge
都是從 微信 JS-SDK(WeiXinJSBridge)
開始,當然如果你從事的是 Hybrid 應用
或 React-Native
開發的話相信你自然(應該、會)很瞭解。
其實 JSBridge
早就出現並被實際應用了,如早前桌面應用的訊息推送等,而在移動端盛行的時代已經越來越需要 JSBridge
,因為我們期望移動端(Hybrid 應用
或 React-Native
)能做更多的事情,其中包括使用 客戶端原生功能 提供更好的 互動 和 服務 等。
然而 JavaScript 並不能直接呼叫和它不同語言(如 Java、C/C++ 等)提供的功能特性,因此需要一箇中間層去實現 JavaScript 與 其他語言 間的一個相互協作,這裡透過一個 Node
架構來進行說明。
Node 架構
核心內容如下:
頂層 Node Api
- 提供 http 模組、流模組、fs檔案模組等等,
可以透過 JavaScript 直接呼叫
- 提供 http 模組、流模組、fs檔案模組等等,
中間層 Node Bindings
- 主要是使 JavaScript 和 C/C++ 進行通訊,原因是 JavaScript 無法直接呼叫 C/C++ 的庫(libuv),需要一箇中間的橋樑,node 中提供了很多 binding,這些稱為
Node bindings
- 主要是使 JavaScript 和 C/C++ 進行通訊,原因是 JavaScript 無法直接呼叫 C/C++ 的庫(libuv),需要一箇中間的橋樑,node 中提供了很多 binding,這些稱為
底層 V8 + libuv
- v8 負責解釋、執行頂層的 JavaScript 程式碼
- libuv 負責提供 I/O 相關的操作,其主要語言是
C/C++
語言,其目的就是實現一個 跨平臺(如 Windows、Linux 等)的非同步 I/O 庫,它直接與作業系統進行互動
這裡不難發現 Node Bindings
就有點類似 JSBridge
的功能,所以 JSBridge 本身是一個很簡單的東西,其更多的是 一種形式、一種思想。
為什麼叫 JSBridge?
Stack Overflow 聯合創始人 Jeff Atwood
在 2007 年的部落格《The Principle of Least Power》中認為 “任何可以使用 JavaScript 來編寫的應用,並最終也會由 JavaScript 編寫”,後來 JavaScript 的發展確實非常驚人,現在我們可以基於 JavaScript 來做各種事情,比如 網頁、APP、小程式、後端等,並且各種相關的生態越來越豐富。
作為 Web 技術邏輯核心的 JavaScript
自然而然就需要承擔與 其他技術 進行『橋接』的職責,而且任何一個 移動作業系統 中都會包含 執行 JavaScript 的容器環境,例如 WebView
、JSCore
等,這就意味著 執行 JavaScript 不用像執行其他語言時需要額外新增相應的執行環境。
JSBridge
應用在國內真正流行起來則是因為 微信 的出現,當時微信的一個主要功能就是可以在網頁中透過JSBridge
來實現 內容分享。
JSBridge 能做什麼?
舉個最常見的前端和後端的例子,後端只提供了一個查詢介面,但是沒有提供更新介面,那麼對於前端來講就是再想實現更新介面,也是沒有任何法子的!
同樣的,JSBridge 能做什麼得看原生端給 JavaScript 提供呼叫 Native 什麼功能的介面,比如透過 微信 JS-SDK
網頁開發者可藉助微信使用 拍照、選圖、語音、位置 等手機系統的能力,同時可以直接使用 微信分享、掃一掃、卡券、支付 等微信特有的能力。
JSBridge
作為 JavaScript
與 Native
之間的一個 橋樑,表面上看是允許 JavaScript 呼叫 Native 的功能,但其核心是建立 Native 和 非 Native 間訊息 雙向通訊 通道。
雙向通訊的通道:
JavaScript 向 Native 傳送訊息:
- 呼叫 Native 功能
- 通知 Native 當前 JavaScript 的相關狀態等
Native 向 JavaScript 傳送訊息:
- 回溯呼叫結果
- 訊息推送
- 通知 JavaScript 當前 Native 的狀態等
JSBridge 是如何實現的?
JavaScript 的執行需要 JS 引擎的支援,包括 Chrome V8
、Firefox SpiderMonkey
、Safari JavaScriptCore
等,總之 JavaScript 執行環境 是和 原生執行環境 是天然隔離的,因此,在 JSBridge 的設計中我們可以把它 類比 成 JSONP 的流程:
- 客戶端透過
JavaScript
定義一個回撥函式,如:function callback(res) {...}
,並把這個回撥函式的名稱以引數的形式傳送給服務端 - 服務端獲取到
callback
並攜帶對應的返回資料,以JS
指令碼形式返回給客戶端 - 客戶端接收並執行對應的
JS
指令碼即可
JSBridge 實現 JavaScript 呼叫的方式有兩種,如下:
JavaScript
呼叫Native
Native
呼叫JavaScript
在開始分析具體內容之前,還是有必要了解一下前置知識 WebView。
WebView 是什麼?
WebView 是 原生系統 用於 移動端 APP
嵌入 Web
的技術,方式是內建了一款高效能 webkit 核心瀏覽器,一般會在 SDK 中封裝為一個 WebView
元件。
WebView
具有一般 View
的屬性和設定外,還對 url
進行請求、頁面載入、渲染、頁面互動進行增強處理,提供更強大的功能。
WebView 的優勢 在於當需要 更新頁面佈局 或 業務邏輯發生變更 時,能夠更便捷的提供 APP 更新:
- 對於
WebView
而言只需要修改前端部分的Html、Css、JavaScript
等,通知使用者端進行重新整理即可 - 對於
Native
而言需要修改前端內容後,再進行打包升級,重新發布,通知使用者下載更新,安裝後才可以使用最新的內容
微信小程式中的 WebView
小程式的主要開發語言是 JavaScript
,其中 邏輯層 和 渲染層 是分開的,分別執行在不同的執行緒中,而其中的渲染層就是執行在 WebView
上:
執行環境 | 邏輯層 | 渲染層 |
---|---|---|
iOS | JavaScriptCore | WKWebView |
安卓 | V8 | chromium 定製核心 |
小程式開發者工具 | NWJS | Chrome WebView |
在開發過程中遇到的一個 坑點
就是:
- 在真機中,需要實現同一域名下不同子路徑的應用實現資料互動(純前端操作,不涉及介面),由於同域名且是基於同一個頁面進行跳轉的(當然只是看起來是),而且這個資料是 臨時資料,因此覺得使用
sessionStorage
實現資料互動是很合適的 - 實際上從 A 應用 跳轉到 B 應用 中卻無法獲取對應的資料,而這是因為 sessionStorage 是基於當前視窗的會話級的資料儲存,移動端瀏覽器 或 微信內建瀏覽器 中在跳轉新頁面時,可能開啟的是一個新的 WebView,這就相當於我們在瀏覽器中的一個新視窗中進行儲存,因此是沒辦法讀取在之前的視窗中儲存的資料
JavaScript 呼叫 Native — 實現方案一
透過 JavaScript 呼叫 Native 的方式,又會分為:
- 注入 API
- 劫持 URL Scheme
- 彈窗攔截
【 注入 API 】
核心原理:
- 透過
WebView
提供的介面,向JavaScript
的上下文(window
)中注入 物件 或者 方法 - 允許
JavaScript
進行呼叫時,直接執行相應的Native
程式碼邏輯,實現JavaScript
呼叫Native
這裡不透過 iOS
的 UIWebView
和 WKWebView
注入方式來介紹了,感興趣可以自行查詢資料,我們們這裡直接透過 微信 JS-SDK 來看看。
當透過 <script src="https://res2.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
的方式引入 JS-SDK
之後,就可以在頁面中使用和 微信相關的 API,例如:
// 微信授權
window.wx.config(wechatConfig)
// 授權回撥
window.wx.ready(function () {...})
// 異常處理
window.wx.error(function (err) {...})
// 拉起微信卡包
window.wx.invoke('chooseInvoice', invokeConf, function (res) {...})
如果透過其內部編譯打包後的程式碼(簡化版)來看的話,其實不難發現:
- 其中的
this
(即引數e
)此時就是指向全域性的window
物件 - 在程式碼中使用的
window.wx
實際上是e.jWeixin
也是其中定義的N
物件 - 而在
N
物件中定義的各種方法實際上又是透過e.WeixinJSBridge
上的方法來實際執行的 e.WeixinJSBridge
就是由 微信內建瀏覽器 向window
物件中注入WeiXinJsBridge
介面實現的!(function (e, n) { 'function' == typeof define && (define.amd || define.cmd) ? define(function () { return n(e) }) : n(e, !0) })(this, function (e, n) { ... function i(n, i, t) { e.WeixinJSBridge ? WeixinJSBridge.invoke(n, o(i), function (e) { c(n, e, t) }) : u(n, t) } if (!e.jWeixin) { var N = { config(){ i(...) }, ready(){}, error(){}, ... } return ( S.addEventListener( 'error',callback1, !0 ), S.addEventListener( 'load',callback2, !0 ), n && (e.wx = e.jWeixin = N), N ) } })
【 劫持 URL Scheme 】
URL Scheme 是什麼?
URL Scheme
是一種特殊的 URL
,一般用於在 Web
端喚醒 App
(或是跳轉到 App
的某個頁面),它能方便的實現 App
間互相呼叫(例如 QQ 和 微信 相互分享訊息)。
URL Scheme
的形式和 普通 URL
(如:https://www.baidu.com
)相似,主要區別是 protocol
和 host
一般是對應 APP
自定義的。
通常當 App
被安裝後會在系統上註冊一個 自定義的 URL Scheme
,比如 weixin://
這種,所以我們在手機瀏覽器裡面訪問這個 scheme
地址,系統就會喚起對應的 App
。
例如,當在瀏覽器中訪問 weixin://
時,瀏覽器就會詢問你是否需要開啟對應的 APP
:
劫持原理
Web
端透過某種方式(如 iframe.src
)傳送 URL Scheme
請求,之後 Native
攔截到請求並根據 URL Scheme
和 攜帶的引數
進行對應操作。
例如,對於谷歌瀏覽器可以透過 chrome://version/、chrome://chrome-urls/、chrome://settings/
定位到不同的頁面內容,假設 跳轉到谷歌的設定頁並期望當前搜尋引擎改為百度,可以這樣設計 chrome://settings/engine?changeTo=baidu&callbak=callback_id
:
- 谷歌客戶端可以攔截這個請求,去解析對應引數
changeTo
來修改預設引擎 - 然後透過
WebView
上面的callbacks
物件來根據callback_id
進行回撥
以上只是一個假設哈,並不是說真的可以這樣去針對谷歌瀏覽器進行修改,當然它要是真的支援也不是不可以。
是不是感覺確實和 JSONP
的流程很相似呀 ~ ~
【 彈窗攔截 】
彈窗攔截核心:利用彈窗會觸發 WebView
相應事件來實現的。
一般是在透過攔截 Prompt、Confirm、Alert
等方法,然後解析它們傳遞過來的訊息,但這種方法存在的缺陷就是 iOS
中的 UIWebView
不支援,而且 iOS
中的 WKWebView
又有更好的 scriptMessageHandler
,因此很難統一。
Native 呼叫 JavaScript — 實現方案二
Native
呼叫 JavaScript
的方式本質就是 執行拼接 JavaScript
字串,這就好比我們透過 eval()
函式來執行 JavaScript
字串形式的程式碼一樣,不同的系統也有相應的方法執行 JavaScript
指令碼。
Android
在 Android
中需要根據版本來區分:
安卓 4.4 之前的版本使用
loadUrl()
loadUrl()
不能獲取JavaScript
執行後的結果,這種方式更像在<a href="javascript:void(0)">
的href
屬性中編寫的JavaScript
程式碼webView.loadUrl("javascript:foo()")
安卓 4.4 以上版本使用
evaluateJavascript()
webView.evaluateJavascript("javascript:foo()", null);
IOS
在
IOS
中需要根據不同的WebView
進行區分:UIWebView
中通常使用stringByEvaluatingJavaScriptFromString
results = [self.webView stringByEvaluatingJavaScriptFromString:"foo()"];
WKWebView
中通常使用evaluateJavaScript
[self.webView evaluateJavaScript:@"document.body.offsetHeight;" completionHandler:^(id _Nullable response, NSError * _Nullable error) { // 獲取返回值 }];
最後
以上透過
微信 JS-SDK
到JSBridge
的一個簡單介紹,大家現在應該不至於認為JSBridge
是一個高大上、深不可測的東西了,畢竟其核心思想是清晰明瞭的,而且本質上還是需要強依賴於原生端的具體實現。
希望本文對你有所幫助!!!
本文參與了SegmentFault 思否寫作挑戰賽,歡迎正在閱讀的你也加入。