在Android项目中,经常会用到在Webview
中js与java的互相调用。当然,Google官方为WebView提供了对应的方法,通过WebView.addJavascriptInterface()
方法来注入java对象来实现。但是这个方法有一个问题,在于4.2版本之前有漏洞。
今天就来介绍另外一种js与java的互相调用的实现方式。当然这种方式可能网上已经有一些教程,我这里介绍的还是相对完善的一套解决方案。
实现这套方案的条件:要求开发人员同时熟悉java与js
首先将js代码注入到webview的页面中,然后通过修改window.location.href
来向java传递参数,java端通过监听页面url的改变来识别js端传递的参数.然后再经解析数据实现java方法的调用.
java通过webView.loadUrl("javascript:"+jsCode);
来实现js代码的调用
window.UAPPJSBridge ={
// 不包含回调方法的参数调用
share:function(){
var arg = {title:"i am title"};
//修改href传递参数
window.location.href="app://share?"+JSON.stringify(arg);
}
}
定义了一个全局对象来定义一些java端需要调用的方法,比如这里的share。关键的地方在于传递的参数格式app://share
,很像http
协议对吧,其实就是自己定义了一个协议开头而已。
参数格式说明:
app://share?+JSON.stringify(arg)
app://
: 协议名称,自己可以随便定义,只要方便解析
share
: 方法名称,用于指定需要执行的java方法
?+JSON.stringify(arg)
: json格式参数
// 用于保存所有回调函数
window._callbacks = {};
// 回调Id生成器
window._callbacks_id = 0;
// js Java 桥接器
window.UAPPJSBridge ={
/**
微信分享
@param message:{url:页面地址string, image:缩略图地址string,title:分享标题string,description:分享内容string }
@param success:function(errCode,errStr); errCode:错误码number,errStr:错误描述string
@param error:function(errCode,errStr); errCode:错误码number,errStr:错误描述string
*/
wxshare:function(message,success,error){
if(!message || !success || !error){
console.log("message or success or error is empty.");
return;
}
//== start =========================
//这里是处理js调用java之后的回调,只要是需要回调都要像下面这样写
// 生成 Callback Id
var id = _callbacks_id++;
// 设置回调函数
_callbacks[id]={
success:success,
error:error
};
//== end =========================
//解析Message
// 通过修改location.href 给WebView传递参数
var arg = {
message:message,
// 回调函数的id,java端通过callback_id来调用指定回调函数
callback_id:id
};
window.location.href= "app://wxshare?"+JSON.stringify(arg);
return id;
}
};
思路: 当java端调用wxshare方法时会注册一组回调函数:success
,error
,然后将callback_id
传递给java端,当java端执行完成后通过callback_id
调用js端的回调函数
_callback
: 保存回调函数 _callback_id
: 回调函数id生成器
private class BridgeWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(final WebView view, String url) {
//在这里处理js传递过来的参数 url
//比如 url = app://share
if (mBridge.handleJsArgument(url, new WebViewBridge.handleJsCallback() {
@Override
public void onHandleJs(String func, JSONObject argument) {
//func 是js传递过来的方法名,比如 share
//argument : 参数
}
})) {
//返回true代表请求已经被处理,为了防止传过来的参数被webview加载,直接返回true
return true;
}
view.loadUrl(url);
return true;
}
@Override
public void onLoadResource(WebView view, String url) {
super.onLoadResource(view, url);
//此处需要注入js代码
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
//这里需要触发js注入完成事件
}
}
设置WebViewClient:
webView.setWebClient(new BridgeWebViewClient());
上面的代码定义了一个BridgeWebViewClient类,关键在sholdOverrideUrlLoading方法中,通过mBridge.handleJsArgument
方法来解析url,如果是js调用就会阻断页面的加载。
下面是 handleJsArgument方法细节,这里的代码可以不用细看,只是用来解析参数的,解析完成后,在回调接口中返回方法名称和JSONObject格式的参数.
@Override
public boolean handleJsArgument(String url, handleJsCallback jscallback) {
if (url == null || !url.startsWith(UAPP_PRE)) {
return false;
}
//解析参数
String func = parseFuncName(url);
jscallback.onHandleJs(func, parseJsArgumentFromJsonStr(url));
return true;
}
@Override
public JSONObject parseJsArgumentFromJsonStr(String json) {
if (json == null || json.equals("") || !json.startsWith(UAPP_PRE) || json.indexOf("?") == -1) {
return null;
}
json = json.substring(json.indexOf("?") + 1);
JSONObject jsonObject=null;
try {
jsonObject = new JSONObject(json);
} catch (JSONException e) {
e.printStackTrace();
} catch (Exception e){
e.printStackTrace();
}
return jsonObject;
}
public String parseFuncName(String url) {
if (url == null || url.equals("")) {
return "";
}
String a = UAPP_PRE + "\\w+\\??";
String result = "";
Pattern pattern = Pattern.compile(a);
Matcher matcher = pattern.matcher(url);
if (matcher.find()) {
result = matcher.group(0);
int start = 0, end = 0;
start = result.indexOf("/") + 2;
start = start >= result.length() ? result.length() : start;
if (result.indexOf("?") == -1) {
end = result.length();
} else {
end = result.indexOf("?");
}
return result.substring(start, end);
}
return result;
}
java调用js的方法,相当简单:
String jsCode = "(function(){ /*js code here*/ })()";
webview.loadUrl("javascript:" + jsCode);
注意:要把js代码用自执行函数包裹起来
利用这种方法可以把任意代码注入到页面中
注意: 当页面刚加载时就需要把 2.1中的js代码注入到页面中,但是有时会出现页面加载完成,但是注入的代码没有执行的现象,应该是因为js没有及时加载而页面先加载完成。要解决这个问题就需要在页面加载完成时触发一个事件比如jsBridgeReady
,js端通过监听jsBridgeReady事件来调用java方法。那么总体流程如下:
加载页面->注入js代码->页面加载完成->触发js注入完成事件
代码:
在BridgeWebClient中添加逻辑: 1,加载页面时注入js代码: 2,加载完成后触发加载完成事件
@Override
public void onLoadResource(WebView view, String url) {
// 注入桥接代码
// 这里通过读取js文件获得js代码
if (mFileName != null) {
view.loadUrl(mBridge.createBaseJs(mContext, mFileName));
}
super.onLoadResource(view, url);
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
// 触发注入完成事件,用于解决 注入的代码没有及时加载成功导致无法调用的问题
view.loadUrl("(function(){if(window.UAPPJSBridgeReady){window.UAPPJSBridgeReady();}})()");
}
如果你也想从文件读取js代码,记得把读取的代码压缩一下,把注释和换行删除,不然很可能会报错
js端调用java注入的代码:
//判断代码是否注入完成
if(window.UAPPJSBridge){
//调用注入的方法
window.UAPPJSBridge.wxshare(message,success,error);
}else{
//监听注入完成的事件
window.onBridgeLoaded=function(){
if(window.UAPPJSBridge){
window.UAPPJSBridge.wxshare(message,success,error);
}
}
}
以上就是Android端js与java互相调用的方法,Good Luck!