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