在移動互聯(lián)網(wǎng)的時代,開發(fā)移動端的前端頁面是前端同學(xué)一項必不可少的技能了。而涉及到與原生移動端的交互太示,除了一些 WEEX攒钳、React Native 這種技術(shù)外帮孔,最常用也是最快捷的方式就是 JSBridge 了。所以不撑,作為前端非常有必要了解一下 JSBridge 的運作原理文兢。
移動端的瀏覽器控件
當(dāng)下主流的移動端操作系統(tǒng)無疑是 Android 和 IOS。而這兩者分別都提供了各自承載網(wǎng)頁的控件 WebView(IOS 里面是 UIWebView 和 WKWebView)焕檬。下面簡單介紹下瀏覽器控件姆坚。
Android WebView
Android 的 WebView 能夠像其他的瀏覽器 APP 一樣顯示網(wǎng)頁并對頁面做一些基礎(chǔ)的操作。由于歷史原因实愚,Android WebView 采用了兩種不同的內(nèi)核兼呵。
在 Android 4.4 以下(不包含 4.4)系統(tǒng) WebView 底層實現(xiàn)是采用 WebKit(http://www.webkit.org/) 內(nèi)核,而在 Android 4.4 及其以上 Google 采用了 chromium(http://www.chromium.org/) 作為系統(tǒng)WebView的底層內(nèi)核支持腊敲。
所以击喂,如果需要適配 4.4 以下機型需要做好樣式和JS的適配兼容工作。
IOS 的 UIWebView 和 WKWebView
在 IOS 中也是由于歷史原因碰辅,出現(xiàn)了兩個 WebView 控件懂昂。 UIWebView 是自 IOS2 就有的,而 WKWebView 從 IOS8 才有乎赴。WKWebView 使用的是 Safari 瀏覽器內(nèi)核忍法,相比于笨重的 UIWebView 性能更佳。
如果需要支持 IOS8 之前的版本榕吼,只好去兼容 UIWebView 饿序,而在 IOS8+ 的版本中,就有 UIWebView 和 WKWebView 兩種選擇了(具體看 native 端的實現(xiàn)選擇)羹蚣。
簡單介紹了下兩個操作系統(tǒng)的瀏覽器控件的情況原探,更佳具體的移動前端問題可以參考我之前整理的2018 年最新的移動前端資料整理(不斷更新)
一文。
JSBridge 原理及實現(xiàn)
具體的原理在有贊前端的# H5與Native交互之JSBridge技術(shù) 一文中講的非常清楚顽素,不多贅述咽弦。簡單說下:
IOS 調(diào)用 JavaScript 方法
在 IOS 中使用 stringByEvaluatingJavaScriptFromString
方法直接調(diào)用掛載在前端 window 下的方法,并獲取方法返回的數(shù)據(jù)胁出。
webview.stringByEvaluatingJavaScriptFromString("Math.random()")
JavaScript 調(diào)用 IOS 方法
有兩種方法可以實現(xiàn) JavaScript 調(diào)用 IOS 方法這個行為:
1. 在 JavaScript 中使用創(chuàng)建一個 iframe 請求 URL 的方式將帶有 scheme 的 URL 傳給 IOS型型。
var url = 'jsbridge://doAction?title=分享標(biāo)題&desc=分享描述&link=http%3A%2F%2Fwww.baidu.com';
var iframe = document.createElement('iframe');
iframe.style.width = '1px';
iframe.style.height = '1px';
iframe.style.display = 'none';
iframe.src = url;
document.body.appendChild(iframe);
setTimeout(function() {
iframe.remove();
}, 100);
IOS 在 WebView 獲取到網(wǎng)頁 URL 請求時將 scheme 碼的 URL 攔截下來去做 native 方法的處理。
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
print("shouldStartLoadWithRequest")
let url = request.URL
let scheme = url?.scheme
let method = url?.host
let query = url?.query
if url != nil && scheme == "jsbridge" {
print("scheme == \(scheme)")
print("method == \(method)")
print("query == \(query)")
switch method! {
case "getData":
self.getData()
case "putData":
self.putData()
case "gotoWebview":
self.gotoWebview()
case "gotoNative":
self.gotoNative()
case "doAction":
self.doAction()
case "configNative":
self.configNative()
default:
print("default")
}
return false
} else {
return true
}
}
2. 對于 WKWebView 有另外的解決方案:iOS下OC與JS的交互(WKWebview-MessageHandler實現(xiàn))
簡單說下實現(xiàn)邏輯:在 IOS 中使用 addScriptMessageHandler 方法定義 MessageHandler全蝶。
[userContentController addScriptMessageHandler:self name:@"Share"];
[userContentController addScriptMessageHandler:self name:@"Camera"];
其中的 Share 和 Camera 是 MessageHandler 的 name 屬性闹蒜。在 JS 端的使用 MessageHandler 的方式如下:
window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
最后回到 IOS 端根據(jù) MessageHandler 的 name 獲取它的 body 并處理執(zhí)行相應(yīng)操作行為寺枉。
if ([message.name isEqualToString:@"Share"]) {
[self ShareWithInformation:message.body];
} else if ([message.name isEqualToString:@"Camera"]) {
[self camera];
}
Android 調(diào)用 JavaScript 方法
在 Android 中直接使用 loadUrl 方法即可調(diào)用 JavaScript 中 window 對象上的方法。
webView.loadUrl("javascript:JSBridge.trigger('webviewReady')");
JavaScript 調(diào)用 Android 方法
有 3 種方法:
- 通過 shouldOverrideUrlLoading 方法處理 URL 的 scheme 碼绷落,做法類似 IOS 的通信方式姥闪。
- Android 中有一個 JSInterface 類可以直接注入原生 JS 代碼。
class JSInterface {
@JavascriptInterface //注意這個代碼一定要加上
public String getUserData() {
return "UserData";
}
}
webView.addJavascriptInterface(new JSInterface(), "AndroidJS");
上面的代碼就是在頁面的 window 對象里注入了 AndroidJS 對象砌烁。在js里可以直接調(diào)用
alert(AndroidJS.getUserData()) //UserDate
- 使用
prompt,console.log,alert
方式筐喳。
LJBridge 學(xué)習(xí)
m-base 源碼簡析
說了這么多,作為前端最關(guān)注必然不是 Native 端如何實現(xiàn)函喉,而是 JavaScript 端如何與 Native 端通信啦避归。下面來看看鏈家的 JSBridge 源碼方法:
function tryCatch(callback) {
// try…catch 執(zhí)行 Native 方法
}
function setBridgeInstance(bridge) {
// 設(shè)置 JSBridge 實例
}
function setBridgeInstanceForWK(bridge) {
// 設(shè)置 JSBridge 實例(適配 WKWebView)
}
function do_callback() {
// 執(zhí)行 ready 方法中的回調(diào)函數(shù)
}
function bindSysSchema(evt){
// 綁定系統(tǒng) scheme (打電話和發(fā)短信)
}
// 在 window 對象中定義 ljBridge 來使用 JSBridge
window.$ljBridge = {
ready: function() {},
webStatus: webStatus
}
// 初始化邏輯
if (isApp) {
// 將 APP 數(shù)據(jù)保存 cookie
// 獲取 bridgeInstance
// 設(shè)置 Title、RightButton函似、ShareConfig
} else {
// 非 APP 環(huán)境調(diào)試
}
LJBridge 如何通信
基于一開始講過的 JSBridge 通信原理槐脏,我們來理解下 m-base 源碼中的通信。
所有在 H5與APP通信文檔(JS bridge) 文檔中接口方法都可以在 setBridgeInstance
和 setBridgeInstanceForWK
方法中看到撇寞。
JS 獲取 Native 端數(shù)據(jù)
在這些方法中,像 getNetwork堂氯、getDeviceId 這類獲取 Native 信息的方法都是直接獲取注入的 window.HybridBridgeLJ 對象中的數(shù)據(jù)蔑担。
// 獲取APP注入的用戶device id
bridgeInstance.getDeviceId = function() {
return objectValueFromPath(bridge, 'staticData.deviceId');
};
// 獲取APP版本
bridgeInstance.getAppVersion = function() {
return objectValueFromPath(bridge, 'staticData.appVersion');
};
// 獲取APP網(wǎng)絡(luò)狀態(tài)
bridgeInstance.getNetwork = function() {
return objectValueFromPath(bridge, 'staticData.network');
};
// 獲取APP的協(xié)議
bridgeInstance.getScheme = function() {
return objectValueFromPath(bridge, 'staticData.scheme');
};
往 Android 中注入 JS 上面已經(jīng)介紹了,而往 IOS 中注入 JS 可以查看 WKWebview:與JS交互數(shù)據(jù)傳值咽白、Cookies的注入與清除 一文啤握。
JS 調(diào)用 Native 端方法
像 setRightButton
、setCity
這些 JavaScript 調(diào)用 Native 方法的行為晶框,有兩種不同的方式:
第一種是直接調(diào)用注入的 window.HybridBridgeLJ
對象中的方法排抬,原理上面提到過的 JavaScript 調(diào)用 Android 方法的第 2 條。
bridge.setShareConfigWithString(paramString);
第二種是針對 WKWebView 的 MessageHandler 形式進(jìn)行 JS 到 Native 端的通信授段。
window.webkit.messageHandlers.lianjia.postMessage(JSON.stringify({
"type": "setShareConfig",
"param": paramString
}));
LJBridge 的 ready 方法
ready 是在 JSBridge 的 JS 文件加載完成后可以被調(diào)用蹲蒲,以獲取 bridge 對象進(jìn)行通信。
下面是 ready 方法的使用及其源碼:
$ljBridge.ready((bridge, webStatus) => {
// ...
})
// 可以定義 ready 方法和 web 狀態(tài)值
window.$ljBridge = {
ready: function(fn, context) {
callbacks.push(arguments.length > 1 ? {
fn: fn,
context: context
} : {
fn: fn
});
requestAnimationFrame(do_callback);
},
webStatus: webStatus
}
var callbacks = [];
// 執(zhí)行回調(diào)函數(shù)
function do_callback() {
if (bridgeInstance === null) return
var callback
while (callback = callbacks.shift()) {
var fn = callback.fn;
try {
if ('context' in callback) {
fn.call(callback.context, bridgeInstance, webStatus);
} else {
fn(bridgeInstance, webStatus);
}
} catch (e) {
if ('console' in window) {
console.error ? console.error(e) : console.log(e);
}
}
}
}
最后
最后總結(jié)一下:
- Android 有一個 WebView 控件侵贵,注意 4.4 版本前后的瀏覽器內(nèi)核差異届搁。
- IOS 有 UIWebView 和 WKWebView 兩個瀏覽器,兩者對于 JS 的通信方式是不同的窍育。
- JS 調(diào)用 Android 有 3 種方法
- JS 調(diào)用 IOS 有 2 種方法卡睦,對應(yīng) UIWebView 和 WKWebView
- Android 和 IOS 都可以往 JS 的 window 對象中注入數(shù)據(jù)對象。
- 鏈家 JSBridge 也是使用了以上的方案來實現(xiàn)漱抓。