Hybrid混合開發知識點(二)
本文基於上文Hybrid混合開發知識點(一) 的部分示例,闡述下我採用的JsBridge框架協議的思路,期間也對比了市場上的其他JsBridge框架,後續文章會付上原始碼解析。
JsBridge橋協議
在Hybrid混合開發中,很多場景需要H5與Android互動通訊。為避免硬編碼造成的臃腫現象,需要統一Android和H5互動的路由協議。
- 為js新增Java對映物件AppBridgeManager.class
//H5呼叫Android t.webview.addJavascriptInterface(new AppBridgeManager(t.webview), "app_bridge");
- AppBridgeManager類中統一處理對映物件方法呼叫
public class AppBridgeManager { private WebView webView; private Gson gson = new Gson(); public AppBridgeManager(WebView webview) { this.webView = webview; } @JavascriptInterface public String appInvoke(String json) { String string = ""; try { JsBridgeModel jsBridgeModel = gson.fromJson(json, JsBridgeModel.class); string = invoke(jsBridgeModel); } catch (JsonParseException e) { e.printStackTrace(); } return string; } /** * 通過反射機制動態呼叫解析類方法 * * @param jsBridgeModel * @return */ private String invoke(JsBridgeModel jsBridgeModel) { if (jsBridgeModel == null) return ""; //H5返回的service要和註冊bridgeModelMap的key一致,確保唯一性 Class<?> model = App.bridgeModelMap.get(jsBridgeModel.getService()); if (model == null) return ""; String result = ""; try { //引數型別格式統一,便於反射動態呼叫 Method method = model.getMethod( jsBridgeModel.getMethod(), String.class, String.class, WebView.class); JSONObject jsonObject = new JSONObject(jsBridgeModel.getData()); if (jsonObject == null) return ""; method.setAccessible(true);//忽略訪問許可權的限制 result = (String) method.invoke( model.newInstance(), jsBridgeModel.getRequestId(), jsonObject.toString(), webView); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } return result; } }
採用Method.invoke()反射機制,在執行時動態呼叫具體業務的類方法,可實現在此統一處理H5與Android的互動協議。
需要滿足一下幾點:
- H5呼叫Android的對映方法引數裡格式統一,推薦json結構體形式,如示例JsBridgeModel.class:
//json結構體 { "requestId":"1231313", "service":"forward", "method":"H5Page", "data":{ //data內部結構體根據業務靈活選擇 "url":"http://baidu.com", "newPage":false } } //json實體類 public class JsBridgeModel { private String requestId; private String service; private String method; private Map<String, Object> data; }
service代表的模組,比如"service":"forward"代表跳轉動作。method代表方法名稱,比如"method":"H5Page"代表跳轉H5頁面,若json回傳值為"method":"nativePage",表示跳轉原生頁面。這裡"method":"H5Page"的value就是Method.invoke()對映呼叫的方法。
- 在Application類中的Map集合中註冊具體業務對映類。
public static HashMap<String, Class<?>> bridgeModelMap = new HashMap<>(); /** * 註冊jsBridge協議類 */ private void registerBridgeModel() { bridgeModelMap.put("forward", ForwardPageModule.class); }
- 實現具體對映類業務。
public class ForwardPageModule { public void H5Page(String requsetId, String data, WebView webView) { //{ //"url":"http://baidu.com", //"newPage":false //} } public void nativePage(String requsetId, String data, WebView webView) { } }
如上註釋中的資料,通過Gson解析引數data獲得對應值,結合ActivityLifecycleCallbacks獲取當前activity,實現頁面跳轉邏輯。其他非頁面跳轉的業務同理。
Java的反射機制
- 支援執行時判斷任意物件所屬的類;
- 支援執行時構造任意類的物件例項;
- 支援執行時判斷任意類的成員變數和方法;
- 支援執行時呼叫任意物件的方法;
- 支援動態代理;
Method的getMethod()可根據方法名稱和相關引數,定位到需要查詢的Method物件。
@CallerSensitive public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException { return getMethod(name, parameterTypes, true); }
根據原始碼可知,引數型別須物件型別,如int->Integer.class。在上述示例中,
Method method = model.getMethod( jsBridgeModel.getMethod(), String.class, String.class, WebView.class);
對應的解析類中的方法引數格式要統一,否則會丟擲NoSuchMethodException。
public void H5Page(String requsetId, String data, WebView webView) {}
根據定位到的Method物件,可通過invoke()動態呼叫包裝在Method物件中的公開方法,如上例。
資原始檔預載入
每載入一個H5頁面,會產生很多網路請求。因此對於變動較少的頁面可儲存在assets檔案目錄中,提升了H5頁面載入效率,降低了網路環境對此的影響。
- shouldInterceptRequest中攔截資原始檔並載入本地檔案(適用於大容量、重複使用的資原始檔)
@Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { //非UI執行緒,若return null 則自行請求網路載入資源 WebResourceResponse webResourceResponse = null; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { String url = request.getUrl().toString(); if (url.contains("baidu")) { try { InputStream inputStream = getAssets().open("lauch.png"); webResourceResponse = new WebResourceResponse("image/png", "UTF-8", inputStream); } catch (IOException e) { e.printStackTrace(); } } } //return super.shouldInterceptRequest(view, request); return webResourceResponse; }
如上,shouldInterceptRequest方法是在非UI執行緒中執行的,若return null,則預設載入網上資源。另外注意版本相容。
載入URL中的資原始檔如圖片時,可將圖片資源存放在assets檔案目錄中,並在WebViewClient#shouldInterceptRequest()方法攔截URL時,判斷所載入資源URL並替換為載入本地資原始檔。此方案可大幅度提高H5頁面資源載入效率,省電省流量且使用者無感知。
- assets檔案目錄載入資源
res目錄下的資原始檔是參與編譯的,assets目錄下的資原始檔是原生資原始檔不參與編譯。但兩者都繫結在apk檔案中,不會解壓到/data/data/包名目錄下。
webView.loadUrl("file:///android_asset/web/index.html");
後續會繼續針對開源Hybrid混合框架原始碼解析,敬請期待。