JSBridge框架解決通訊問題實現移動端跨平臺開發

宜信技術學院發表於2019-05-20

 

一、跨平臺開發是趨勢

目前主流的移動端平臺主要是Android和iOS,為了儘可能複用程式碼和節省開發成本,各大巨頭都開發了自己的跨平臺框架,比如Facebook的React-Native、阿里的Weex、Cordova,以及今年Google開發者大會上介紹的Flutter框架。這些框架各有優缺點,但是到目前為止都沒有大規模地推廣開來,在我看來主要有以下幾個原因:

1、開發者生態圈還不夠成熟

RN是三大跨平臺框架中關注人最多、生態最活躍的框架,但是到目前為止也沒有到1.0版本(最新的release是0.57.8),更別說作為後來者的Weex和Flutter了。生態不成熟,意味著開發文件少,可以使用的開源控制元件少,比如在RN上想做一個最基本的下拉重新整理和上拉載入更多的listview都比較費勁。Weex已經貢獻給Apache,很久沒有更新release了。Flutter現在還在beta版本,其發展還有待觀察。

2、效能問題

雖然這幾大框架都對渲染效能做了優化,但是相比原生還是差一些,RN和weex都自己實現了一個瀏覽器核心(JSCore),因此多了一層js解析,渲染較慢。比如RN的listview,如果資料量太大就會出現卡頓。Flutter雖然自帶繪製引擎,但是跟原生比起來還是有一些距離。

3、相容問題

雖然這三大平臺的初衷都是為了跨平臺(Write/Learn once, run everywhere),但在實際應用中還是需要耗費很多的精力去相容和適配,比如RN在Android低端機器上表現就不盡如人意,連曾經RN的堅實擁護者Airbnb都宣佈放棄使用RN了。

4、開發整合成本

三個框架都需要學習新的語言React/vue/dart,weex的最大優勢就是入門簡單,但是版本迭代慢,RN上手門檻高,開發除錯難度大,整合RN和weex框架還會加入很多so檔案,增加安裝包的大小(至少在10M左右),這還不包括第三方的library。Flutter因為剛出來,應用的人還不多,其效果還有待觀察。

總結:雖然上述自主研發的跨平臺框架都或多或少地存在問題,但是移動開發的跨平臺是大勢所趨,可以節省開發成本,提高開發效率,迅速響應業務變化,現在主流的應用還是使用H5和原生的通訊來實現跨平臺的開發。Android和iOS平臺都有自己內建的瀏覽器核心webkit框架,跨平臺的本質就是用H5/JS編寫的程式碼能夠分別執行在Android和IOS的WebView中,從而實現一套程式碼兩個平臺都能執行的目的。

二、安卓跨平臺開發實踐

在Android平臺上要實現Native和JS的通訊主要通過WebViewClient和WebChromeClient兩個類來實現。

  • WebViewClient的作用是幫助WebView處理各種通知、事件請求,其主要的方法有:onLoadResource、onPageStart、onPageFinished、onReceiveError、shouldOverrideUrlLoading等;

  • WebChromeClient處理JS頁面的事件響應,比如網頁中的對話方塊、網頁圖示、網站標題、網頁的載入進度等事件,對應的響應方法有onJsAlert、onJsConfirm、onJsConsole、onProgressChanged、onReceiveIcon、onReceiveTitle等。

要實現Java和JS通訊就要:

  • 解決Java調JS;

  • JS調Java。

Java呼叫JS通過loadUrl和evaluateJavaScript兩個方法。

通過webview.loadUrl(“javascript:alert(‘hello world’)”),可以在android平臺將js程式碼注入到html頁面,loadUrl方法可以直接呼叫js中定義的函式,也可以把android本地的assets目錄下的js檔案以字串的形式注入到html頁面中,但是這個注入時機一定要等到html頁面載入完畢才能做,即在WebViewClient.onPageFinished的回撥函式中呼叫,這樣就相當於在html頁面中直接引用了js資原始檔。對於客戶端來說,java呼叫js本質上是一個拼接js字串的過程,但是呼叫loadUrl不能直接獲取js函式的返回值,而要實現Java呼叫js函式後。

獲取js函式的返回值可以使用webview.evaluateJavaScript方法,但是該方法只有在android4.4及以上的版本才可以使用。其他用法和loadUrl一致。

JS呼叫Java可以分為三種:物件對映、URL攔截、JS方法攔截。

物件對映是通過webview.addJavascriptInterface(new JSObject(), “javaObject”),這樣可以js程式碼中可以直接呼叫javaObject物件,從而實現JS呼叫Java的功能,但是這個方法在android4.2以下會有安全漏洞,利用反射機制呼叫Android API getRuntime執行shell命令進行攻擊,比如遍歷sdcard、傳送簡訊、安裝木馬APK等。

URL攔截是指在html頁面通過iframe.src、window.open、documention.location或者href,這四種方法都可以在html頁面中開啟一個連線,從而會觸發Java中的WebViewClient.shouldOverrideUrlLoading方法。例如在js中執行

在shouldOverrrideUrlLoading中可以根據約定的協議格式(Scheme)和協議名(Authority)獲取從JS中傳輸過來的資料(Data)。

在JS中呼叫alert、console、prompt、confirm等方法就會觸發WebChromeClient的onJsAlert、onConsoleMessage、onJsPrompt、onJsConfirm方法的回撥。比如在js中可以呼叫

在onJsPrompt的message中可以獲取prompt的內容,然後根據約定的協議格式可以獲取資料。

三、JSBridge框架

為了解決JS和Native的通訊問題,需要使用一個JSBridge框架(https://github.com/lzyzsd/JsBridge)用來負責H5和Java之間的通訊,此時需要解決以下兩個問題:

1)JS互相Java呼叫後如何回撥,將responseData傳遞回去;

2)JS呼叫Java有三種方法,如果選擇哪一種方法比較合適。

針對問題1,可以在java端和js端定義一個資料結構: Message={callbackId:xxx, handleName:xxx,responseData:xxx,responseId:xxx}。將回撥函式儲存在callbackId中,當JS或者Java處理完資料回撥的時候再將儲存在callbackId的回撥函式存放在responseId,相應的回撥的資料存放在responseData中,這樣就能響應JS或者Java呼叫後的回撥訊息。

Js呼叫Java的方法雖然有三種,但是addJavaScriptInterface存在安全性問題一般不建議使用,JS中的alert、console方法都會在Html頁面比較常用,confirm和prompt雖然不常用但是某些手機系統版本上會有對話方塊彈出,不通用,所以比較好的選擇是url攔截,可以通過iframe.src觸發shouldOverrideUrlLoading。

JsBridge框架的使用主要分為:

  • 在H5頁面載入完畢注入一個本地的js檔案;

  • Java程式碼中註冊BridgeHandler,用來處理JS傳送過來的訊息;

  • 在本地注入的js檔案中定義_handleMessageFromNative,用來接收java傳遞過來的訊息;

  • 因為客戶端注入js是非同步的,所以需要在js檔案中註冊Event監聽器,成功後通知H5。

Native呼叫JS,例如通過

webview.loadUrl(
"javascript:WebViewJavascriptBridge._handleMessageFromNative('{
\"callbackId\":\"JAVA_CB_2_559\",\"data\":\"just data from java\"
}')");

 

這樣就可以呼叫JS的handleMessageFromNative方法,傳遞的資料格式是Message,callBackId響應js的回撥,傳送前會儲存到HashMap中,js回撥的時候根據JAVA_CB_2_559找到對應的的回撥函式處理js的響應資料,具體流程如下:

Js呼叫Java,通過sendMessageQueue將傳遞的資訊轉換成

Message= {data: {…}, callbackId: "cb_1_1234"}

 

其中callbackId是js的回撥函式。然後通過

iframe.src=’yy://return/_fetchQueue/[{"data":"xxxx","callbackId":"cb_1_4321"}]’,

 

觸發shouldOverrideUrlLoading方法,java處理完js傳遞過來的data後,將回撥函式cb_1_4321設定到

Message={responseId: cb_1_4321, responseData:XXX},

 

這樣在js中就能處理回撥函式。具體的流程圖如下:

JsBridge框架提供兩種Handler方法,registerHandler方法需要傳入handler的名字,這樣需要iOS、Android、H5三方約定這個名字,因為H5開發的的業務需求變化比較快,而且H5呼叫原生的方法也是隨機的,所以每次都用registerHandler約定名字需要使用全域性變數存放這些handler方法名不便於擴充套件,所以實際開發中使用defaultHanlder,H5呼叫原生方法的的時候約定一個cmd,即約定data={cmd:xxx,time:data:{…}},只要在原生程式碼中對cmd命令有對應的功能,那麼H5頁面就可以隨時呼叫原生的方法。

JSBridge的改進建議,由於webview呼叫js方法的時候必須在主執行緒才能生效,所以偶然會出現java呼叫js失敗。另外,Js呼叫Java偶爾也會失敗,因為iframe機制不能保證每次都能觸發shouldOverrideUrlLoading回撥。

目前JSBridge採用的是url scheme的方式,如果不考慮Android4.2以下,iOS7以下,可以採用的互動,比如直接使用addJavaScriptInterface在JS頁面注入一個Native物件,將之前觸發u步驟變為使用這個Native物件向Native傳送訊息。這種方法只是一個可行的方案,實際使用過程中目前的JSBridge方案基本上滿足業務需求了。

作者:周智

來源:宜信技術學院

相關文章