【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阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 在Android中,JSBridge已经不是什么新鲜的事物了,各家的实现方式也略有差异。大多数人都知道WebVie...
    Jannonx阅读 1,308评论 0 5
  • 随着H5性能的提升,在我们移动应用开发的过程中,我们会越来越多的在我们的App页面内嵌入H5页面,使得App变的更...
    Jensen95阅读 4,197评论 0 21
  • 先问男生们一个问题。 娇柔文静淑女完美的女孩,和泼辣有个性又爱又恨的女孩,你们喜欢哪一种? 估计每一个男生都会有不...
    小灰灰喜欢吃草莓阅读 795评论 0 3
  • 偶然的机会,看到了成都2017国际马拉松的报名链接,也许是年轻的心还有那么一点倔强,手指轻敲着完成了报名:全马,4...
    掬一口阅读 277评论 0 1
  • 夏建建随堂弟夏小乐搭渡船过河来到藕池镇上,不巧在派出所门前碰到花蝴蝶。花蝴蝶主动上前问道: "夏小乐,你带的...
    紫翼惠瑄阅读 376评论 0 0