【Android】實現一個JSBridge

為什么使用JSBridge

<p>

在平時業務的開發中,為了追求開發的效率以及移植的便利性,一些展示性強的頁面我們會偏向于使用h5來完成,功能性強的頁面我們會偏向于使用native來完成,而一旦使用了h5,為了在h5中盡可能的得到native的體驗,我們native層需要暴露一些方法給js調用,比如,彈Toast提醒,彈Dialog,分享等等,同樣我們也可以通過native方法去直接調用js的方法,所以 JsBridge 就是用來在 Android app的原生 java 代碼與 javascript 代碼中架設通信(調用)橋梁的輔助工具。

這里我們分析一下JSBridge的使用場景

  • 1.JS調用JAVA的方法
  • 2.JAVA調用JS的方法
  • 3.JS調用JAVA方法后,JAVA回調給JS
  • 4.JAVA調用JS方法后,JS回調給JAVA

所以針對上面的使用場景,我們分別來實現

1.JS調用JAVA的方法

<p>

WebView 提供了一個接口,可以讓我們注入 Java 對象到頁面中,這樣,頁面中的 javascript 就能直接訪問 Java 對象的接口,從而實現 Java 和 Javascript 的交互。

首先必須啟用 WebView 中的 Javascript 支持

mWebView.getSettings().setJavaScriptEnabled(true);

注入 Java 對象到 WebView 中

 mWebView.addJavascriptInterface(new JavaExecutor(mWebView), "JavaExecutor");

Java 對象定義如下(需要特別注意的是,在 JELLY_BEAN_MR1 之后,只有 public 且添加了 @JavascriptInterface 注解的方法才能被調用)

    @JavascriptInterface
    public final void onJSExecutorJava(String className,String methodName,String params) throws Exception {

        Class<?> targetClass = Class.forName(JSApplication.Instance().getPackageName()+ "." + className);
        HashMap<String, Method> extendsMethods =  getAllMethod(targetClass);;

       if (extendsMethods.containsKey(methodName)) {
           Method method = extendsMethods.get(methodName);
           if (method != null) {
                   method.invoke(null, new JSONObject(params));
           }
       }

    }

    private static HashMap<String, Method> getAllMethod(Class injectedCls) throws Exception{
        HashMap<String, Method> mMethodsMap = new HashMap<>();
        Method[] methods = injectedCls.getDeclaredMethods();
        for (Method method : methods){
            String name;
            if (method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC) || (name = method.getName()) == null){
                continue;
            }
            Class[] parameters = method.getParameterTypes();
            if (null != parameters && parameters.length == 1){
                if (parameters[0] == JSONObject.class) {
                    mMethodsMap.put(name, method);
                }
            }
        }
        return mMethodsMap;
    }

所以JS可以調用的就是JavaExecutor類中的onJSExecutorJava方法,這個方法我定義了三個參數,分別是調用類,方法與傳遞過來的參數(主要定義為json類型的格式)

接下來就是看看JS里面的實現了

call: function (obj, method, params) {
     JavaExecutor.onJSExecutorJava(obj, method ,JSON.stringify(params));
},  

JSBridge.call('JSBridgeImpl','showToast',{'msg':'Hello JSBridge'});

我這里是調用了JSBridgeImpl的showToast方法,傳遞的參數是'msg':'Hello JSBridge',這個方法是彈一個Toast,并且提示語為Hello JSBridge,這個方法比較簡單就不貼出來了

接下來就是運行實驗

圖1 Js調用Java方法

所以JS調用JAVA成功了

2.JAVA調用JS的方法

<p>

至于JAVA調用JS大致有以下幾個可用方法:

1.loadUrl方法

webView.loadUrl("javascript:scriptString"); //其中 scriptString 為 Javascript 代碼

2.在 KITKAT 之后,又新增了一個方法:

 webView.evaluateJavascript(scriptString, new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
    
        }
    });//其中 scriptString 為 Javascript 代碼,ValueCallback 的用來獲取 Javascript 的執行結果。這是一個異步調用。

所以其實也是調用了JS的方法,接下來看下JS的方法

onJavaCall: function (method, params){
    var targetFn = window[method(params)];
    if (targetFn) {
        targetFn(this);
    }
},  

我這里定義了兩個參數分別是調用方法與傳遞過來的參數(主要定義為json類型的格式)

接下來看下實際調用的方法

private static final String CALLBACK_JS_FORMAT = "JSBridge.onJavaCall(%s, %s);";

private void transact(final String script){
    mHandler.post(new Runnable() {
        @Override
        public void run() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                mWebView.evaluateJavascript(script, new ValueCallback<String>() {
                    @Override
                    public void onReceiveValue(String value) {

                    }
                });
            } else {
                mWebView.loadUrl("javascript:" + script);
            }
        }
    });
}

JSONObject object = new JSONObject();
object.put("key", "value");
object.put("key1", "value1");
transact(String.format(CALLBACK_JS_FORMAT, "jsFn2", String.valueOf(jsonObject));

所以是調用了js中的onJavaCall方法,通過onJavaCall去調用jsFn2方法并且把相關的json數據傳遞過去了

看一下我們定義的jsFn2方法

function jsFn2(res) {
    title.style.background = 'red';
    console.log(JSON.stringify(res))
}   

這里主要把html中的標題的背景色進行了修改,并把傳遞的數據打印出來

運行如下

圖2 Java調用Js方法一
圖3 Java調用Js方法二

所以JAVA調用JS成功了

3.JS調用JAVA方法后,JAVA回調給JS

<p>

這種類型的使用場景,肯定會在日常開發中會遇到,所以我們可以以并非JSBridge的方式去想這個問題,普通Java相關使用場景看到會用到CallBack,那么這里我們同樣使用CallBack來處理回調。

對于JS和JAVA中的CallBack肯定不是同樣的類型,所以我們需要把JS中定義的CallBack生成一個ID,并把ID與CallBack關聯存儲下來,然后把ID傳遞給執行JAVA方法,當JAVA方法執行完后再通過上述的JAVA調用JS的方法調用JS中處理回調的方法,同時把CallBack的ID,與數據傳遞過來,JS通過ID獲取關聯數據中的CallBack,然后執行CallBack的方法

所以流程如下

1.JS調用JAVA方法,傳遞需要信息,與CallBackID
2.JAVA方法執行完后,調用JS的回調處理方法,通過CallBackID獲取CallBack方法
3.執行CallBack方法

所以這里我們需要對JS調用JAVA的方法中定義的方法進行修改

@JavascriptInterface
public final void onJSExecutorJava(String className,String methodName,String params,long callbackId) throws Exception {

    Class<?> targetClass = Class.forName(JSApplication.Instance().getPackageName()+ "." + className);
    HashMap<String, Method> extendsMethods =  getAllMethod(targetClass);

   if (extendsMethods.containsKey(methodName)) {
       Method method = extendsMethods.get(methodName);
       if (method != null) {
               JSCallBack callBack = new JSCallBack(mWebView,callbackId);
               method.invoke(null, new JSONObject(params),callBack);
       }
   }
}

public class JSCallBack {

    private static final String CALLBACK_JS_FORMAT = "JSBridge.onJSCallBack(%d, %s);";

    WebView mWebView;
    long mCallbackId;

    public JSCallBack(WebView webView,long callbackId) {
        mWebView = webView;
        mCallbackId = callbackId;
    }

    public void onTranst(JSONObject jsonObject){
        final String execJs = String.format(CALLBACK_JS_FORMAT,mCallbackId,String.valueOf(jsonObject));
        new JsExecutor(mWebView).JavaExecutorJS(execJs);
    }
}

這里主要是為JS執行的JAVA方法增加了callbackId,并通過callbackId新建一個JSCallBack對象,當JS要執行的JAVA方法執行結束,就可以執行JSCallBack中的onTranst來調用loadUrl方法來執行JS中回調處理方法

接下來看一個JS中回調處理方法

 onJSCallBack: function (callbackId, params){
            var callback = this.callbacks[callbackId];
            callback && callback(params);
            delete this.callbacks[callbackId];
        },

這里是通過callbackId獲取callback,并執行callback方法,最后刪除存儲的callback

最后看一下JS調用JAVA的方法,這里也需要進行修改

 call: function (obj, method, params, callback) {
             var callbackId = Util.getID();
             this.callbacks[callbackId] = callback;
             JavaExecutor.onJSExecutorJava(obj, method ,JSON.stringify(params), callbackId);
        },

JSBridge.call('JSBridgeImpl','showToast',{'msg':'Hello JSBridge'},function (res){console.log(JSON.stringify(res))})

這里是首先獲取了callbackId,并存儲下來,最后將callbackId傳遞給執行的JAVA方法

接下來就是測試一下我們執行的方法,我們這邊是調用了showToast方法,并將回調的數據打印出來。
如下

圖4 Js調用Java方法
圖5 Js調用Java方法,回調打印結果
4.JAVA調用JS方法后,JS回調給JAVA

<p>

其實JAVA調用JS方法JS回調給JAVA和前面的方法步驟是類似的

1.JAVA調用JS方法,傳遞需要信息,與CallBackID
2.JS方法執行完后,調用JAVA的回調處理方法,通過CallBackID獲取CallBack方法
3.執行CallBack方法

所以這里我們需要對JAVA調用JS的方法中定義的方法進行修改

private static final String CALLBACK_JS_FORMAT = "JSBridge.onJavaCall(%s, %s, %d);";

 public void JavaExecutorJS(String method,JSONObject jsonObject,JavaCallBack callBack){
      long callbackId = System.currentTimeMillis();
      Commons.mJavaCallBacks.put(callbackId + "",callBack);
      transact(String.format(CALLBACK_JS_FORMAT, method, String.valueOf(jsonObject), callbackId));
  }

public interface JavaCallBack {
    void onReceiveResult(JSONObject jsonObject);
}

這里獲取當前的時間作為callbackId,然后通過loadurl調用JSBridge的onJavaCall方法

onJavaCall: function (method, params, callbackId){
    var targetFn = window[method(params,callbackId)];
    if (targetFn) {
        targetFn(this);
    }
},  

當我們調用JS方法時候會把參數和callbackId傳遞過去,當JS方法執行完后,調用JAVA方法將返回的參數與callbackId傳遞過去就可以調用JAVA中定義的Callback方法了

JavaExecutor增加回調返回方法

@JavascriptInterface
public final void onJavaCallBack(String params,long callbackId){
    try {
        JavaCallBack callBack = Commons.mJavaCallBacks.get(callbackId + "");
        callBack.onReceiveResult(new JSONObject(params));
    } catch (JSONException e) {
        e.printStackTrace();
    }
}

回調后獲取callback,調用callBack的onReceiveResult方法

同時JS中也要添加相關的調用方法如下

// JAVA調用js后的回調方法
onJAVACallBack: function (callbackId, params){
    JavaExecutor.onJavaCallBack(JSON.stringify(params), callbackId);
},  

到這里基本就完成了JAVA調用JS方法后,JS回調給JAVA的需求了

接下來看一下JAVA調用方法與JS中被調用的方法

JSONObject object = new JSONObject();
object.put("key", "value");
object.put("key1", "value1");
new JsExecutor(mWebView).JavaExecutorJS("jsFn2", getJSONObject(0, "ok", object), new JavaCallBack() {
    @Override
    public void onReceiveResult(JSONObject jsonObject) {
        Toast.makeText(getBaseContext(),jsonObject.toString(),Toast.LENGTH_SHORT).show();
    }
}); 
function jsFn2(res,callbackId) {
            title.style.background = 'red';
            console.log(JSON.stringify(res))
            JSBridge.onJAVACallBack(callbackId,{'msg':'Hello JSBridge'})
        }

所以需要實現的效果是,title的背景色變化后彈出Toast
運行如下

圖6JAVA調用JS方法后,JS回調給JAVA

bingo~~~完成

寫在后面的話

<p>

java 代碼與 javascript 代碼中通信通過這幾種方式都可以實現,當然根據不同的應用和業務需求,開發者們可以根據各自的場景進行相關封裝,peace~~~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容

  • 在Android中,JSBridge已經不是什么新鮮的事物了,各家的實現方式也略有差異。大多數人都知道WebVie...
    Jannonx閱讀 1,342評論 0 5
  • 隨著H5性能的提升,在我們移動應用開發的過程中,我們會越來越多的在我們的App頁面內嵌入H5頁面,使得App變的更...
    Jensen95閱讀 4,225評論 0 21
  • 先問男生們一個問題。 嬌柔文靜淑女完美的女孩,和潑辣有個性又愛又恨的女孩,你們喜歡哪一種? 估計每一個男生都會有不...
    小灰灰喜歡吃草莓閱讀 841評論 0 3
  • 偶然的機會,看到了成都2017國際馬拉松的報名鏈接,也許是年輕的心還有那么一點倔強,手指輕敲著完成了報名:全馬,4...
    掬一口閱讀 286評論 0 1
  • 夏建建隨堂弟夏小樂搭渡船過河來到藕池鎮上,不巧在派出所門前碰到花蝴蝶。花蝴蝶主動上前問道: "夏小樂,你帶的...
    紫翼惠瑄閱讀 397評論 0 0