Android中JSBridge的原理与实现

Android中的JSBridge是H5与Native通信的桥梁,其作用是实现H5与Native间的双向通信。要实现H5与Native的双向通信,解决如下四个问题即可:
1、Java如何调用JavaScript
2、JavaScript如何调用Java
3、方法参数以及回调如何处理
4、通信的数据格式是怎么样的

下面从以上问题依次开始讨论:

1、Java如何调用JavaScript

在Android 中,Java与JavaScript的一切交互都是依托于WebView的。可通过如下方法来完成,其中function()即为JavaScript代码,来实现相应的具体H5层功能

WebView.loadUrl("javascript:function()");

2、JavaScript如何调用Java

要实现在JavaScript中调用Java,就需要在JavaScript中有触发Java方法的对象和方法。在JavaScript中,当调用window对象的prompt方法时,会触发Java中的WebChromeClient对象的onJsPrompt方法,因此可以利用这个机制来实现js调用native的代码

3、方法参数以及回调处理

任何IPC通信都涉及到参数序列化的问题,同理,Java与JavaScript之间只能传递基础类型(包括基本类型和字符串),不包括其他对象或者函数。所以可以采用json格式来传递数据。JavaScript与Java相互调用不能直接获取返回值,只能通过回调的方式来获取返回结果。

4、通信的数据格式

Java与JavaScript通信需要遵循一定的通信协议,可以仿照HTTPS协议来将此协议定义为jsbridge协议:

jsbridge://className:port/methodName?jsonObj

当js调用native功能时,应当指定native层要完成某个功能调用的类名(className)和方法名(methodName),以及js传递过来的参数(jsonObj)。port值是指当native需要将操作结果返回给js时,在js中定义一个callback,并将这个callback存储在指定的位置上,这个port就定义了callback的存储位置。

jsbridge原理图.png

JSBridge的具体工作流程图如上所示:

1、js触发调用native层的行为

JSBridge.call(className, methodName, params, callback);

将call方法中的参数组合成jsbridge协议格式的url。然后通过prompt方法将url传递到native层。

window.prompt(url);

2、通过WebChromeClient来获取js传递过来的url.

public class JSBridgeWebChromeClient extends WebChromeClient {
    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        result.confirm(JSBridge.callJava(view, message));
        return true;
    }
}

3、JSBridge类管理暴露给前端方法,前端调用的方法应该在此类中注册才可使用。register的实现是从Map中查找key是否存在,不存在则反射取得对应class中的所有方法,具体方法是在BridgeImpl中定义的,方法包括三个参数分别为WebView、JSONObject、CallBack。如果满足条件,则将所有满足条件的方法put到map中。

private static Map<String, HashMap<String, Method>> exposedMethods = new HashMap<>();
public static void register(String exposedName, Class<? extends IBridge> clazz) {
        if (!exposedMethods.containsKey(exposedName)) {
            try {
                exposedMethods.put(exposedName, getAllMethod(clazz));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

JSBridge类中的callJava方法就是将js传递过来的URL解析,根据将要调用的类名从刚刚建立的Map中找出,根据方法名调用具体的方法,并将解析出的三个参数传递进去。

public static String callJava(WebView webView, String uriString) {
        String methodName = "";
        String className = "";
        String param = "{}";
        String port = "";
        if (!TextUtils.isEmpty(uriString) && uriString.startsWith("JSBridge")) {
            Uri uri = Uri.parse(uriString);
            className = uri.getHost();
            param = uri.getQuery();
            port = uri.getPort() + "";
            String path = uri.getPath();
            if (!TextUtils.isEmpty(path)) {
                methodName = path.replace("/", "");
            }
        }


        if (exposedMethods.containsKey(className)) {
            HashMap<String, Method> methodHashMap = exposedMethods.get(className);

            if (methodHashMap != null && methodHashMap.size() != 0 && methodHashMap.containsKey(methodName)) {
                Method method = methodHashMap.get(methodName);
                if (method != null) {
                    try {
                        method.invoke(null, webView, new JSONObject(param), new Callback(webView, port));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return null;
    }

4、CallBack类是用来回调js中回调方法的Java对应类。Java层处理好的返回结果是通过CallBack类来实现的。在这个回调类中传递的参数是JSONObject(返回结果)、WebView和port,port应与js传递过来的port相对应。

private static Handler mHandler = new Handler(Looper.getMainLooper());
    private static final String CALLBACK_JS_FORMAT = "javascript:JSBridge.onFinish('%s', %s);";
    private String mPort;
    private WeakReference<WebView> mWebViewRef;

    public Callback(WebView view, String port) {
        mWebViewRef = new WeakReference<>(view);
        mPort = port;
    }
    public void apply(JSONObject jsonObject) {
        final String execJs = String.format(CALLBACK_JS_FORMAT, mPort, String.valueOf(jsonObject));
        if (mWebViewRef != null && mWebViewRef.get() != null) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    mWebViewRef.get().loadUrl(execJs);
                }
            });
        }
    }

5、JSBridgeImpl类中定义所有暴露给前端的方法的具体实现。本文以showToast为例来通过native代码显示toast,并给出js的回调函数,返回一个JSONObject对象。

public static void showToast(WebView webView, JSONObject param, final Callback callback) {
        String message = param.optString("msg");
        Toast.makeText(webView.getContext(), message, Toast.LENGTH_SHORT).show();
        if (null != callback) {
            try {
                JSONObject object = new JSONObject();
                object.put("key", "value");
                object.put("key1", "value1");
                callback.apply(getJSONObject(0, "ok", object));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

6、在js中通过

JSBridge.call('bridge','showToast',{'msg': 'Hello'}, function(res){alert(JSON.stringi
    fy(res))})"

即可调用在Java层定义的showToast方法,调用前不要忘记在java层的JSBridge中注册该方法。

JSBridge.register("bridge", BridgeImpl.class);

二、总结:

JSBridge的基本原理为:
H5->通过某种方式触发一个url->Native捕获到url,进行分析->原生做处理->Native调用H5的JSBridge对象传递回调。如下图

h5与原生页面通过jsbridge交互.png

三、安全性:

1、Android4.2以下,addJavascriptInterface方法有安全漏洞,js代码可以获取到Java层的运行时对象,来伪造当前用户执行恶意代码。
2、ios7以下,JavaScript无法调用native代码。
3、通过js声明的对象,是通过loadUrl注入到页面中的,所以这个对象是js对象,而不是Java对象,没有getClass等Object方法,因此也无法获得Runtime对象,避免了恶意代码的注入。
4、JSBridge采用URL解析的交互方式,是一套成熟的解决方案,便于拓展,无重大安全性问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容