程序員筆記|JSBridge框架解決通信問題實現移動端跨平臺開發
一、跨平臺開發是趨勢
目前主流的移動端平臺主要是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方案基本上滿足業務需求了。
作者:周智
來源:宜信技術學院
程序員筆記|JSBridge框架解決通信問題實現移動端跨平臺開發