JSBridge 學(xué)習(xí)筆記

在移動(dòng)互聯(lián)網(wǎng)的時(shí)代,開(kāi)發(fā)移動(dòng)端的前端頁(yè)面是前端同學(xué)一項(xiàng)必不可少的技能了。而涉及到與原生移動(dòng)端的交互,除了一些 WEEX、React Native 這種技術(shù)外,最常用也是最快捷的方式就是 JSBridge 了。所以,作為前端非常有必要了解一下 JSBridge 的運(yùn)作原理。

移動(dòng)端的瀏覽器控件

當(dāng)下主流的移動(dòng)端操作系統(tǒng)無(wú)疑是 Android 和 IOS。而這兩者分別都提供了各自承載網(wǎng)頁(yè)的控件 WebView(IOS 里面是 UIWebView 和 WKWebView)。下面簡(jiǎn)單介紹下瀏覽器控件。

Android WebView

Android 的 WebView 能夠像其他的瀏覽器 APP 一樣顯示網(wǎng)頁(yè)并對(duì)頁(yè)面做一些基礎(chǔ)的操作。由于歷史原因,Android WebView 采用了兩種不同的內(nèi)核。

在 Android 4.4 以下(不包含 4.4)系統(tǒng) WebView 底層實(shí)現(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 以下機(jī)型需要做好樣式和JS的適配兼容工作。

IOS 的 UIWebView 和 WKWebView

在 IOS 中也是由于歷史原因,出現(xiàn)了兩個(gè) WebView 控件。 UIWebView 是自 IOS2 就有的,而 WKWebView 從 IOS8 才有。WKWebView 使用的是 Safari 瀏覽器內(nèi)核,相比于笨重的 UIWebView 性能更佳。

如果需要支持 IOS8 之前的版本,只好去兼容 UIWebView ,而在 IOS8+ 的版本中,就有 UIWebView 和 WKWebView 兩種選擇了(具體看 native 端的實(shí)現(xiàn)選擇)。

簡(jiǎn)單介紹了下兩個(gè)操作系統(tǒng)的瀏覽器控件的情況,更佳具體的移動(dòng)前端問(wèn)題可以參考我之前整理的2018 年最新的移動(dòng)前端資料整理(不斷更新)
一文。

JSBridge 原理及實(shí)現(xiàn)

具體的原理在有贊前端的# H5與Native交互之JSBridge技術(shù) 一文中講的非常清楚,不多贅述。簡(jiǎn)單說(shuō)下:

IOS 調(diào)用 JavaScript 方法

在 IOS 中使用 stringByEvaluatingJavaScriptFromString 方法直接調(diào)用掛載在前端 window 下的方法,并獲取方法返回的數(shù)據(jù)。

webview.stringByEvaluatingJavaScriptFromString("Math.random()")

JavaScript 調(diào)用 IOS 方法

有兩種方法可以實(shí)現(xiàn) JavaScript 調(diào)用 IOS 方法這個(gè)行為:

1. 在 JavaScript 中使用創(chuàng)建一個(gè) iframe 請(qǐng)求 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)頁(yè) URL 請(qǐng)求時(shí)將 scheme 碼的 URL 攔截下來(lái)去做 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. 對(duì)于 WKWebView 有另外的解決方案:iOS下OC與JS的交互(WKWebview-MessageHandler實(shí)現(xiàn))

簡(jiǎn)單說(shuō)下實(shí)現(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 對(duì)象上的方法。

webView.loadUrl("javascript:JSBridge.trigger('webviewReady')");

JavaScript 調(diào)用 Android 方法

有 3 種方法:

  1. 通過(guò) shouldOverrideUrlLoading 方法處理 URL 的 scheme 碼,做法類似 IOS 的通信方式。
  2. Android 中有一個(gè) JSInterface 類可以直接注入原生 JS 代碼。
class JSInterface {
    @JavascriptInterface //注意這個(gè)代碼一定要加上
    public String getUserData() {
        return "UserData";
    }
}
webView.addJavascriptInterface(new JSInterface(), "AndroidJS");

上面的代碼就是在頁(yè)面的 window 對(duì)象里注入了 AndroidJS 對(duì)象。在js里可以直接調(diào)用

alert(AndroidJS.getUserData()) //UserDate
  1. 使用 prompt,console.log,alert 方式。

LJBridge 學(xué)習(xí)

m-base 源碼簡(jiǎn)析

說(shuō)了這么多,作為前端最關(guān)注必然不是 Native 端如何實(shí)現(xiàn),而是 JavaScript 端如何與 Native 端通信啦。下面來(lái)看看鏈家的 JSBridge 源碼方法:

function tryCatch(callback) {
  // try…catch 執(zhí)行 Native 方法
}
function setBridgeInstance(bridge) {
  // 設(shè)置 JSBridge 實(shí)例
}
function setBridgeInstanceForWK(bridge) {
  // 設(shè)置 JSBridge 實(shí)例(適配 WKWebView)
}
function do_callback() {
  // 執(zhí)行 ready 方法中的回調(diào)函數(shù)
}
function bindSysSchema(evt){
  // 綁定系統(tǒng) scheme (打電話和發(fā)短信)
}
// 在 window 對(duì)象中定義 ljBridge 來(lái)使用 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 如何通信

基于一開(kāi)始講過(guò)的 JSBridge 通信原理,我們來(lái)理解下 m-base 源碼中的通信。

所有在 H5與APP通信文檔(JS bridge) 文檔中接口方法都可以在 setBridgeInstancesetBridgeInstanceForWK 方法中看到。

JS 獲取 Native 端數(shù)據(jù)

在這些方法中,像 getNetwork、getDeviceId 這類獲取 Native 信息的方法都是直接獲取注入的 window.HybridBridgeLJ 對(duì)象中的數(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 端方法

setRightButtonsetCity 這些 JavaScript 調(diào)用 Native 方法的行為,有兩種不同的方式:

第一種是直接調(diào)用注入的 window.HybridBridgeLJ 對(duì)象中的方法,原理上面提到過(guò)的 JavaScript 調(diào)用 Android 方法的第 2 條。

bridge.setShareConfigWithString(paramString);

第二種是針對(duì) 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 對(duì)象進(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 有一個(gè) WebView 控件,注意 4.4 版本前后的瀏覽器內(nèi)核差異。
  • IOS 有 UIWebView 和 WKWebView 兩個(gè)瀏覽器,兩者對(duì)于 JS 的通信方式是不同的。
  • JS 調(diào)用 Android 有 3 種方法
  • JS 調(diào)用 IOS 有 2 種方法,對(duì)應(yīng) UIWebView 和 WKWebView
  • Android 和 IOS 都可以往 JS 的 window 對(duì)象中注入數(shù)據(jù)對(duì)象。
  • 鏈家 JSBridge 也是使用了以上的方案來(lái)實(shí)現(xiàn)。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容