JSBridge實戰

前言

H5 VS Native 一直是前端技術界爭執不下的話題。react、vue等技術棧引領着純H5開發,rn、week則倡導原生體驗。但在項目實戰中,常常會選擇一箇中立的方案:混合開發。大衆稱呼:Hybrid。javascript

本人目前從事新聞類產品研發,對於你們來說,就是熟知的現在日頭條、百度新聞、網易新聞等。在產品設計初期,考慮到一些實現難易程度問題(如新聞詳情頁,圖文混排,NA實現起來不如H5這樣自如),一些部分選擇了Hybrid方式開發,本篇就把開發過程當中的一些想法分享一下,以供你們參考。html

JSBridge解決的問題

混合開發,最重要的問題是:H5和Native的雙向通訊。 但現實中JS和NA的交互方法很是有限,下面會詳細說明。開發中如只是單純的方法調用,既沒法確保調用成功率,也沒法確保代碼足夠簡潔。因而就有了JSBridge。JSBridge,是一種JS實現的Bridge,是一種思路,能夠有不一樣理解,不一樣的代碼實現。主旨思想是在H5和NA之間搭建一個橋樑(Bridge),給兩端留好更友好、更合理的接口。前端

H5和NA的雙向通訊通用方法

H5通訊方式和兼容性以下表所示。指的是藉助Native的webview加載H5頁面,H5和NA之間經過API、URL攔截、全局調用等形式,實現消息通訊。站在大廠的角度考慮,在實戰的時候,會選擇更兼容的方式。vue

H5調用NA方法梳理

平臺 方法 備註
Android shouldOverrideUrlLoading scheme攔截方法
Android addJavascriptInterface API
Android onJsAlert()、onJsConfirm()、onJsPrompt()
IOS 攔截URL
IOS(UIwebview) JavaScriptCore API方法,IOS7+ 支持
IOS(WKwebview) window.webkit.messageHandlers APi方法,IOS8+支持

NA調用H5方法梳理

平臺 方法 備註
Android loadurl()
Android evaluateJavascript() Android 4.4 +
IOS(UIwebview) stringByEvaluatingJavaScriptFromString
IOS(UIwebview) JavaScriptCore IOS7.0+
IOS(Wkwebview) evaluateJavaScript:javaScriptString iOS8.0+

經過上面兩端調用方法梳理表,不難分析出,URL攔截 & 執行JS是 安卓和IOS比較通用且兼容性較好的方案。咱們混合開發的基礎正是基於這種方法來實現的。java

常規混合開發思路

H5和NA通訊方面,最簡單直接的思路是:NA攔截H5的URL獲取消息(通常是經過修改iframe的src來實現 ①),通過業務處理,NA執行JS(在H5側提早註冊好的全局方法③)回調通知H5(以下圖)。node

H5代碼實現以下:react

<html>
...
<body>
    <div class="content">XXXXX</div>
</body>
  
<script>
    // ① 註冊全局函數,以便端調用
    window.setAllContent = function(){
         
    }
 
    // ② 通用方法函數
    var sendschema = function(action,param){
        let tempnode = document.createElement('iframe');
        tempnode.src = "bdnews://"+action+param;
    }
 
    // ③ H5邏輯開始 運行函數
    document.addEventListener("DOMContentLoaded",function(){
        sendschema('load_finish');
    },false);
</script>
  
...
</html>
複製代碼

Android原理大體以下:git

webView.setWebViewClient(new WebViewClient() {
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
         
        // 場景一: 攔截請求、接收schema
        if (url.equals("load_url")) {
           
            // 處理邏輯
            dosomething
  
            // 回掉
            view.loadUrl("javascript:setAllContent(" + json + ");")
        }
  
  
        // 場景二:端本身調用H5,沒有請求發起
        clickbutton(){
            view.loadUrl("javascript:setAllContent(" + json + ");")
        }
    }
});

複製代碼

IOS大概邏輯以下:github

// 初始化webview
UIWebView * view = [[UIWebView alloc]initWithFrame:self.view.frame];
[view loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.xx.com"]]];
[self.view addSubview:view];&nbsp;
&nbsp;
/*
webView協議中的方法
shouldStartLoadWithRequest //準備加載內容時調用的方法,經過返回值來進行是否加載的設置
webViewDidStartLoad //開始加載時調用的方法
webViewDidFinishLoad //結束加載時調用的方法
didFailLoadWithError //加載失敗時調用的方法
*/
&nbsp;
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    if ([urlString hasPrefix:@"scheme://hybrid?info="]) {
        if([name isEqualToString:@"load_finish"]){
            // [self.webView setContent];
            [self.webView stringByEvaluatingJavaScriptFromString:strFormat];
        }
    }
}
 
- clickbutton(){
    [self.webView setContent];
}
複製代碼

但這樣開發存在一些痛點:web

1)回調函數不明確。能夠說目前沒有回調函數的機制,這致使一些依賴於回調函數的分析及判斷沒法正常使用,如:功能調用方、調用是否成功、調用失敗異常處理等這些CASE;

2)對應關係不明確。有一些調用看起來像是回調,但沒有把他們放到一塊兒,致使代碼散亂,難以維護。如上面demo:sendschema('load_finish') 和 setAllContent 原本含義是 告訴NA頁面準備好了,NA收到後,向頁面塞數據。原本緊密相關的一對功能,拆分開看不出有什麼聯繫;

3)全局函數冗雜。理想中若是調用和回調成對出現,DEMO中註冊及維護全局函數的工做就會減小不少。提高頁面可讀性和維護成本。如 load_finish 和 setAllContent,只保留 load_finish 便可;

4)端內代碼冗雜。端內註冊了與H5約定的調用方法,很顯然也須要維護一套代碼標識何時調用。

以上開發中遇到的問題,也許剛開始功能很少的時候還察覺不出問題,可是隨着功能增長,後期維護成本很大。

JSB方案設計

在H5和NA之間增長一箇中間層,這層封裝了H5和NA通訊的交互方式。H5和NA互不關心對方的樣子,經過中間層暴露的方法進行功能調用便可。

JSB交互模型

H5跟NA交互,從H5角度來看大體可分爲兩大類:有去無回&有去有回、無去有回。

第一類交互模型

請求邏輯:有去無回、有去有回。這裏有兩種實現方案(初步思路稿以下):

① 函數名關聯

let BDAPPnode = {
   callbacks: {},
   // 調用函數註冊
   invoke(action, params, successfnname, successfn) {
       this.callbacks[successfnname] = {
           success: successfn
       };
       sendschema(action, params);
   },
 
   // NA調用
   callbackSuccess(callbackname, params) {
       try {
           BDAPPnode.callbackFromNative(callbackname, params, true);
       } catch (e) {
           console.log('Error in error callback: ' + callbackname + ' = ' + e);
       }
   },
   callbackFromNative(callbackname, params, isSuccess) {
       let callback = this.callbacks[callbackname];
       if (callback) {
           if (isSuccess) {
               callback.success && callback.success(params);
           }
       };
   }
};
複製代碼

② ID 關聯

let BDAPPnode = {
   callbackId: Math.floor(Math.random() * 2000000000),
   callbacks: {},
   invoke(action, params, onSuccess, onFail) {
       this.callbackId++;
       this.callbacks[self.callbackId] = {
           success: onSuccess,
           fail: onFail
       };
       sendschema(action, params, this.callbackId);
   },
   callbackSuccess(callbackId, params) {
       try {
           BDAPPnode.callbackFromNative(callbackId, params, true);
       } catch (e) {
           console.log('Error in error callback: ' + callbackId + ' = ' + e);
       }
   },
   callbackError(callbackId, params) {
       try {
           BDAPPnode.callbackFromNative(callbackId, params, false);
       } catch (e) {
           console.log('Error in error callback: ' + callbackId + ' = ' + e);
       }
   },
   callbackFromNative(callbackId, params, isSuccess) {
       let callback = this.callbacks[callbackId];
       if (callback) {
           if (isSuccess) {
               callback.success && callback.success(callbackId, params);
           } else {
               callback.fail && callback.fail(callbackId, params);
           }
           delete BDAPPnode.callbacks[callbackId];
       };
   }
};
複製代碼

在發出請求的時候,註冊回調方法。這麼作有兩個目的:

  • 無需提早註冊全部全局回掉函數,減小沒必要要的初始化,進而減小白屏時間;

  • 不用額外起回掉函數的名稱,發起請求的時候傳入一個隨機ID,同時註冊此ID的回掉函數。NA經過統一封裝好的回掉函數調用,回調ID和參數,進而達到執行回調邏輯。

具體選用那個,還得根據具體狀況具體分析看。

第二類交互模型看

請求邏輯:無去有回,沒有發出請求,NA主動調用。此類還需註冊全局變量,等待NA調用。跟非JSBridge的實現是一個道理

window.fn1 = () =>{
   // do fn1
}
  
window.fn2 = () =>{
   // do fn2
}

複製代碼

方案選擇

實戰過程當中深入體會到,混合開發能夠分爲兩大類:NA服務H5,H5服務NA

前者H5爲主,大多數交互是H5發起NA請求,等待NA回調,可稱之爲:『一對一請求』,如:H5請求獲取地理位置,NA作完後返回N\S座標;

後者主要是爲了解決NA成本實現高的問題,多爲NA主動調用H5提早註冊好的方法,可稱之爲:『單獨請求』,確保功能順利實現。

在項目實戰過程當中,常常會有這種狀況:回調函數既是一對一請求,也是單獨調用,如:評論功能,能夠頁面點擊彈出NA輸入框發送,也能夠點擊底BAR上NA實現的按鈕彈框發送。對於頁面來說都須要更新。站在H5角度但願NA區分,H5頁面調用的評論成功和NA調用的評論成功進行區分,這樣就能夠把模型一和模型二區分開獨立實現(同時也能夠區分頁面刷新的來源)。但站在NA角度來說,不關心誰吊起的,只要評論成功,就應該去調用更新頁面的H5方法。否則NA須要從調用開始就攜帶參數,一路到底。跟端溝通後,雙方都妥協了一步,簡單功能的進行了來源區分模型一實現,較爲複雜的模型二實現。

API封裝

API層處於JSBridge底層和業務,有些人也把它當作JSBridge的一部分,爲了更好理解,我將它單獨抽離出來。此處主要封裝業務層調用,以下面代碼。

此處多說一句:平日開發要有封裝和抽離的思想,一方面減小重複代碼,一方面不斷抽離將代碼分層,沒一層能夠作一些封裝和擴展,能夠提升代碼複用性。

JSB注入時機

NA注入

咱們確定是指望JSB注入越早越好,這樣不論在前端頁面中任何位置均可以隨時調用,NA注入JS的方法和時機都比較侷限。以下表:

平臺 方法 時機
IOS[UI] [self.webView stringByEvaluatingJavaScriptFromString:injectjs] webViewDidFinishLoad(會有時機問題)
IOS[wk] evaluateJavaScript:xxxx didCreateJavaScriptContext
Android webView.loadUrl("javascript:" + injectjs);) OnPageFinished

網頁描述頁面狀態的值有如下方法,根據兼容性及實現完整性,通常用DOMContentLoaded,IE9如下用readystatechange來判斷頁面是否加載成功。

名稱 父對象 描述 兼容性
DOMContentLoaded doc 頁面內容OK IE9+
onload win 頁面全部只要加載完成
readystatechange doc 頁面加載狀態:uninitialized(爲初始化):對象存在但還沒有初始化。loading(正在加載):對象正在加載數據。loaded(加載完畢):對象加載數據完成。interactive(交互):能夠操做對象了,但尚未徹底加載。complete(完成):對象已經加載完畢 IE9&IE10有實現bug

IOS的uiwebview提供了代理WebViewDidFinishLoad,WebViewDidFinishLoad 被調用時,readyState 可能處在 interactive 和 complete 兩種狀態,因此初始化頁面直接調用會有問題。對於這個問題從NA角度能夠實現一個NSObject的擴展,並實現webView:didCreateJavaScriptContext:forFrame。從H5角度能夠檢測頁面狀態,在complete以後再調用native。

IOS的didCreateJavaScriptContext和Android的OnPageFinished(the page has finished loading)均是在網頁onload以前完成,因此這兩個時機沒有調用順序的問題。

優勢:

1)註冊早,即便在頁面初始化就調用端能力,也能夠知足

缺點:

因爲咱們選擇的是uiwebview若是按照上面的考慮,這樣作有幾點不足之處 1)監聽實現成本高 2)須要NA注入,NA對於JS不熟悉,JS每每也不清楚NA邏輯,後面維護成本不可控制。

若是時間不充裕的狀況下,除了NA注入,還有別的辦法嘛?

JS注入

其實JS也能夠在頁面一開始就注入。好比在head裏直接應用抽離出來的Jsbridge代碼,本次8.0咱們採用了這種降級方案,短期內完成了架構搭建。

優勢:

這樣減少了維護成本,功能完整,提升了調用成功的概率。

缺點:

增長了頁面加載解析時間會影響白屏時間。

總結

Hybrid是一種鏈接H5跟NA的思路,便可以快速迭代H5功能,又能夠有NA的體驗,是混合開發的典型開發模式。實踐過程當中須要根據業務形態模型來定製代碼實現,注入時機也不是一成不變的能夠根據業務形態來選擇。

參考文獻

移動混合開發中的 JSBridge

遠程過程調用

你要的WebView與 JS 交互方式 都在這裏了

UIWebView與WKWebView、JavaScript與OC交互

iOS中UIWebView的使用詳解

UIWebView代碼注入時機與姿式

Hybrid 開發

JavaScriptCore在實際項目中的使用的坑

相關文章
相關標籤/搜索