安卓原生与网页js的通信
前言
前一段时间在搞安卓的Hybird App,涉及到了安卓原生与网页js的通信交互。趁现在年末有空就做一下总结。
1、url schema
通过自定义协议来实现原生端与js端进行交互,自定义协议的格式如下:jsbridge://(method)?(params),即jsbridge://+方法名+参数。下面简单看一下这个url:jsbridge://showToast?message=text,showToast就是需要调用到的原生端方法,方法名就是showToast,参数则是message=text,key是message,value是text。这种协议类似于http协议,js端发出url请求,原生端进行拦截,判断是否是自定义请求协议,是则拦截下来,解析url的内容,然后进行相应的操作并返回数据。下面我们来看一下代码
<body>
<input id="input" type="text" placeholder="请输入">
<button id="btn">调用java方法</button>
</body>
<script>
var input = document.getElementById('input')
var btn = document.getElementById('btn')
btn.addEventListener('click', function () {
var text = input.value.trim();
alert("jsbridge://showToast?message=" + text)
})
</script>
private void initView() {
webView = findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient());
webView.setWebChromeClient(new WebChromeClient(){
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
if(!message.startsWith("jsbridge://")){
return super.onJsAlert(view, url, message, result);
}
String[] split = message.split("=");
showToast(split[1]);
result.confirm();
return true;
}
});
webView.loadUrl("http://192.168.7.11:8080?time="+new Date().getTime());
}
public void showToast(String message){
Toast.makeText(this,message,Toast.LENGTH_SHORT).show();
}
从上面可以看出来,我是通过js端自带window.alert来发出url请求的,在原生端中,我通过重写onJsAlert(WebView view, String url, String message, JsResult result) 这个方法来拦截window.alert所发出来的请求,然后通过判断message是否以jsbridge://开头,是则拦截,并实现自己的处理逻辑,不是则直接放行,默认处理。不过这种方式基本只会用在4.0以下的手机,而现在4.0以下的手机基本已经灭绝了,所以这种方式基本不会被用到。
2、往浏览器中注入对象
先上代码,在慢慢解释
<body>
<div>jsbridge</div>
<input id="input" type="text" placeholder="请输入">
<button id="btn">调用java方法</button>
<div id="msg"></div>
</body>
<script>
var id = 1;
var map = {}
window.jsSdk = {
showToast(message, fun) {
if (fun) {
var channelId = id++;
map[channelId] = fun
}
window.android.showToast(message, channelId)
},
callBack(message, channelId) {
if (map[channelId]) {
map[channelId](message)
}
}
}
var input = document.getElementById('input')
var btn = document.getElementById('btn')
var msg = document.getElementById('msg')
btn.addEventListener('click', function () {
var text = input.value.trim();
jsSdk.showToast("js调用java方法:" + text, function (message) {
msg.innerHTML = message
})
})
</script>
private void initView() {
webView = findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient());
webView.setWebChromeClient(new WebChromeClient());
webView.addJavascriptInterface(new JsApi(),"android");
webView.loadUrl("http://192.168.7.11:8080/jsbridge.html?time="+new Date().getTime());
}
private class JsApi{
@JavascriptInterface
public void showToast(String message, final int channelId){
Toast.makeText(JsbridgeActivity.this,message+channelId,Toast.LENGTH_SHORT).show();
runOnUiThread(new Runnable() {
@Override
public void run() {
String jsCode = "javascript:jsSdk.callBack('调用成功',"+channelId+")";
webView.evaluateJavascript(jsCode,null);
}
});
}
}
首先看java代码,webView.addJavascriptInterface(new JsApi(),“android”)这个就是往webview中注入一个对象,对象名就叫做android,JsApi这类里面的方法就是提供给js端调用的方法,需要注意的是需要给js端调用方法中必须有@JavascriptInterface这个注解,否则会报错。然后再看看前端代码,window.android.showToast(message, channelId)就是调用原生端的方法,这里为什么要封装一个jsSdk?这是为了方便调用了java方法后获取调用后的结果。我在每次调用原生代码的时候,携带一个id参数,并用key-value的形式保存对应的回调方法,然后原生代码执行完后调用jsSdk中的callBack,把结果和id传递给callBack这个函数,callBack函数在根据id找出对应的回调函数,然后执行。
3、原生端调用js
<script>
function showMessage(message) {
alert(message)
return 'success'
}
</script>
public void javacalljs(View view){
String jsCode = "javascript:showMessage('java调用js的代码')";
webView.evaluateJavascript(jsCode, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Toast.makeText(UrlSchemaActivity.this,value,Toast.LENGTH_SHORT).show();
}
});
}
从上面代码可以看见,js端的showMessage方法必须是挂载到window对象下的,否则原生端是调用不到的,在原生端中webView.evaluateJavascript就相当于js中的eval,不过webView.evaluateJavascript执行的脚本格式必须符合要求,就是 javascript:js代码,然后webView.evaluateJavascript得第二个参数是一个回调函数,如果原生端调用的是js端的方法,并且该方法有返回值,则可以使用该回调函数接收返回的值。
总结
最后,其实有很多的第三方库已经给我们封装好了原生端与js端的通信桥梁,比如JsBridge和DSBridge-Android,这些用起来都比较的舒服。