【Android】實現(xiàn)一個JSBridge

為什么使用JSBridge

<p>

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

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

  • 1.JS調(diào)用JAVA的方法
  • 2.JAVA調(diào)用JS的方法
  • 3.JS調(diào)用JAVA方法后匿情,JAVA回調(diào)給JS
  • 4.JAVA調(diào)用JS方法后兰迫,JS回調(diào)給JAVA

所以針對上面的使用場景,我們分別來實現(xiàn)

1.JS調(diào)用JAVA的方法

<p>

WebView 提供了一個接口码秉,可以讓我們注入 Java 對象到頁面中逮矛,這樣鸡号,頁面中的 javascript 就能直接訪問 Java 對象的接口转砖,從而實現(xiàn) Java 和 Javascript 的交互。

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

mWebView.getSettings().setJavaScriptEnabled(true);

注入 Java 對象到 WebView 中

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

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

    @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可以調(diào)用的就是JavaExecutor類中的onJSExecutorJava方法晋控,這個方法我定義了三個參數(shù),分別是調(diào)用類姓赤,方法與傳遞過來的參數(shù)(主要定義為json類型的格式)

接下來就是看看JS里面的實現(xiàn)了

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

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

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

接下來就是運行實驗

圖1 Js調(diào)用Java方法

所以JS調(diào)用JAVA成功了

2.JAVA調(diào)用JS的方法

<p>

至于JAVA調(diào)用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 的執(zhí)行結果誓斥。這是一個異步調(diào)用只洒。

所以其實也是調(diào)用了JS的方法,接下來看下JS的方法

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

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

接下來看下實際調(diào)用的方法

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));

所以是調(diào)用了js中的onJavaCall方法劳坑,通過onJavaCall去調(diào)用jsFn2方法并且把相關的json數(shù)據(jù)傳遞過去了

看一下我們定義的jsFn2方法

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

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

運行如下

圖2 Java調(diào)用Js方法一
圖3 Java調(diào)用Js方法二

所以JAVA調(diào)用JS成功了

3.JS調(diào)用JAVA方法后,JAVA回調(diào)給JS

<p>

這種類型的使用場景距芬,肯定會在日常開發(fā)中會遇到涝开,所以我們可以以并非JSBridge的方式去想這個問題,普通Java相關使用場景看到會用到CallBack框仔,那么這里我們同樣使用CallBack來處理回調(diào)舀武。

對于JS和JAVA中的CallBack肯定不是同樣的類型,所以我們需要把JS中定義的CallBack生成一個ID存和,并把ID與CallBack關聯(lián)存儲下來,然后把ID傳遞給執(zhí)行JAVA方法奕剃,當JAVA方法執(zhí)行完后再通過上述的JAVA調(diào)用JS的方法調(diào)用JS中處理回調(diào)的方法,同時把CallBack的ID捐腿,與數(shù)據(jù)傳遞過來纵朋,JS通過ID獲取關聯(lián)數(shù)據(jù)中的CallBack,然后執(zhí)行CallBack的方法

所以流程如下

1.JS調(diào)用JAVA方法茄袖,傳遞需要信息操软,與CallBackID
2.JAVA方法執(zhí)行完后,調(diào)用JS的回調(diào)處理方法宪祥,通過CallBackID獲取CallBack方法
3.執(zhí)行CallBack方法

所以這里我們需要對JS調(diào)用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執(zhí)行的JAVA方法增加了callbackId聂薪,并通過callbackId新建一個JSCallBack對象,當JS要執(zhí)行的JAVA方法執(zhí)行結束蝗羊,就可以執(zhí)行JSCallBack中的onTranst來調(diào)用loadUrl方法來執(zhí)行JS中回調(diào)處理方法

接下來看一個JS中回調(diào)處理方法

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

這里是通過callbackId獲取callback藏澳,并執(zhí)行callback方法,最后刪除存儲的callback

最后看一下JS調(diào)用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傳遞給執(zhí)行的JAVA方法

接下來就是測試一下我們執(zhí)行的方法,我們這邊是調(diào)用了showToast方法蓄愁,并將回調(diào)的數(shù)據(jù)打印出來双炕。
如下

圖4 Js調(diào)用Java方法
圖5 Js調(diào)用Java方法,回調(diào)打印結果
4.JAVA調(diào)用JS方法后撮抓,JS回調(diào)給JAVA

<p>

其實JAVA調(diào)用JS方法JS回調(diào)給JAVA和前面的方法步驟是類似的

1.JAVA調(diào)用JS方法妇斤,傳遞需要信息,與CallBackID
2.JS方法執(zhí)行完后丹拯,調(diào)用JAVA的回調(diào)處理方法站超,通過CallBackID獲取CallBack方法
3.執(zhí)行CallBack方法

所以這里我們需要對JAVA調(diào)用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調(diào)用JSBridge的onJavaCall方法

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

當我們調(diào)用JS方法時候會把參數(shù)和callbackId傳遞過去,當JS方法執(zhí)行完后乖酬,調(diào)用JAVA方法將返回的參數(shù)與callbackId傳遞過去就可以調(diào)用JAVA中定義的Callback方法了

JavaExecutor增加回調(diào)返回方法

@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();
    }
}

回調(diào)后獲取callback顷编,調(diào)用callBack的onReceiveResult方法

同時JS中也要添加相關的調(diào)用方法如下

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

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

接下來看一下JAVA調(diào)用方法與JS中被調(diào)用的方法

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'})
        }

所以需要實現(xiàn)的效果是剑刑,title的背景色變化后彈出Toast
運行如下

圖6JAVA調(diào)用JS方法后媳纬,JS回調(diào)給JAVA

bingo~~~完成

寫在后面的話

<p>

java 代碼與 javascript 代碼中通信通過這幾種方式都可以實現(xiàn),當然根據(jù)不同的應用和業(yè)務需求施掏,開發(fā)者們可以根據(jù)各自的場景進行相關封裝钮惠,peace~~~

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市七芭,隨后出現(xiàn)的幾起案子素挽,更是在濱河造成了極大的恐慌,老刑警劉巖狸驳,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件预明,死亡現(xiàn)場離奇詭異,居然都是意外死亡耙箍,警方通過查閱死者的電腦和手機撰糠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辩昆,“玉大人阅酪,你說我怎么就攤上這事≈耄” “怎么了术辐?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長施无。 經(jīng)常有香客問我辉词,道長,這世上最難降的妖魔是什么猾骡? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任瑞躺,我火速辦了婚禮隧魄,結果婚禮上,老公的妹妹穿的比我還像新娘隘蝎。我一直安慰自己,他們只是感情好襟企,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布嘱么。 她就那樣靜靜地躺著,像睡著了一般顽悼。 火紅的嫁衣襯著肌膚如雪曼振。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天蔚龙,我揣著相機與錄音冰评,去河邊找鬼。 笑死木羹,一個胖子當著我的面吹牛甲雅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坑填,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼抛人,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了脐瑰?” 一聲冷哼從身側響起妖枚,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎苍在,沒想到半個月后绝页,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡寂恬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年续誉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片初肉。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡屈芜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出朴译,到底是詐尸還是另有隱情井佑,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布眠寿,位于F島的核電站躬翁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏盯拱。R本人自食惡果不足惜盒发,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一例嘱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宁舰,春花似錦拼卵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至壤蚜,卻和暖如春即寡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背袜刷。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工聪富, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人著蟹。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓墩蔓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親萧豆。 傳聞我的和親對象是個殘疾皇子钢拧,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 在Android中,JSBridge已經(jīng)不是什么新鮮的事物了炕横,各家的實現(xiàn)方式也略有差異源内。大多數(shù)人都知道WebVie...
    Jannonx閱讀 1,328評論 0 5
  • 隨著H5性能的提升,在我們移動應用開發(fā)的過程中份殿,我們會越來越多的在我們的App頁面內(nèi)嵌入H5頁面膜钓,使得App變的更...
    Jensen95閱讀 4,207評論 0 21
  • 先問男生們一個問題。 嬌柔文靜淑女完美的女孩卿嘲,和潑辣有個性又愛又恨的女孩颂斜,你們喜歡哪一種? 估計每一個男生都會有不...
    小灰灰喜歡吃草莓閱讀 814評論 0 3
  • 偶然的機會拾枣,看到了成都2017國際馬拉松的報名鏈接沃疮,也許是年輕的心還有那么一點倔強,手指輕敲著完成了報名:全馬梅肤,4...
    掬一口閱讀 281評論 0 1
  • 夏建建隨堂弟夏小樂搭渡船過河來到藕池鎮(zhèn)上司蔬,不巧在派出所門前碰到花蝴蝶∫毯花蝴蝶主動上前問道: "夏小樂俊啼,你帶的...
    紫翼惠瑄閱讀 383評論 0 0