在Android平台上要实现native 和 js的通信主要是通过WebViewClient 和 WebChromeClient 两个类来实现。

WebViewClient的作用是帮助WebView处理各种通知、事件请求,其主要的方法有:onLoadResource、onPageStart、onPageFinished、onReceiveError、shouldOverrideUrlLoading等;
WebChromeClient处理JS页面的事件响应,比如网页中的对话框、网页图标、网站标题、网页的加载进度等事件,对应的响应方法有onJsAlert、onJsConfirm、onJsConsole、onProgressChanged、onReceiveIcon、onReceiveTitle等。

要实现Java 和 JS 通信就要:

解决Java调JS;
JS调Java。
Java调用JS通过loadUrl和evaluateJavaScript两个方法。

通过webview.loadUrl(“javascript:alert(‘hello world’)”),可以在android平台将js代码注入到html页面,loadUrl方法可以直接调用js中定义的函数,也可以把android本地的assets目录下的js文件以字符串的形式注入到html页面中,但是这个注入时机一定要等到html页面加载完毕才能做,即在WebViewClient.onPageFinished的回调函数中调用,这样就相当于在html页面中直接引用了js资源文件。对于客户端来说,java调用js本质上是一个拼接js字符串的过程,但是调用loadUrl不能直接获取js函数的返回值,而要实现Java调用js函数后。

获取js函数的返回值可以使用webview.evaluateJavaScript方法,但是该方法只有在android4.4及以上的版本才可以使用。其他用法和loadUrl一致。

JS调用Java可以分为三种:对象映射、URL拦截、JS方法拦截。

对象映射是通过webview.addJavascriptInterface(new JSObject(), “javaObject”),这样可以js代码中可以直接调用javaObject对象,从而实现JS调用Java的功能,但是这个方法在android4.2以下会有安全漏洞,利用反射机制调用Android API getRuntime执行shell命令进行攻击,比如遍历sdcard、发送短信、安装木马APK等。
URL拦截是指在html页面通过iframe.src、window.open、documention.location或者href,这四种方法都可以在html页面中打开一个连接,从而会触发Java中的WebViewClient.shouldOverrideUrlLoading方法。例如在js中执行

153585008923

在shouldOverrrideUrlLoading中可以根据约定的协议格式(Scheme)和协议名(Authority)获取从JS中传输过来的数据(Data)。
在JS中调用alert、console、prompt、confirm等方法就会触发WebChromeClient的onJsAlert、onConsoleMessage、onJsPrompt、onJsConfirm方法的回调。比如在js中可以调用

181168094048

在onJsPrompt的message中可以获取prompt的内容,然后根据约定的协议格式可以获取数据。

JSBridge框架

为了解决JS和Native的通信问题,需要使用一个JSBridge框架(https://github.com/lzyzsd/JsBridge)用来负责H5和Java之间的通信,此时需要解决以下两个问题:

1)JS互相Java调用后如何回调,将responseData传递回去;

2)JS调用Java有三种方法,如果选择哪一种方法比较合适。

针对问题1,可以在java端和js端定义一个数据结构: Message={callbackId:xxx, handleName:xxx,responseData:xxx,responseId:xxx}。将回调函数保存在callbackId中,当JS或者Java处理完数据回调的时候再将保存在callbackId的回调函数存放在responseId,相应的回调的数据存放在responseData中,这样就能响应JS或者Java调用后的回调消息。
Js调用Java的方法虽然有三种,但是addJavaScriptInterface存在安全性问题一般不建议使用,JS中的alert、console方法都会在Html页面比较常用,confirm和prompt虽然不常用但是某些手机系统版本上会有对话框弹出,不通用,所以比较好的选择是url拦截,可以通过iframe.src触发shouldOverrideUrlLoading。

JsBridge框架的使用主要分为:

在H5页面加载完毕注入一个本地的js文件;
Java代码中注册BridgeHandler,用来处理JS发送过来的消息;
在本地注入的js文件中定义_handleMessageFromNative,用来接收java传递过来的消息;
因为客户端注入js是异步的,所以需要在js文件中注册Event监听器,成功后通知H5。

Native调用JS,例如通过
webview.loadUrl(
“javascript:WebViewJavascriptBridge._handleMessageFromNative(‘{
"callbackId":"JAVA_CB_2_559","data":"just data from java"
}’)”);

这样就可以调用JS的handleMessageFromNative方法,传递的数据格式是Message,callBackId响应js的回调,发送前会存储到HashMap中,js回调的时候根据JAVA_CB_2_559找到对应的的回调函数处理js的响应数据,具体流程如下:

3

Js调用Java,通过sendMessageQueue将传递的信息转换成
Message= {data: {…}, callbackId: “cb_1_1234”}

其中callbackId是js的回调函数。然后通过
iframe.src=’yy://return/_fetchQueue/[{“data”:”xxxx”,”callbackId”:”cb_1_4321”}]’,

触发shouldOverrideUrlLoading方法,java处理完js传递过来的data后,将回调函数cb_1_4321设置到
Message={responseId: cb_1_4321, responseData:XXX},

这样在js中就能处理回调函数。具体的流程图如下:

4

JsBridge框架提供两种Handler方法,registerHandler方法需要传入handler的名字,这样需要iOS、Android、H5三方约定这个名字,因为H5开发的的业务需求变化比较快,而且H5调用原生的方法也是随机的,所以每次都用registerHandler约定名字需要使用全局变量存放这些handler方法名不便于扩展,所以实际开发中使用defaultHanlder,H5调用原生方法的的时候约定一个cmd,即约定data={cmd:xxx,time:data:{…}},只要在原生代码中对cmd命令有对应的功能,那么H5页面就可以随时调用原生的方法。

JSBridge的改进建议,由于webview调用js方法的时候必须在主线程才能生效,所以偶然会出现java调用js失败。另外,Js调用Java偶尔也会失败,因为iframe机制不能保证每次都能触发shouldOverrideUrlLoading回调。

目前JSBridge采用的是url scheme的方式,如果不考虑Android4.2以下,iOS7以下,可以采用的交互,比如直接使用addJavaScriptInterface在JS页面注入一个Native对象,将之前触发u步骤变为使用这个Native对象向Native发送消息。这种方法只是一个可行的方案,实际使用过程中目前的JSBridge方案基本上满足业务需求了。