從 微信 JS-SDK 認識 JSBridge
作者: 熊的貓
https://juejin.cn/post/7199297355748458551
前言
前段時間由於要實現 H5 移動端拉取微信卡包並同步卡包數據的功能,於是在項目中引入了 微信 JS-SDK(jweixin)[1] 相關包實現功能,但也由此讓我對其產生了好奇心,於是打算好好了解下相關的內容,通過查閱相關資料發現這其實屬於 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 直接調用
-
中間層 Node Bindings
-
主要是使 JavaScript 和 C/C++ 進行通信,原因是 JavaScript 無法直接調用 C/C++ 的庫(libuv),需要一箇中間的橋樑,node 中提供了很多 binding,這些稱爲
Node bindings
-
底層 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[2]》中認爲 “任何可以使用 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
上:
在開發過程中遇到的一個 坑點
就是:
-
在真機中,需要實現同一域名下不同子路徑的應用實現數據交互(純前端操作,不涉及接口),由於同域名且是基於同一個頁面進行跳轉的(當然只是看起來是),而且這個數據是 臨時數據,因此覺得使用
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[3] 來看看。
當通過 <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()
webView.loadUrl("javascript:foo()")
-
loadUrl()
不能獲取JavaScript
執行後的結果,這種方式更像在<a href="javascript:void(0)">
的href
屬性中編寫的JavaScript
代碼 -
安卓 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
是一個高大上、深不可測的東西了,畢竟其核心思想是清晰明瞭的,而且本質上還是需要強依賴於原生端的具體實現。
參考資料
-
JSBridge 的原理
-
JS Bridge 通信原理與實踐
-
webview 到底是什麼?
參考資料
[1] 微信 JS-SDK(jweixin): https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html
[2] The Principle of Least Power: https://link.zhihu.com/?target=https%3A//blog.codinghorror.com/the-principle-of-least-power/
[3] 微信 JS-SDK: https://res2.wx.qq.com/open/js/jweixin-1.4.0.js
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/yi5U8FjeJ9YKc1u4Bqp5Fg