Android与JavaScript相互调用桥梁JsBridge

什么是JSBridge

JSBridge:听其取名就是 JSNative 之间的桥梁,而实际上 JSBridge 确实是 JS 和 Native 之前的一种通信方式。简单的说,JSBridge 就是定义 Native 和 JS 的通信, Native 只通过一个固定的桥对象调用 JS, JS 也只通过固定的桥对象调用 Native。JSBridge 另一个叫法及大家熟知的 Hybrid app 技术。

项目地址:https://github.com/lzyzsd/JsBridge

整个库的结构也比较简单:一个用来注入的 JS 文件,一个自定义的 Webview(包括webViewClient),以及作为载体的 BridgeHandler

为什么要使用JsBridge

大多数人都知道 WebView 存在一个漏洞,见 WebView 中接口隐患与手机挂马利用,虽然该漏洞已经在 Android 4.2 上修复了,即使用@JavascriptInterface 代替 addJavascriptInterface,但是由于兼容性和安全性问题,基本上我们不会再利用 Android 系统为我们提供的addJavascriptInterface 方法或者 @JavascriptInterface 注解来实现,所以我们只能另辟蹊径,去寻找既安全,又能实现兼容 Android 各个版本的方案。

JsBridge的使用

JsBridge库集成

  1. 使用作者推荐方式

    repositories {  
    maven {url "https://jitpack.io"}  
    }  
    dependencies {  
    compile 'com.github.lzyzsd:jsbridge:1.0.4'
    }
    
  2. 使用源码集成: 下载源码,将源码拷贝至自己的工程内,作为工程的文件。

  3. 自定义Module: 下载源码,新建 Module,将源码导入 Module,并将项目依赖自定义的 Module

使用JsBridge库

提供操作给Js调用

webView.registerHandler("submitFromWeb", new BridgeHandler(){
    @Override
    public void handler(String data, CallBackFunction function){
        function.onCallBack("submitFrom web exe, response data from java");
    }
}

JS 如果需要调用 Java 提供的方法时候,则需要调用这个 Handler,而在注册时参数 submitFromWeb 将作为 JS 调用时使用的 Key 值。调用方式如下:

WebViewJavascriptBridge.callHandler(
    'submitFromWeb',
    {'param':str1},
    function(responseData){
        //这里打印的应该是上面Handler实现方法中的callback的入参:submitFrom web exe, response data from java
        document.getElementById("show").innerHTML = "response data from java, data = "+responseData
    }
)

另外,库也提供了一个简单的没有回调的调用方式:

webView.setDefaultHandler(new DefaultHandler());

JS 调用的方式也可以简化为:

WebViewJavascriptBridge.send(
    data,
    function(responseData){
        //java中DefaultHandler所实现的方法中callback所定义的入参
    }
)

提供操作给Java调用

注册方法与 Java 雷同:

WebViewJavascriptBridge.registerHandler("functionInJs", function(data, responseCallback) {
    document.getElementById("show").innerHTML = ("data from Java: = " + data);
    var responseData = "Javascript Says Right back aka!";
    responseCallback(responseData);
});

Java 调用 Handler 时,也跟 JS 一样:

webView.callHandler("functionInJs", new Gson().toJson(user),
    new CallBackFunction(){
        @Override
        public void onCallBack(String data){
        }
    }
);

同样的,在 JS 中也可以注册默认的 Handler,以方便 Java 调用时,通过 send 方法发送数据

bridge.init(function(message, responseCallback) {
    console.log('JS got a message', message);
    var data = {
        'Javascript Responds': 'Wee!'
    };
    console.log('JS responding with', data);
    responseCallback(data);
});

java 调用 send 方法:

webView.send("hello");

JsBridge使用原理

在讲 JsBridge 的实现之前,首先要讲下各个文件的作用:

WebViewJavascriptBridge.js       被注入到各个页面的 js 文件;提供初始化,注册 Handler,调用 Handler 等方法。

WebViewJavascriptBridge.java

bridge 接口文件,定义了发送信息的方法,由 BridgeWebView 来实现。

BridgeWebView.java

WebView 的子类,提供了注册 Handler,调用 Handler 等方法。

BridgeWebViewClient.java

WebViewClient 的子类,重写了ShouldOverrideUrlLoadingonPageFinishonPageStart 等方法。

BridgeHandler.java

作为 JavaJs 交互的载体。Java & Js 通过 Handler 的名称来找到响应的 Handler 来操作。

DefaultBridgeHandler.java

BridgeHandler 的子类,不做任何操作。仅为 Java 提供默认的接收数据的 Handler

CallBackFunction.java

回调函数,Handler 处理完成后,用来给 Js 发送消息。

Message.java

消息对象,用来封装与 js 交互时的 json 数据,callidresponseid 等。

BridgeUtil.java

工具类,提供从 Url 中提取数据,获取回调方法,注入 js 等方法。

JsBridge调用过程

  1. Native 初始化 webview,注册 Handler;加载页面完成后,将 WebViewJavascriptBridge.js 文件注入页面。查询消息队列是否有信息需要被接收。
  2. H5 页面初始化,注册 Handler,查询消息队列是否有信息需要别接收。
  3. 用户操作,H5 调用本地功能:Js 将消息内容放在 sendMessageQueue 中,并设置 iframesrcyy://__QUEUE_MESSAGE__/
  4. Webview 设置的 WebViewClient 拦截到约定 url,调用Webview的刷新消息队列的方法 flushMessageQueue,此方法就是加载了一个url:javascript:WebViewJavascriptBridge._fetchQueue();, 这也是 Js 中定义的方法,另外定义了一个回调;回调方法主要做了两件事:①判断 Native 是否为此返回数据保有响应回调操作,若有,则执行,若没有,则为判断 callId,不为空时为这个 callId 初始化一个回调。②通过 handlername 判断是否为默认的 Handler 还是自定义的 Handler,调用相应 Handlerhandler 方法,入参为消息数据内容和第一步中定义的回调。【这段较为难消化,需要阅读代码来理解】
  5. Js_fetchQueue 设置了 iframesrc,内容为:yy://return/_fetchQueue/+ 第二步中放入 sendMessageQueue 中的消息内容。
  6. WebViewClient 拦截到 urlyy://return/,调用 WebViewhandlerReturnData 方法;通过 url 中定义的方法名,找到第四个步骤中定义的回调,并调用。回调方法走完后,删除此回调方法。
  7. 如果 Js 在调用 Handler 的时候设置了回调方法,也就是在第四步骤中的含有 callId,就会调用 queueMessage 的方法,然后往下就是走 NativeJs 发送消息的步骤。 Ps: NativeJs 发送消息的步骤跟上述从第三步骤到第七步骤完全相同,只不过 NativeJs 对象调换位置即可。

JsBridge的核心

JsBridge 之所以能实现 NativeJs 相互调用的功能,其核心实现其实就是:

  1. 拦截 Url

  2. load url("javascript:js_method()");

先说第二点,Native 调用 Js,通过加载以 javascript: 开头的 url 即可实现调用 Js 的方法。这个很好理解,在 web 中也是通过这种方式调用 Js 的方法的。

然后细说下第一点的实现:

  1. body 中添加一个不可见的 iframe 元素。通过拦截 url 的方法来执行相应的操作,但是页面本身不能跳转,所以改变一个不可见的iframesrc 就可以让 webview 拦截到 url,而用户是无感知的。
  2. 拦截 url。通过 shouldOverrideUrlLoading 来拦截约定规则的 Url,再做具体操作。 Ps: 添加 iframeH5 自身可实现的,但是如果 H5 来实现的话,需要每个页面实现,且耦合较高;因此放在库里,通过加载完成注入的方式,则会降低耦合