日前在總結專案中已有的jsbridge方案的時候,因為覺得存在諸多不合理的地方,所以針對業務的場景以及實際的情況,重寫了一個簡單易用的js-bridge方案,命名為EasyBridge
EasyBridge是一個簡單易用的js-bridge的工具庫,提供了日常開發中,JavaScript與Java之間通訊的能力,與其他常見的js-bridge工具庫實現方案不同,EasyBridge具備以下幾個特點:
- 基於Android
WebView
的addJavascriptInterface
特性實現 - 提供了基於介面粒度的安全管理介面
- 輕量級,並且簡單易用。以這個工具庫作為依賴,只需要編寫實際通訊介面
實現原理說明
混合開發一直是工業界移動端開發比較看好的技術手段,結合h5的特性,能夠更好的支援業務發展的需要,不僅快速上線、部署功能而且能夠快速響應線上的bug。目前混合開發的方案包括:
- JSBridge
- Cordova
- React Native
- Flutter
EasyBridge就是一種簡單的JSBridge解決方案。在眾多的解決方案中,都是在利用系統的WebView
所開放的許可權和介面,開啟Java與JavaScript通訊的渠道,這些方案的實現原理分別包括:
-
攔截
onJsPrompt()
方法當
WebView
中的頁面呼叫了JavaScript當中的window.prompt()
方法的時候,這個方法會被回撥。而且這個方法不僅能獲取到JavaScript傳遞過來的string字串內容,同時也能返回一段string字串內容被JavaScript接收到,是一個相當適合構建bridge的入口方法。 -
攔截
shouldOverrideUrlLoading()
方法當頁面重新load URL或者頁面的iframe元素重新載入新的URL的時候,這個方法被回撥。
-
addJavascriptInterface()
介面這個介面簡單卻強大,通過這個介面,我們能夠直接把Java中定義的物件在JavaScript中對映出一個對應的物件,使其直接呼叫Java當中的方法,但是,在android 4.1及之前的版本存在著嚴重的漏洞,所以一直被忽視。
EasyBridge在眾多的解決方案中,最終了選擇了addJavascriptInterface()
介面作為方案的基礎,主要基於以下幾點考量:
- 目前Android版本已經到了9.0版本,市面上Android4.4之前的版本手機佔有率已經很低,很多業務都已經把最低相容版本定在了4.2以上,因此不需要考量4.1以下存在的漏洞問題;
addJavascriptInterface()
能夠提供最簡單的同步呼叫addJavascriptInterface()
與evaluateJavascript()
/loadUrl
結合,能夠帶來更加簡單的非同步呼叫的解決方案
方案設計說明
EasyBridge最終方案實現,只支援了非同步呼叫的方式,主要是基於以下的考量:
- 同步的呼叫可以轉化為非同步呼叫的方式,保留一種呼叫方式會使得整個方案更加簡單;
方案結構
EasyBridge的方案結構如下圖所示:
EasyBridge總共會向頁面中注入兩個JavaScript物件,:
-
easyBridge
在頁面載入完成
onPageFinished()
回撥的時候,通過執行工具庫中的一個js檔案注入的。這個物件主要的作用是定義了業務頁面的JavaScript程式碼呼叫native的Java程式碼的規範入口,物件中定義的一個最關鍵的函式就是callHandler(handlerName, args, callback)
,這就是橋樑的入口。實際上在這個方法的內部,最終就是通過下面的**_easybridge**物件進入到Java程式碼層。 -
_easybridge
通過
addJavascriptInterface()
對映和注入的一個物件,這個物件提供了實質的入口方法enqueue()
,在這個方法當中程式碼的路線從JavaScript層進入到了Java層,開啟了兩者的互動。
介面分發
實際上,我們可以通過@JavascriptInterface
註解開放很多的介面給JavaScript層呼叫,也可以通過addJavascriptInterface()
對映多個Java物件到JavaScript層,但是為了維護簡單和通訊方便,EasyBridge的設計只提供了一個入口和一個出口。所有需要開放給JavaScript層的功能,都是通過構建介面例項進行處理。
介面的定義如下:
public interface BridgeHandler {
String getHandlerName();
void onCall(String parameters, ResultCallBack callBack);
SecurityPolicyChecker securityPolicyChecker();
}
複製程式碼
實際的工作流程如下圖所示:
最開始初始化的時候需要註冊所有可以被JavaScript層呼叫的業務介面。在執行的過程中,enqueue()
入口當中會根據協議定義,通過介面名稱找到對應的處理介面例項,並觸發介面響應。並且最終的介面響應都在入口處進行回傳。因此,實際上,_easybridge物件(在Java層中,其實是EasyBridge
的例項)就是一個樞紐站,做任務的分派和結果的傳遞。
安全控制
每一個BridgeHandler
例項,都可以定義自己的安全控制策略,對應的是一個SecurityPolicyChecker
的例項,其定義如下:
public interface SecurityPolicyChecker {
boolean check(String url, String parameters);
}
複製程式碼
每一個介面在接收到分派的指令之前,會先呼叫其安全控制策略,根據當前載入的頁面地址以及傳入的指令引數判斷是否需要進行指令的分派,否則將會直接命令安全受限,錯誤返回,結果呼叫。
方案使用
EasyBridge是一個極其簡單易用的方案,只需要簡單的幾步即可具備JavaScript層與Java層通訊的能力。在引入EasyBridge庫作為依賴之後:
-
繼承/直接使用
EasyBridgeWebView
EasyBridgeWebView
是功能的承載者,負責了bridge物件的注入,以及handler介面的管理(內部使用`EasyBridge物件管理) -
根據業務以及協議定義實現對應的
BridgeHandler
例項 -
在載入第一步的webview的例項頁面繫結第二步構造的handler例項
以上三步即完成了所有的工作。
如果你需要除錯這個方案的實際工作,你可以在把手機連線到電腦之後,使用chrome進行除錯。EasyBridge會把傳遞的結果資訊以及錯誤資訊列印在控制檯之上。你將會很容易的感知和發現問題。關於在Chrome中除錯web頁面,你可以參考官方的教程文件Remote Debugging WebViews
關於這個方案目前已經具備的feature,以及demo,歡迎訪問我的GitHub倉庫EasyBridge。同時歡迎大家對這個方案的設計和實現提出你們的改進意見,謝謝