CC框架實踐(3): 讓jsBridge更優雅

齊翊發表於2017-12-25

前言

今天給大家講一下在CC框架下如何讓我們的jsBridge更加優雅。

jsBridge是作為js和java之間通訊的橋樑,本身它的職責只是完成通訊。

本文不是介紹js與java通訊過程的實現,你可以使用第三方庫(如:JsBridge),也可以自己來實現,或者用addJavascriptInterface,都可以。本文的側重點是如何讓我們的jsBridge不那麼臃腫,實現得更優雅,更利於維護。

但在實際封裝過程中,會發現需要我們需要解決很多耦合的問題:

  • js呼叫的功能在其他module中,如何呼叫到這些功能,如何向jsbridge註冊這些功能?
  • jsbridge依賴了太多module,怎麼解耦?
  • 當js呼叫的功能是開啟其它頁面獲取該頁面處理後的結果並回撥給js,怎麼破? onResume? startActivityForResult? 一個常見的場景是:開啟登入介面,登入成功後將使用者資訊回撥給js。你是不是想過這樣做?
    • jsBridge中封裝一個Activity/Fragment
    • 用startActivityForResult的方式來開啟登入頁面
    • 在onActivityResult方法中從登入介面設定的result中獲取使用者登入的資訊(或者onResume或EventBus方式來獲取返回值)
    • 然後將使用者資訊回撥給js

將具體的業務邏輯寫在jsBridge模組中,本身就是一個災難,而且隨著業務型別的增加,最後這個Activity/Fragment會變得非常臃腫,而且難以複用

快速瞭解CC

  • 是一套基於元件匯流排的元件化實施方案
  • 一靜一動,開發時執行2個app,業務環境始終是完整的:
    • 靜:主App (通過跨App的方式呼叫單元件App內的元件)
    • 動:正在開發中的單元件App (通過跨App的方式呼叫主App內的元件)
  • 支援漸進式元件化改造
    • 解耦只是過程,而不是前提

CC框架下如何讓jsBridge更優雅?

CC框架為所有元件提供了統一的呼叫入口和回撥結果格式。

所以,在CC框架下,js呼叫native變得很簡單:

  • jsBridge僅暴露一個介面給js,那就是元件呼叫介面
  • js呼叫jsBridge的介面,將元件呼叫所需的引數傳給jsBridge
  • jsBridge將引數透傳去呼叫功能元件(所有功能實現均在各個元件內部完成)
  • jsBridge中接收到呼叫結果後,將結果轉換成json回撥給js

流程圖:

jsBridge呼叫流程

JsBridge的核心程式碼如下:

public class JsBridge {

    private WeakReference<WebView> webViewWeakReference;

    public JsBridge(WebView webView) {
        this.webViewWeakReference = new WeakReference<WebView>(webView);
    }

    @JavascriptInterface
    public void callNativeCC(String componentName, String actionName, String dataJson, final String callbackId) {
        final WebView webView = webViewWeakReference.get();
        if (webView == null) {
            return;
        }
        Map<String, Object> params = null;
        if (!TextUtils.isEmpty(dataJson)) {
            try {
                JSONObject json = new JSONObject(dataJson);
                params = JsonUtil.toMap(json); //引數列表
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
        //統一使用這種方式進行CC呼叫,不用關心具體元件是如何實現的
        CC cc = CC.obtainBuilder(componentName)
            .steActionName(actionName)
            .setContext(webView.getContext()) //可用於startActivity等需要Context的功能
            .setParams(params)
            .build();
        if (TextUtils.isEmpty(jsCallbackId)) {
            cc.callAsync(); //無需回撥結果給js
        } else {
            cc.callAsyncCallbackOnMainThread(new IComponentCallback() {
                @Override
                public void onResult(CC cc, CCResult result) {
        			//將結果回撥給js
                    webView.loadUrl("javascript: callback(" + callbackId + "," + result + ")"); 
                }
            });
        }
    }
}

複製程式碼

是不是超級簡單?

這樣做的好處有:

  1. jsbridge迴歸初心:只是作為一個橋樑。
  2. jsBridge支援的功能更全面,app內部幾乎所有元件的功能都可以給js呼叫,而無需新增額外的程式碼
  3. 業務完全在元件內部實現,jsbridge跟元件之間無耦合
  4. 無論功能是同步實現的還是非同步回撥實現的,中間需要經歷什麼樣的流程,對於js和jsBridge來說呼叫方式完全一樣。
  5. 支援元件的按需依賴:jsBridge不再是全家桶,給多個app使用時,各app可以按需選擇需要支援js呼叫的元件,新增gradle依賴到主module的依賴列表中即可。
  6. 同一個元件在不同的app內可以有不同的實現,但需要保持介面協議一致,例如:不同app可以有自己特定的登入元件
  7. 後續新增新功能給js呼叫時,只要功能提供方實現一下,js中去呼叫即可,jsbridge元件無需修改

Tips


1. 有些功能必須要在onActivityResult中接收結果,如何在元件內部實現而不影響jsBridge?

確實有些功能必須要在onActivityResult中接收結果,例如:呼叫系統的選擇聯絡人、從系統相簿選擇圖片等。

其實,不止是onActivityResult,還有獲取許可權的回撥onRequestPermissionsResult

這些功能在元件內部實現時,可以在元件中通過建立一個透明的Activity或Fragment來實現結果的接收,然後將結果傳送給呼叫方: CC.sendCCResult(callId, result);

推薦使用Fragment方式實現

具體實現方式可參考如下開源庫:

2. js呼叫的有些功能需要使用者登入後才能用,如何加入登入條件判斷?

按照元件化開發的思想,是否需要登入才能用應由各元件自行判斷。

需要在元件內部完成登入狀態校驗、開啟登入介面、登入完成後再執行元件實際功能。

具體實現可參考另一篇文章: CC框架實踐(1):實現登入成功再進入目標介面功能

3. 沒有使用CC框架的情況下,如何讓jsBridge實現類似效果?

在沒有使用CC框架的情況下,也可以實現類似效果的。思路如下:

  1. 在工程的Common基礎庫中定義一套介面,例如: IJsCall/IJsCallback
public interface IJsCall {
    String name(); //功能的名稱,供js呼叫
    void handleJsCall(JSONObject params, IJsCallback callback);
}
public interface IJsCallback {
    void callback(String result);
}
複製程式碼
  1. 在所有需要註冊給js呼叫的元件中實現IJsCall介面,實現具體的業務邏輯
  2. 在jsBridge中建立一個IJsCall的管理類JsCallMananger,示例程式碼:
public class JsCallManager {
    private final Map<String, IJsCall> map = new HashMap<>();
    
    public static final String DEFAULT_RESULT = "{\"success\":false}";
    
    void init() {
        //用於IJsCall自動註冊到list
        //使用AutoRegister外掛將生成如下程式碼:
        // registerJsCall(new JsCallA());
        // registerJsCall(new JsCallB());
    }
    
    void registerJsCall(IJsCall call) {
        if (call != null) {
            map.put(call.name(), call);
        }
    }
    
    public void onJsCall(String name, JSONObject json, IJsCallback callback) {
        IJsCall jsCall = map.get(name);
        if (jsCall != null) {
            jsCall.handleJsCall(json, callback);
        } else {
            callback.callback(DEFAULT_RESULT);
        }
    }
}
複製程式碼
  1. 使用AutoRegister來完成IJsCall介面的自動註冊, Github原始碼原理介紹
  2. 在jsBridge中只暴露一個介面給js呼叫
  3. 在jsBridge中呼叫JsCallManager.onJsCall方法來實現統一的功能呼叫

總結


本文介紹了在CC框架下用元件呼叫的方式讓jsBridge實現跟具體業務完全解耦。並給出了非CC框架環境下實現類似效果的思路。

系列文章


CC:可關聯生命週期的android元件化開發框架

CC框架實踐(1):實現登入成功再進入目標介面功能

CC框架實踐(2):Fragment和View的元件化

CC框架實踐(3): 讓jsBridge更優雅

相關文章