WebView中实现js与java互相调用

在Android项目中,经常会用到在Webview中js与java的互相调用。当然,Google官方为WebView提供了对应的方法,通过WebView.addJavascriptInterface()方法来注入java对象来实现。但是这个方法有一个问题,在于4.2版本之前有漏洞。

今天就来介绍另外一种js与java的互相调用的实现方式。当然这种方式可能网上已经有一些教程,我这里介绍的还是相对完善的一套解决方案。

实现这套方案的条件:要求开发人员同时熟悉java与js

1.整体思路:

  • js调用java

首先将js代码注入到webview的页面中,然后通过修改window.location.href来向java传递参数,java端通过监听页面url的改变来识别js端传递的参数.然后再经解析数据实现java方法的调用.

  • java调用js

java通过webView.loadUrl("javascript:"+jsCode);来实现js代码的调用

2.详解:

2.1 js调用java的实现

2.1.1 js调用Java最简单版本,不支持callback

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格式参数

2.1.1 js调用java高端版支持callback

// 用于保存所有回调函数
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生成器

2.2 java 端代码

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;
}

2.2 java调用js的实现

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!

你可能感兴趣的:(android)