<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          JSB 原理與實(shí)踐

          共 9478字,需瀏覽 19分鐘

           ·

          2021-08-05 21:10


           大廠技術(shù)  堅(jiān)持周更  精選好文

          什么是 JSB

          我們開發(fā)的 h5 頁面運(yùn)行在端上的 WebView 容器之中,很多業(yè)務(wù)場景下 h5 需要依賴端上提供的信息/能力,這時(shí)我們需要一個(gè)可以連接原生運(yùn)行環(huán)境和 JS 運(yùn)行環(huán)境的橋梁 這個(gè)橋梁就是 JSB,JSB 讓 Web 端和 Native 端得以實(shí)現(xiàn)雙向通信

          WebView 概述

          WebView 是移動端中的一個(gè)控件,它為 JS 運(yùn)行提供了一個(gè)沙箱環(huán)境。WebView 能夠加載指定的 url,攔截頁面發(fā)出的各種請求等各種頁面控制功能,JSB 的實(shí)現(xiàn)就依賴于 WebView 暴露的各種接口。由于歷史原因,安卓和 iOS 均有高低兩套版本的 WebView 內(nèi)核:

          平臺和版本WebView 內(nèi)核
          iOS 8+WKWebView
          iOS 2-8UIWebView
          Android 4.4+Chrome
          Android 4.4-Webkit

          PS: 下文中出現(xiàn)的高版本均代指 iOS 8+ 或 Android 4.4+,低版本則相反。

          JSB 原理

          要實(shí)現(xiàn)雙向通信自然要依次實(shí)現(xiàn) Native 向 Web 發(fā)送消息和 Web 向 Native 發(fā)送消息。

          Native 向 Web 發(fā)送消息

          Native 向 Web 發(fā)送消息基本原理上是在 WebView 容器中動態(tài)地執(zhí)行一段 JS 腳本,通常情況下是調(diào)用一個(gè)掛載在全局上下文的方法。Android 和 iOS 均提供了不同的接口來實(shí)現(xiàn)這一過程。

          方法

          • Android 高低版本存在兩種直接執(zhí)行 JS 字符串的方法:
          Android 版本API特點(diǎn)
          低版本WebView.loadUrl無法執(zhí)行回調(diào)
          高版本WebView.evaluateJavascript可以拿到 JS 執(zhí)行完畢的返回值
          • iOS 高低版本同樣存在兩種不同的實(shí)現(xiàn)方式:
          iOS 版本API特點(diǎn)
          低版本UIWebView.stringByEvaluatingJavaScriptFromString無法執(zhí)行回調(diào)
          高版本WKWebView.evaluateJavaScript可以拿到 JS 執(zhí)行完畢的返回值

          實(shí)踐

          下面我們通過一個(gè)小 Demo 來看一下在 iOS 端實(shí)現(xiàn) Native 向 Web 端發(fā)消息的實(shí)際效果:

          本文所有 Demo 均運(yùn)行在 iOS14.5 模擬器中,WebView 容器采用 WKWebView 內(nèi)核

          頁面上半部分的 UI 是由 HTML + CSS 渲染所得,是一個(gè)純靜態(tài)的 webpage,中間的輸入框和按鈕是 Native 原生控件,直接覆蓋在 WebView 容器之上。在 Native 按鈕上綁定了一個(gè)點(diǎn)擊事件:將文本框輸入的字符視為 JS 字符串并調(diào)用相關(guān) API 直接執(zhí)行

          可以看到當(dāng)我們在文本框中輸入下列字符并點(diǎn)擊按鈕后,h5 頁面中 id 為 test 的 p 標(biāo)簽內(nèi)容被修改了。

          document.querySelector('#test').innerHTML = 'I am from native';

          敏銳同學(xué)到這一步其實(shí)就已經(jīng)知道我們在日常使用 JSB 時(shí)客戶端是如何調(diào)用前端 JS 代碼了,我們在剛剛的靜態(tài) html 文件中添加幾行 JS 代碼:

          function evaluateByNative(params) {
              const p = document.createElement('p');
              p.innerText = params;
              document.body.appendChild(p);
              return 'Hello Bridge!';
          }

          在文本框中輸入 evaluateByNative(23333),來看一下調(diào)用的結(jié)果:

          可以看到 Native 端可以直接調(diào)用掛載在 window 上的全局方法并傳入相應(yīng)的函數(shù)執(zhí)行參數(shù)并且在函數(shù)執(zhí)行結(jié)束后 Native 端可以直接拿到執(zhí)行成功的返回值。

          Web 向 Native 發(fā)送消息

          Web 向 Native 發(fā)送消息本質(zhì)上就是某段 JS 代碼的執(zhí)行端上是可感知的,目前業(yè)界主流的實(shí)現(xiàn)方案有兩種,分別是攔截式注入式

          攔截式

          和瀏覽器類似 WebView 中發(fā)出的所有請求都是可以被 Native 容器感知到的(是不是想到了Gecko),因此攔截式具體指的是 Native 攔截 Web 發(fā)出的 URL 請求,雙方在此之前約定一個(gè) JSB 請求格式,如果該請求是 JSB 則進(jìn)行相應(yīng)的處理,若不是則直接轉(zhuǎn)發(fā)。

          Native 攔截請求的鉤子方法:

          平臺API
          AndroidshouldOverrideUrlLoading
          iOS 8+decidePolicyForNavigationAction
          iOS 8-shouldStartLoadWithRequest

          攔截式的基本流程如下

          上述流程存在幾個(gè)問題:

          1. 通過何種方式發(fā)出請求?

          Web 端發(fā)出請求的方式非常多樣,例如 <a/>iframe.srclocation.hrefajax 等,但 <a/> 需要用戶手動觸發(fā),location.href 可能會導(dǎo)致頁面跳轉(zhuǎn),安卓端攔截 ajax 的能力有所欠缺,因此絕大多數(shù)攔截式實(shí)現(xiàn)方案均采用iframe 來發(fā)送請求

          1. 如何規(guī)定 JSB 的請求格式?

          一個(gè)標(biāo)準(zhǔn)的 URL 由 <scheme>://<host>:<port><path> 組成,相信大家都有過從微信或手機(jī)瀏覽器點(diǎn)擊某個(gè)鏈接意外跳轉(zhuǎn)到其他 App 的經(jīng)歷,如果有仔細(xì)留意過這些鏈接的 URL 你會發(fā)現(xiàn)目前主流 App 都有其專屬的一個(gè) scheme 來作為該應(yīng)用的標(biāo)識,例如微信的 URL scheme 就是 weixin://JSB 的實(shí)現(xiàn)借鑒這一思路,定制業(yè)務(wù)自身專屬的一個(gè) URL scheme 來作為 JSB 請求的標(biāo)識,例如字節(jié)內(nèi)部實(shí)現(xiàn)攔截式 JSB 的 SDK 中就定義了 bytedance:// 這樣一個(gè) scheme。

          // Web 通過動態(tài)創(chuàng)建 iframe,將 src 設(shè)置為符合雙端規(guī)范的 url scheme
          const CUSTOM_PROTOCOL_SCHEME = 'prek'

          function web2Native(event{    
              const messagingIframe = document.createElement('iframe');
              messagingIframe.style.display = 'none';
              messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + event;
              document.documentElement.appendChild(messagingIframe);

              setTimeout(() => {
                  document.documentElement.removeChild(messagingIframe);
              }, 200)
          }

          攔截式在雙端都具有非常好的向下兼容性,曾經(jīng)是最主流的 JSB 實(shí)現(xiàn)方案,但目前在高版本的系統(tǒng)中已經(jīng)逐漸被淘汰,理由是它有如下幾個(gè)劣勢:

          • 連續(xù)發(fā)送時(shí)可能會造成消息丟失(可以使用消息隊(duì)列解決該問題)
          • URL  字符串長度有限制
          • 性能一般,URL request 創(chuàng)建請求有一定的耗時(shí)(Android 端 200-400ms)

          實(shí)踐案例

          同樣用一個(gè)簡單的 Demo2 來看一下如何使用攔截式實(shí)現(xiàn) Web 向 Native 發(fā)送消息,這里實(shí)現(xiàn)了在 Web 端喚起 Native 的相冊。

          遵循上述實(shí)現(xiàn)方式,Web 發(fā)送消息的代碼如下:

          const CUSTOM_PROTOCOL_SCHEME = 'prek' // 自定義 url scheme

          function web2Native(event_name{
              const messagingIframe = document.createElement('iframe')
              messagingIframe.style.display = 'none'
              messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + event_name
              document.documentElement.appendChild(messagingIframe)
              setTimeout(() => {
                  document.documentElement.removeChild(messagingIframe)
              }, 0)
          }

          const btn = document.querySelector('#btn')

          btn.onclick = () => {
              web2Native('openPhotoAlbum')
          }

          Native 側(cè)通過 decidePolicyForNavigationAction 這一 delegate 實(shí)現(xiàn)請求攔截,解析 URL 參數(shù),若 URL scheme 是 prek 則認(rèn)為該請求是一個(gè)來自 Web 的 JSB 調(diào)用:

          - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
            NSURL *url = navigationAction.request.URL;
            NSLog(@"攔截到 Web 發(fā)出的請求 = %@", url);

            if ([self isSchemeMatchPrek:url]) {
              NSString* host = url.host.lowercaseString;
              if ([host isEqualToString: @"openphotoalbum"]) {
                [self openCameraForWeb]; // 打開相冊
                NSLog(@"打開相冊");
              }
              decisionHandler(WKNavigationActionPolicyCancel);
              return;
            } else {
              decisionHandler(WKNavigationActionPolicyAllow);
            }
          }

          為了更清晰地看到 Native 攔截的結(jié)果,在上述代理方法中打個(gè)斷點(diǎn):

          繼續(xù)執(zhí)行,Congratulation!模擬器的相冊被打開了!

          注入式

          注入式的原理是通過 WebView 提供的接口向 JS 全局上下文對象(window)中注入對象或者方法,當(dāng) JS 調(diào)用時(shí),可直接執(zhí)行相應(yīng)的 Native 代碼邏輯,從而達(dá)到 Web 調(diào)用 Native 的目的。

          Native 注入 API 的相關(guān)方法:

          平臺API特點(diǎn)
          AndroidaddJavascriptInterface4.2 版本以下有安全風(fēng)險(xiǎn)
          iOS 8+WKScriptMessageHandler
          iOS 7+JavaSciptCore
          JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

          context[@"getAppInfo"] = ^(msg) {
              return @"ggl_2693";
          };
          window.getAppInfo(); // 'ggl_2693'

          這種方法簡單而直觀,并且不存在參數(shù)長度限制和性能瓶頸等問題,目前主流的 JSB SDK 都將注入式方案作為優(yōu)先使用的對象。注入式的實(shí)現(xiàn)非常簡單,這里不做案例展示。

          兩種方案對比

          為了更清晰地表達(dá)這兩種方式的區(qū)別,這里貼一個(gè)對比表格:

          方案兼容性性能參數(shù)長度限制
          攔截式無兼容性問題較差,安卓端尤為明顯有限制
          注入式安卓4.2+ 和 iOS 7+以上可用較好

          如何執(zhí)行回調(diào)

          通過上述介紹我們已經(jīng)知道如何實(shí)現(xiàn)雙端互相發(fā)送消息,但上述兩個(gè)通信過程缺少了“回應(yīng)”這一動作,原因就是上述步驟缺少了回調(diào)函數(shù)的執(zhí)行。以攔截式為例,常見的一個(gè) JSB 調(diào)用是 Web 獲取當(dāng)前 App 信息, Native 攔截到 bytedance://getAppInfo這樣一個(gè)請求后將獲取當(dāng)前 App 信息,那獲取完成后如何讓 Web 端拿到該信息呢?

          一個(gè)最簡單的做法是類比 JSONP 的實(shí)現(xiàn),我們可以在請求的 URL 上拼接回調(diào)方法的事件名,將該事件掛載在全局 window 上,由于 Native 端可以輕松執(zhí)行 JS 代碼,因此在完成端邏輯后直接執(zhí)行該事件名對應(yīng)的回調(diào)方法即可。以 getAppInfo 為例:

          // Web
          const uniqueID = 1 // 為防止事件名沖突,給每個(gè) callback 設(shè)置一個(gè)唯一標(biāo)識
          function webCallNative(event, params, callback{
              if (typeof callback === 'Function') {
                  const callbackID = 'jsb_cb_' + (uniqueID++) + '_' + Date.now();
                  window[callbackID] = callback
              }
              const params = {callback: callbackID}
              // 構(gòu)造 url scheme
              const src = 'bytedance://getAppInfo?' + JSON.stringify(params)
              ...
          }

          // Native
          1. 解析傳入的參數(shù) 'getAppInfo' 得知 Web 希望獲取 AppInfo
          2. 執(zhí)行端邏輯獲取 AppInfo
          3. 執(zhí)行參數(shù)中掛載在全局的 callback 方法,AppInfo 作為回調(diào)方法的參數(shù)

          因此只要把相應(yīng)的回調(diào)方法掛載在全局對象上,Native 即可把每次調(diào)用后的響應(yīng)通過動態(tài)執(zhí)行 JS 方法的形式傳遞到 Web 端,這樣一來整個(gè)通信過程就實(shí)現(xiàn)了閉環(huán)。

          串聯(lián)雙端通信的過程

          現(xiàn)在我們已經(jīng)知道如何實(shí)現(xiàn)兩端互相發(fā)送消息以及執(zhí)行回調(diào)了,但看起來并不好用:首先調(diào)用 JSB 時(shí)需要在方法名后拼接參數(shù)和對應(yīng)的回調(diào)函數(shù),其次回調(diào)函數(shù)還需要一個(gè)一個(gè)地掛載在全局對象上。

          我們期望的使用方式其實(shí)是這樣:

          // Web
          web.call('event1', {param1}, (res) => {...}) // 觸發(fā) native event1 執(zhí)行
          web.on('event2', (res) => {...})

          // Native 
          // 這里用 js 代替,理解大致意思即可
          native.call('event2', {param2}, (res) => {...}) // 觸發(fā) web event2 執(zhí)行
          native.on('event1', (res) => {...})

          這里的 JSB 就像是一個(gè)跨越兩端的 EventEmitter,因此需要 Web 和 Native 遵循同一套調(diào)度機(jī)制。

          上圖給出了 Web 調(diào)用 -> Native 監(jiān)聽的執(zhí)行過程,同理 Native 調(diào)用 -> Web 監(jiān)聽也是同樣的邏輯,只是把兩邊的實(shí)現(xiàn)調(diào)換一種語言,這里不贅述了。

          貼一張其他同學(xué)畫的時(shí)序圖,幫助理解整個(gè)通信過程

          Demo3 基于開源的 WebViewJavascriptBridge 演示了一套完整的通訊流程是怎樣進(jìn)行的,有興趣的同學(xué)請自行戳源碼地址 JSB_Demo 自行體驗(yàn)。(需要使用 Xcode 打開,會涉及一些客戶端的知識,請配合文檔和 Google 使用)。

          一點(diǎn)感受

          筆者所在業(yè)務(wù)使用的 bridge 即司內(nèi)目前最新的 SDK,沒有歷史包袱、使用體驗(yàn)也非常良好。得益于客戶端遵循該 SDK 配套的實(shí)現(xiàn)機(jī)制,即使完全不了解 JSB 原理的同學(xué)在與端上對接 bridge 時(shí)也幾乎沒有遇到障礙。倘若拋開公司完備的基礎(chǔ)建設(shè),想實(shí)現(xiàn)一個(gè)通用且好用的 JSB 并非易事,因此了解其中的門道還是非常有益的。(巨人的肩膀站久了,確實(shí)巴適得很??)

          參考文獻(xiàn)


          深入淺出 JSBridge[4]

          JSB 實(shí)戰(zhàn)[5]

          [1]

          JSONP: https://en.wikipedia.org/wiki/JSONP

          [2]

          WebViewJavascriptBridge: https://github.com/marcuswestin/WebViewJavascriptBridge

          [3]

          JSB_Demo: https://code.byted.org/caocheng.viccc/JSB_Demo

          [4]

          深入淺出 JSBridge: https://juejin.cn/post/6936814903021797389#heading-8

          [5]

          JSB 實(shí)戰(zhàn): https://juejin.cn/post/6844903702721986568

          ?? 謝謝支持

          以上便是本次分享的全部內(nèi)容,希望對你有所幫助^_^

          喜歡的話別忘了 分享、點(diǎn)贊、收藏 三連哦~。

          歡迎關(guān)注公眾號 ELab團(tuán)隊(duì) 收貨大廠一手好文章~

          我們來自字節(jié)跳動,是旗下大力教育前端部門,負(fù)責(zé)字節(jié)跳動教育全線產(chǎn)品前端開發(fā)工作。

          我們圍繞產(chǎn)品品質(zhì)提升、開發(fā)效率、創(chuàng)意與前沿技術(shù)等方向沉淀與傳播專業(yè)知識及案例,為業(yè)界貢獻(xiàn)經(jīng)驗(yàn)價(jià)值。包括但不限于性能監(jiān)控、組件庫、多端技術(shù)、Serverless、可視化搭建、音視頻、人工智能、產(chǎn)品設(shè)計(jì)與營銷等內(nèi)容。

          歡迎感興趣的同學(xué)在評論區(qū)或使用內(nèi)推碼內(nèi)推到作者部門拍磚哦 ??

          字節(jié)跳動校/社招投遞鏈接: https://job.toutiao.com/

          內(nèi)推碼:7EZKXME

          瀏覽 33
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  激情电影午夜色导航 | 一黄大毛片 | 国产精品免费人成网站酒店 | 正在播放91大神 | 亚洲第三十七页 |