小白必看,JSBridge 初探

🙏向這次肺炎疫情中逝世的同胞表示哀悼。javascript

本文首發於政採雲前端團隊博客: 小白必看,JSBridge 初探

JSBridge 的起源

近些年,移動端普及化愈來愈高,開發過程當中選用 Native 仍是 H5 一直是熱門話題。Native 和 H5 都有着各自的優缺點,爲了知足業務的須要,公司實際項目的開發過程當中每每會融合二者進行 Hybrid 開發。Native 和 H5 分處兩地,看起來沒法聯繫,那麼如何才能讓雙方協同實現功能呢?前端

這時咱們想到了 Codova ,Codova 提供了一組與設備相關的 API ,是早期 JS 調用原生代碼來實現原生功能的經常使用方案。不過 JSBridge 真正在國內普遍應用是因爲移動互聯網的盛行。java

JSBridge 是一種 JS 實現的 Bridge,鏈接着橋兩端的 Native 和 H5。它在 APP 內方便地讓 Native 調用 JS,JS 調用 Native ,是雙向通訊的通道。JSBridge 主要提供了 JS 調用 Native 代碼的能力,實現原生功能如查看本地相冊、打開攝像頭、指紋支付等。web

H5 與 Native 對比objective-c

name H5 Native
穩定性 調用系統瀏覽器內核,穩定性較差 使用原生內核,更加穩定
靈活性 版本迭代快,上線靈活 迭代慢,須要應用商店審覈,上線速度受限制
受網速 影響 較大 較小
流暢度 有時加載慢,給用戶「卡頓」的感受 加載速度快,更加流暢
用戶體驗 功能受瀏覽器限制,體驗有時較差 原生系統 api 豐富,能實現的功能較多,體驗較好
可移植性 兼容跨平臺跨系統,如 PC 與 移動端,iOS 與 Android 可移植性較低,對於 iOS 和 Android 須要維護兩套代碼

JSBridge 的雙向通訊原理

  • JS 調用 Native

JS 調用 Native 的實現方式較多,主要有攔截 URL Scheme 、重寫 prompt 、注入 API 等方法。npm

攔截 URL Scheme

Android 和 iOS 均可以經過攔截 URL Scheme 並解析 Scheme 來決定是否進行對應的 Native 代碼邏輯處理。json

Android 的話,Webview 提供了 shouldOverrideUrlLoading 方法來提供給 Native 攔截 H5 發送的 URL Scheme 請求。代碼以下:swift

public class CustomWebViewClient extends WebViewClient {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
      ......
      // 場景一: 攔截請求、接收 scheme
        if (url.equals("xxx")) {

            // handle
            ...
            // callback
            view.loadUrl("javascript:setAllContent(" + json + ");")
            return true;
        }
        return super.shouldOverrideUrlLoading(url);
     }
}

iOS 的 WKWebview 能夠根據攔截到的 URL Scheme 和對應的參數執行相關的操做。代碼以下:api

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    if ([navigationAction.request.URL.absoluteString hasPrefix:@"xxx"]) {
        [[UIApplication sharedApplication] openURL:navigationAction.request.URL];
    }
    decisionHandler(WKNavigationActionPolicyAllow);
}

這種方法的優勢是不存在漏洞問題、使用靈活,能夠實現 H5 和 Native 頁面的無縫切換。例如在某一頁面須要快速上線的狀況下,先開發出 H5 頁面。某一連接填寫的是 H5 連接,在對應的 Native 頁面開發完成前先跳轉至 H5 頁面,待 Native 頁面開發完後再進行攔截,跳轉至 Native 頁面,此時 H5 的連接無需進行修改。可是使用 iframe.src 來發送 URL Scheme 須要對 URL 的長度做控制,使用複雜,速度較慢。瀏覽器

重寫 prompt 等原生 JS 方法

Android 4.2 以前注入對象的接口是 addJavascriptInterface ,可是因爲安全緣由慢慢不被使用。通常會經過修改瀏覽器的部分 Window 對象的方法來完成操做。主要是攔截 alert、confirm、prompt、console.log 四個方法,分別被 Webview 的 onJsAlert、onJsConfirm、onConsoleMessage、onJsPrompt 監聽。其中 onJsPrompt 監聽的代碼以下:

public boolean onJsPrompt(WebView view, String origin, String message, String         defaultValue, final JsPromptResult result) {
  String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message,             defaultValue);
  xxx;
  return true;
}

iOS 因爲安全機制,WKWebView 對 alert、confirm、prompt 等方法作了攔截,若是經過此方式進行 Native 與 JS 交互,須要實現 WKWebView 的三個 WKUIDelegate 代理方法。代碼示例以下:

-(void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{

  UIAlertController *alertController = [UIAlertController                    alertControllerWithTitle:nil message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];

  [alertController addAction:([UIAlertAction actionWithTitle:@"確認" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {

      completionHandler();

  }])];

  [self presentViewController:alertController animated:YES completion:nil];

}

使用該方式時,能夠與 Android 和 iOS 約定好使用傳參的格式,這樣 H5 能夠無需識別客戶端,傳入不一樣參數直接調用 Native 便可。剩下的交給客戶端本身去攔截相同的方法,識別相同的參數,進行本身的處理邏輯便可實現多端表現一致。如:

alert("肯定xxx?", "取消", "肯定", callback());

另外,若是能與 Native 肯定好方法名、傳參等調用的協議規範,這樣其它格式的 prompt 等方法是不會被識別的,能起到隔離的做用。

##### 注入 API

基於 Webview 提供的能力,咱們能夠向 Window 上注入對象或方法。JS 經過這個對象或方法進行調用時,執行對應的邏輯操做,能夠直接調用 Native 的方法。使用該方式時,JS 須要等到 Native 執行完對應的邏輯後才能進行回調裏面的操做。

Android 的 Webview 提供了 addJavascriptInterface 方法,支持 Android 4.2 及以上系統。

gpcWebView.addJavascriptInterface(new JavaScriptInterface(), 'nativeApiBridge'); 
public class JavaScriptInterface {
    Context mContext;

  JavaScriptInterface(Context c) {
    mContext = c;
  }

  public void share(String webMessage){            
    // Native 邏輯
  }
}

JS 調用示例:

window.NativeApi.share(xxx);

iOS 的 UIWebview 提供了 JavaScriptScore 方法,支持 iOS 7.0 及以上系統。WKWebview 提供了 window.webkit.messageHandlers 方法,支持 iOS 8.0 及以上系統。UIWebview 在幾年前經常使用,目前已不常見。如下爲建立 WKWebViewConfiguration 和 建立 WKWebView 示例:

WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
WKPreferences *preferences = [WKPreferences new];
preferences.javaScriptCanOpenWindowsAutomatically = YES;
preferences.minimumFontSize = 40.0;
configuration.preferences = preferences;
    

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"share"];
      [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"pickImage"];
}
- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self.webView.configuration.userContentController     removeScriptMessageHandlerForName:@"share"];
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"pickImage"];
}

JS 調用示例:

window.webkit.messageHandlers.share.postMessage(xxx);
  • Native 調用 JS

Native 調用 JS 比較簡單,只要 H5 將 JS 方法暴露在 Window 上給 Native 調用便可。

Android 中主要有兩種方式實現。在 4.4 之前,經過 loadUrl 方法,執行一段 JS 代碼來實現。在 4.4 之後,可使用 evaluateJavascript 方法實現。loadUrl 方法使用起來方便簡潔,可是效率低沒法得到返回結果且調用的時候會刷新 WebView。evaluateJavascript 方法效率高獲取返回值方便,調用時候不刷新WebView,可是隻支持 Android 4.4+。相關代碼以下:

webView.loadUrl("javascript:" + javaScriptString);
webView.evaluateJavascript(javaScriptString, new ValueCallback<String>() {
  @Override
  public void onReceiveValue(String value){
    xxx
  }
});

iOS 在 WKWebview 中能夠經過 evaluateJavaScript:javaScriptString 來實現,支持 iOS 8.0 及以上系統。

// swift
func evaluateJavaScript(_ javaScriptString: String, 
    completionHandler: ((Any?, Error?) -> Void)? = nil)
// javaScriptString 須要調用的 JS 代碼
// completionHandler 執行後的回調
// objective-c
[jsContext evaluateJavaScript:@"ZcyJsBridge(ev, data)"]

JSBridge 的使用

  • 如何引用

    • 由 H5 引用

      在我司移動端初期版本時採用的是該方式,採用本地引入 npm 包的方式進行調用。這種方式能夠肯定 JSBridge 是存在的,可直接調用 Native 方法。可是若是後期 Bridge 的實現方式改變,雙方須要作更多的兼容,維護成本高

    • 由 Native 注入

      這是當前我司移動端選用的方式。在考慮到後期業務須要的狀況下,進行了從新設計,選用 Native 注入的方式來引用 JSBridge。這樣有利於保持 API 與 Native 的一致性,可是缺點是在 Native 注入的方法和時機都受限,JS 調用 Native 以前須要先判斷 JSBridge 是否注入成功

  • 使用規範

H5 調用 Native 方法的僞代碼實例,如:

params = {
  api_version: "xxx",    // API 版本
  title: "xxx",    // 標題
  filename: "xxx",    // 文件名稱
  image: "xxx",    // 圖片連接
  url: "xxx",    // 網址連接
  success: function (res) {
    xxx;    // 調用成功後執行
  },
  fail: function (err) {
    if (err.code == '-2') {
      fail && fail(err);    //    調用了當前客戶端中不存在的 API 版本
    } else {
      const msg = err.msg;    //異常信息
      Toast.fail(msg);
    }
  }
};
window.NativeApi.share(params);

如下簡要列出通用方法的抽象,目前基本遵循如下規範進行雙端通訊。

window.NativeApi.xxx({
    api_version:'',
    name: "xxx",
    path: "xxx",
    id:    "xxx",
    success: function (res) {
      console.log(res);
    },
    fail: function (err) {
      console.log(err);
    }
});

因爲初期版本選擇了由 H5 本地引用 JSBridge,後期採用 Native 注入的方式。現有的 H5 須要對各類狀況作兼容,邏輯抽象以下:

reqNativeBridge(vm, fn) {
  if (!isApp()) {
    // 若是不在 APP 內進行調用
    vm.$dialog.alert({
      message: "此功能須要訪問 APP 才能使用",
    });
  } else {
    if (!window.NativeApi) {
      // 針對初期版本
      vm.$dialog.alert({
        message: "請更新到最新 APP 使用該功能",
      });
    } else {
      // 此處只針對「調用了當前客戶端中不存在的 API 版本」的報錯進行處理
      // 其他種類的錯誤信息交由具體的業務去處理
      fn && fn((err) => {
        vm.$dialog.alert({
          message: "請更新到最新 APP 使用該功能",
        });
      });
    }
  }
}

總結

上述內容簡要介紹了 JSBridge 的部分原理,但願對從未了解過 JSBridge 的同窗能有所幫助。若是須要更深刻的瞭解 JSBridge 的原理和實現,如 JSBridge 接口調用的封裝實現,JS 調用 Native 時的回調的惟一性等。你們能夠去查閱更多資料,參考更詳細的相關文檔或他人的整理成文的沉澱。

招賢納士

政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 50 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。

若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com

相關文章
相關標籤/搜索