Android之WebView Android调用JS方法 JS调用Android方法 【三】

老早之前就想总结下Webview相关的知识点了,因为互联网大潮中,很多APP都会使用到Webview,像那些不计其数的电商APP,无一例外的使用Webview;或者一些非电商APP中的像广告页面,注册协议页面都会用到;最后因为一些事情拖到现在才做,感觉事情真不能拖,越往后推越做不了,罪过罪过。

怎么总结Webview呢

1.简单介绍

2.WebView/WebViewClient/WebChromeClient api介绍

3.简单使用

4.JS调用Android本地

5.Android调用JS方法

6.缓存处理及性能优化

7.webview使用注意点


webview系列文章

Android之WebView/WebViewClient/WebChromeClient简介 API详述 【一】

Android之WebView/WebViewClient/WebChromeClient 使用样例 【二】

Android之WebView Android调用JS方法 JS调用Android方法 【三】

Android之WebView 缓存处理 性能优化【四】

Android之WebView 使用注意点 JS注入漏洞问题 内存优化【五】


4.JS调用Android

方法主要有三种 

1.通过Webview的addJavaScriptInterface方法注入java对象

2.通过WebViewClient的shouldOverrideUrlLoading方法拦截url,这个是用的最普遍的

3.通过WebChromeClient 的onJsAlert、onJsConfirm、onJsPrompt 提示接口进行相关操作


我们先看第一种 addJavaScriptInterface 方法

* 先编写Android端

1.布局编写

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white">

    <WebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="280dp"/>

    <TextView
        android:id="@+id/result"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_marginTop="20dp"
        android:textSize="18sp"
        android:textColor="@color/colorAccent"/>

</LinearLayout>

布局很简单,就是一个webview和一个接受js返回值的textview

2.既然是注入java对象,那就编写这个对象

public class JSInterface {

    private String TAG = "JSInterface";

    // 定义JS需要调用的方法
    // 被JS调用的方法必须加入@JavascriptInterface注解
    @JavascriptInterface
    public void setValue(String value) {
        Log.e("JSInterface","setValue value="+value);
    }
}

3.注入java对象,也就是添加js接口

private void initWebview() {
    WebSettings webSettings = mWebView.getSettings();
    // 设置与Js交互的权限
    webSettings.setJavaScriptEnabled(true);
    // 通过addJavascriptInterface()将Java对象映射到JS对象
    //参数1:java对象
    //参数2:Java对象在js里的对象名,也就是这个对象在js里叫啥
    mWebView.addJavascriptInterface(new JSInterface(), "mango");

    mWebView.loadUrl("file:///android_asset/javascript_android_2.html");

}

到这里客户端就写完了。

* 接下来我们编写HTML部分

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebView</title>

<style type="text/css">

        body{
        background-color: rgb(210, 154, 28)
        }

        .btn{
        line-height: 40px;
        width: 80px;
        margin: 10px;
        background: rgb(238, 244, 233)
        }
</style>
</head>

<body>

<h1 >JS调用Android</h1>
<div >
<span >请输入要传递给Android的值</span>
<input type="text" id="input"/>
</div>

<div id="btn" class="btn" >
<span >click</span>
</div>

<!-- javascript逻辑就是点击button将输入框的值返回给android客户端 -->
<script type="text/javascript">

        var btnEle = document.getElementById('btn');
        var inputEle = document.getElementById('input');
        // 给按钮添加一个click事件监听
        btnEle.addEventListener("click",function () {

          var value = inputEle.value;
            / / 在页面show一下输入的值
            // alert(value);
            // 先判断android客户端传入的java对象存不存在
           if(window.mango){
            mango.setValue(value);
           }else{
            alert("mango not found");
           }
        });


</script>

</body>

具体逻辑都注释了,我们先把这个html在浏览器运行看看,完全符合最后那个判断。



* 然后我们运行客户端



我这用的是模拟器,在输入框输入值后,看看在JsInterface类的setValue方法打印的log

06-11 21:58:53.863 3770-3878/? D/JSInterface: setValue value=2552

这里可以得出:

1.android中原生方法成功被JS调用了

2.这个被调用的方法所在线程并不是在主线程,看上面这个日志 3770-3878 ,3770表示主线程id,3878表示子线程id;这就表示它是一个非UI线程,不能在这里更新UI.要更新UI需要通过接口或者Handler,如下

@JavascriptInterface
public void setValue(String value) {
    Log.d("JSInterface","setValue value="+value);
    Message msg = mHandler.obtainMessage();
    msg.what = UPDATE_TEXT;
    msg.obj = value;
    mHandler.sendMessage(msg);
}
Handler mHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        int tag = msg.what ;
        if (UPDATE_TEXT == tag) {
            String result = (String) msg.obj;
            mResult.setText(result);
        }
    }
};


这样就实现了android 和 JS 交互,但是实现功能的同时也带了安全问题,通过注入的 Java 类作为桥梁,JS 就可以利用这个漏洞,具体什么我们放在最后总结。


看第二种方法 shouldOverrideUrlLoading

工作逻辑是

1.通过shouldOverrideUrlLoading方法拦截URL,

2.解析url

3.根据约定好的规则去匹配解析后的url,如果规则相同,就进行操作

先写好html文件

<!DOCTYPE html>
<html lang="en" dir="ltr">

<head>
    <meta charset="utf-8">
    <title>webview</title>

    <style media="screen">

            body{
                background-color: rgb(224, 112, 57);
            }
        </style>
</head>


<body>

<h1>Js调用Android二</h1>

<button type="button" id="btn" onclick="callAndroid()">点击调用Android方法</button>

<script type="text/javascript">
            function callAndroid(){
                document.location = "js://webview?id=1&value=2";
            }
        </script>

</body>


</html>

再写客户端

WebSettings webSettings = mWebView.getSettings();
        // 设置与Js交互的权限
        webSettings.setJavaScriptEnabled(true);


        mWebView.loadUrl("file:///android_asset/jscallandroid.html");
        // 复写WebViewClient类的shouldOverrideUrlLoading方法
        // 设置允许JS弹窗
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
        mWebView.setWebViewClient(new WebViewClient() {
                @Override
                public boolean shouldOverrideUrlLoading(WebView view, String url) {

                    Log.e(TAG,"shouldOverrideUrlLoading url="+url);
                    // 根据协议的参数,判断是否是所需要的url
                    // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
                    //假定传入进来的 url = "js://webview?id=1&value=2"(同时也是约定好的需要拦截的)
                    Uri uri = Uri.parse(url);
                    // 如果url的协议 = 预先约定的 js 协议
                    // 就解析往下解析参数
                    if ( uri.getScheme().equals("js")) {

                        // 如果 authority  = 预先约定协议里的 webview,即代表都符合约定的协议
                        // 所以拦截url,下面JS开始调用Android需要的方法
                        if (uri.getAuthority().equals("webview")) {

                            // 执行JS所需要调用的逻辑
                            Toast.makeText(mContext,"js调用了Android的方法",Toast.LENGTH_LONG).show();
                            // 可以在协议上带有参数并传递到Android上
                            HashMap<String, String> params = new HashMap<>();
                            Set<String> collection = uri.getQueryParameterNames();

                            //如果js需要返回值,就从这里再次执行js方法把结果返回回去
//                            mWebView.loadUrl("javascript:returnResult(" + "result" + ")");
                        }
                        return true;
                    }
                    return super.shouldOverrideUrlLoading(view, url);
                }
            }
        );


看看打印的日志

06-11 22:57:29.033 6256-6256/? E/AndroidCallJSFrag: shouldOverrideUrlLoading url=js://webview?id=1&value=2

这个url与js里的一样,这就是双方约定好,然后一个一个解析url结构,然后就可以进行相关操作;并且看到6256-6256,说明这是在UI线程回调。

这种方法不会出现第一种方法出现的漏洞。


再看第三种方法  通过WebChromeClient 的onJsAlert、onJsConfirm、onJsPrompt 

当js里调用alert(),confirm(),prompt()方法的时候,我们可以通过WebChromClient类的onJsAlert、onJsConfirm、onJsPrompt三个方法去拦截消息,然后解析。

这三个js方法有点不同,

alert():弹出一个警告框,没有返回值

confirm():弹出确认框,有两个状态返回值(ok/no)

prompt():弹出输入框,可设置任意返回值,这就是最灵活的,也是我们最常用的

我们先写html

<!DOCTYPE html>
<html lang="en" dir="ltr">
    <head>
        <meta charset="utf-8">
        <title>WebView</title>

        <style media="screen">

            body{
                background-color: rgb(224, 112, 57);
            }
        </style>

    </head>

    <body>

        <h1>Js调用Android三</h1>

        <button type="button" id="btn" onclick="clickPrompt()">clickPrompt调用android</button>

        <script type="text/javascript">

            function clickPrompt(){
                // 通过prompt调用android,获取返回值result
                var result = prompt("js://webview?id=1&value=2");
                // 将获取的android返回值通过alert弹出框显示出来
                alert(result);
            }

        </script>
    </body>

</html>


再写android客户端

WebSettings webSettings = mWebView.getSettings();
// 设置与Js交互的权限
webSettings.setJavaScriptEnabled(true);
mWebView.loadUrl("file:///android_asset/jscallandroid-2.html");
// 复写WebViewClient类的shouldOverrideUrlLoading方法
// 设置允许JS弹窗
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
mWebView.setWebChromeClient(new WebChromeClient(){

    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
        Log.d(TAG,"onJsAlert");
        return super.onJsAlert(view, url, message, result);
    }

    @Override
    public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
        return super.onJsConfirm(view, url, message, result);
    }

    /**
     * 拦截js的prompt弹出框
     * @param view
     * @param url
     * @param message
     *          js里promt()方法的参数内容,不是url
     * @param defaultValue
     * @param result
     *          代表输入框的值
     * @return
     */
    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        Log.d(TAG,"onJsPrompt message="+message);
        return super.onJsPrompt(view, url, message, defaultValue, result);
    }

});

我们如果在onJsPrompt不做处理,让webview自己处理看看什么情况



跟js代码里的逻辑是一样的,先通过prompt弹出个输入框,而且onJsPrompt方法里的日志打印了出来

06-12 22:46:51.855 5010-5010/? D/JsCallAndroid: onJsPrompt message=js://webview?id=1&value=2

看到线程id说明这个回调是在UI线程

当输入之后点击确定



也是跟js里逻辑一样,把输入内容弹出框显示

然后我们在onJsPrompt方法里对Message内容进行解析过滤,如果是我们自己想要的,进行相应处理

/**
 * 拦截js的prompt弹出框
 * @param view
 * @param url
 * @param message
 *          js里promt()方法的参数内容,不是url
 * @param defaultValue
 * @param result
 *          代表输入框的返回值
 * @return
 */
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
    Log.d(TAG,"onJsPrompt url = " + url +",message="+message );
    // 根据协议的参数,判断是否是所需要的url
    // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
    //假定传入进来的 url = "js://webview?id=1&value=2"(同时也是约定好的需要拦截的)
    Uri uri = Uri.parse(message);
    // 如果url的协议 = 预先约定的 js 协议
    // 就解析往下解析参数
    if ( uri.getScheme().equals("js")) {

        // 如果 authority  = 预先约定协议里的 webview,即代表都符合约定的协议
        // 所以拦截url,下面JS开始调用Android需要的方法
        if (uri.getAuthority().equals("webview")) {

            //
            // 执行JS所需要调用的逻辑
            Log.d(TAG,"js调用了Android的方法");
            // 可以在协议上带有参数并传递到Android上
            HashMap<String, String> params = new HashMap<>();
            Set<String> collection = uri.getQueryParameterNames();
            Iterator<String>  iterator = collection.iterator();
            while (iterator.hasNext()) {
                String key = iterator.next();
                String value = uri.getQueryParameter(key);
                Log.d(TAG,"key="+key+",value="+value);
                params.put(key,value);
            }
            //参数result:代表消息框的返回值(输入值)
            result.confirm("js调用了Android的方法成功啦");
        }
        return true;
    }
    return super.onJsPrompt(view, url, message, defaultValue, result);
}



我们点击js里button后回回调android里的onJsPrompt方法,这里我们可以获取js传给我们的数据

看日志

06-12 22:56:15.525 5379-5379/? D/AndroidCallJSFrag: key=id,value=1

06-12 22:56:15.525 5379-5379/? D/AndroidCallJSFrag: key=value,value=2

再与js代码中的prompt方法比较

var result = prompt("js://webview?id=1&value=2");

可以看到获取的值是一样的。

至此JS调用Android的三种方法总结完毕。


接下来讲述第五步

5.Android调用JS

android调用js主要有两种方法

1.使用webview的loadurl

2.使用webview的evaluateJavascript

我们先看第一种方法

先把html代码编写完成

<!DOCTYPE html>
<html lang="en" dir="ltr">
    <head>
        <meta charset="utf-8">
        <title>WebView</title>

        <style >

            body{
                background-color: rgb(230, 105, 34);
            }

        </style>
    </head>
    <body>

        <h1 > android调用js</h1>

        <script type="text/javascript">
            //提供一个方法供android客户端调用
            function callJS(){
                //如果android调用js成功就用弹出框提示
                alert("android成功调用了js");
                //如果android调用js成功就返回值
                return "success";
            }
        </script>


    </body>
</html>

然后在客户端编写

private void initWebview() {

    WebSettings set = mWebView.getSettings();
    // 设置与Js交互的权限
    set.setJavaScriptEnabled(true);
    // 设置允许JS弹窗
    set.setJavaScriptCanOpenWindowsAutomatically(true);
    // 由于设置了弹窗检验调用结果,所以需要支持js对话框
    // 通过设置WebChromeClient对象处理JavaScript的对话框
    //设置响应js 的Alert()函数
    mWebView.setWebChromeClient(new WebChromeClient());

    mWebView.setWebViewClient(new WebViewClient(){
        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
            Log.e(TAG,"onPageFinished url="+url);
            //JS代码调用一定要在 onPageFinished() 回调之后才能调用,否则不会调用。
            mWebView.loadUrl("javascript:callJs()");
        }
    });

    mWebView.loadUrl("file:///android_asset/androidcalljs.html");
}


我们看第二种方法:

这个方法比第一种方法使用更高效,可以获取js返回值,要4.4以后才可以用

html不用变,只需在调用的客户端修改下


private void initWebview() {

    WebSettings set = mWebView.getSettings();
    // 设置与Js交互的权限
    set.setJavaScriptEnabled(true);
    mWebView.loadUrl("file:///android_asset/androidcalljs.html");
    // 设置允许JS弹窗
    set.setJavaScriptCanOpenWindowsAutomatically(true);
    // 由于设置了弹窗检验调用结果,所以需要支持js对话框
    // 通过设置WebChromeClient对象处理JavaScript的对话框
    //设置响应js 的Alert()函数
    mWebView.setWebChromeClient(new WebChromeClient());


}


@OnClick(R.id.tv_callJs)
public void callJS(){

    final int version = Build.VERSION.SDK_INT;
    if (version < 18) {
        mWebView.loadUrl("javascript:callJS()");//低版本使用这个方法
    } else {
        mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
            @Override
            public void onReceiveValue(String value) {
                //此处为 js 返回的结果
                Log.e(TAG,"value="+value);
            }
        });
    }
}

看看打印的日志

06-13 20:56:34.478 9464-9464/? E/AndroidCallJSFrag: value="success"

获取js的返回值成功,并且这个回调也是在UI线程

到这里本节就讲完了

相关代码已托管到GitHub

获取去这里下载我的下载



猜你喜欢

转载自blog.csdn.net/qq_30993595/article/details/80657021