Android与Web的通信方式
Native调用JS
Native端调用Web端,这个比较简单,JavaScript作为解释性语言,最大的一个特性就是可以随时随地地通过解释器执行一段JS代码,所以可以将拼接的JavaScript代码字符串,传入JS解析器执行就可以,JS解析器在这里就是webView。
loadUrl
Android 4.4之前只能用loadUrl来实现,并且无法执行回调:
java
String jsCode = String.format("window.showWebDialog('%s')", text);
webView.loadUrl("javascript: " + jsCode);
evaluateJavascript
Android 4.4之后提供了evaluateJavascript来执行JS代码,并且可以获取返回值执行回调:
java
String jsCode = String.format("window.showWebDialog('%s')", text);
webView.evaluateJavascript(jsCode, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
}
});
JS调用Native
拦截JS对话框
这种方式用的比较少,本质上也是通过解析URL来实现通信。JS有三种类型的对话框分别是alert()、confirm()、prompt(),可以重写WebViewClient对应的onJsAlert()、onJsConfirm()、onJsPrompt()三个方法,以onJsPrompt为例:
Web段代码
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="Generator" content="EditPlus®">
<meta name="Author" content=">
<meta name="Keywords" content="">
<meta name="Description" content="">
<title>Document</title>
<script>
function callAndroid3(){
// 调用prompt()
var result=prompt("js://webview?arg1=111&arg2=222");
alert("demo " + result);
}
</script>
</head>
<body>
<button type="button" id="button3" onclick="callAndroid3()">js调用安卓方式3</button>
</body>
</html>
Android段代码:
java
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// 根据协议的参数,判断是否是所需要的url
//假定传入进来的 message = "jsbridge://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)
Uri uri = Uri.parse(message);
if ( uri.getScheme().equals("jsbridge")) {
// 所以拦截url,下面JS开始调用Android需要的方法
if (uri.getAuthority().equals("webview")) {
//
// 执行JS所需要调用的逻辑
System.out.println("js调用了Android的方法");
// 可以在协议上带有参数并传递到Android上
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
//参数result:代表消息框的返回值(输入值)
result.confirm("js调用了Android的方法成功啦");
}
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
拦截请求的URL
类似拦截对话框,与Web段约定一个用来通信交互的URL,如:jsbridge://showToast?text=hello,Natvie重写WebViewClient的shouldOverrideUrlLoading方法进行拦截,原理和对话框拦截相似,这里就不贴代码了
addJavascriptInterface
这个方法会通过webView提供的接口,App将Native的相关接口注入到JS的Context(window)的对象中,一般来说这个对象内的方法名与Native相关方法名是相同的,Web端就可以直接在全局window下使用这个暴露的全局JS对象,进而调用原生端的方法。
Android(4.2+)提供了addJavascriptInterface注入:
java
webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");
class NativeBridge {
private Context ctx;
NativeBridge(Context ctx) {
this.ctx = ctx;
}
// 增加JS调用接口
@JavascriptInterface
public void showNativeDialog(String text) {
new AlertDialog.Builder(ctx).setMessage(text).create().show();
}
}
Web端调用:
javascript
window.NativeBridge.showNativeDialog('hello');
如何实现回调
前面已经讲完了Native、Web间双向通信的常用方法,但站在一端而言还是一个单向通信的过程 ,比如站在Web的角度:Web调用Native的方法,Native直接相关操作但无法将结果返回给Web,但实际使用中会经常需要将操作的结果返回,也就是JS回调。如何实现?
基于之前的单向通信,我们在一端调用的时候在参数中加一个**callbackId**标记对应的回调,对端接收到调用请求后,进行实际操作,如果带有callbackId,对端再进行一次调用,将结果、callbackId回传回来,这端根据callbackId匹配相应的回调,将结果传入执行就可以了。
有点抽象是不是?下面结合我们在上一节讲的通信方式来举例:
基于代码注入addJavascriptInterface的方式
javascript
// Web端代码:
<body>
<div>
<button id="showBtn">获取Native输入,以Web弹窗展现</button>
</div>
</body>
<script>
let id = 1;
// 根据id保存callback
const callbackMap = {};
// 使用JSSDK封装调用与Native通信的事件,避免过多的污染全局环境
window.JSSDK = {
// 获取Native端输入框value,带有回调
getNativeEditTextValue(callback) {
const callbackId = id++;
callbackMap[callbackId] = callback;
// 调用JSB方法,并将callbackId传入
window.NativeBridge.getNativeEditTextValue(callbackId);
},
// 接收Native端传来的callbackId
receiveMessage(callbackId, value) {
if (callbackMap[callbackId]) {
// 根据ID匹配callback,并执行
callbackMap[callbackId](value);
}
}
};
const showBtn = document.querySelector('#showBtn');
// 绑定按钮事件
showBtn.addEventListener('click', e => {
// 通过JSSDK调用,将回调函数传入
window.JSSDK.getNativeEditTextValue(value => window.alert('Natvie输入值:' + value));
});
</script>
代码很简单,简而言之:JS以键值对的形式保存回调,调用native方法时,将key传过去
java
// Android端代码
webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");
class NativeBridge {
private Context ctx;
NativeBridge(Context ctx) {
this.ctx = ctx;
}
// 获取Native端输入值
@JavascriptInterface
public void getNativeEditTextValue(int callbackId) {
MainActivity mainActivity = (MainActivity)ctx;
// 获取Native端输入框的value
String value = mainActivity.editText.getText().toString();
// 需要注入在Web执行的JS代码
String jsCode = String.format("window.JSSDK.receiveMessage(%s, '%s')", callbackId, value);
// 在UI线程中执行
mainActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
mainActivity.webView.evaluateJavascript(jsCode, null);
}
});
}
}
Native接收到callbackId后,在执行evaluateJavascript方法反向调用JS方法,将返回值传过去。
基于拦截请求的方式
同理,重写shouldOverrideUrlLoading拦截请求,解析URL参数,去执行Native方法,一样可以达到我们的目的。
为什么要提这种方法呢?在接下来的文章将会讲解基于shouldOverrideUrlLoading 的通信框架JsBridge的实现原理