Android -- WebView 与 JS 交互方式总结

前言

  • 现在很多App都选择混合开发模式,即App内置Web网页(Hybrid App),比如说很多电商平台,淘宝、京东、聚划算等等
  • App中加载网页是由Android的WebView实现的,其中涉及到Android客户端与Web网页交互的实现
  • 下面是对 Android通过WebView与JS交互 的总结

1. 交互方式总结

Android与JS通过WebView互相调用的方法,实际上是:

  • Android去调用JS的代码
  • JS去调用Android的代码

二者沟通的桥梁是WebvView

Android调用JS代码的方法有2种:

  1. 通过 WebViewloadUrl() 方法
  2. 通过 WebViewevaluateJavascript 方法

JS调用Android代码的方法有3种:

  1. 通过 WebViewaddJavascriptInterface() 进行对象映射
  2. 通过 WebViewClient shouldOverrideUrlLoading () 方法回调拦截 url
  3. 通过 WebViewClient onJsAlert()onJsConfirm()onJsPrompt() 方法回调拦截JS对话框的 alert()confirm()prompt() 消息

2. 具体分析

2.1 Android 通过 WebView 调用 JS 代码

对于Android调用JS代码的方法有2种:

  1. 通过 WebViewloadUrl() 方法
  2. 通过 WebViewevaluateJavascript 方法
2.1.1 方法分析
方式1:通过 WebViewloadUrl() 方法
  • 实例介绍:点击Android按钮,即调用 WebView JS(文本名为javascript)中 callJS()
  • 具体使用:

步骤1:将需要调用的JS代码以.html格式放到src/main/assets文件夹里

  1. 为了方便展示,本文是采用Andorid调用本地JS代码说明;
  2. 实际情况时,Android更多的是调用远程JS代码,即将加载的JS代码路径改成url即可

需要加载JS代码:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		// JS代码
		<script>
		    // Android需要调用的方法
			function callJS(){
				alert("Android调用了JS的callJS方法");
			}
		</script>
	</head>

    <body>
		 <h1>你好!我是网页</h1>
	</body>
</html>

步骤2:在Android里通过WebView设置调用JS代码

这里我继承了BaseActivty ,里面封装了一些初始化的东西,实际上就是在 onCreate() 里初始化 initView()

public class WebViewActivity extends BaseActivty {
    @BindView(R.id.webview)
    WebView webview;

    @Override
    protected int getLayoutResId() {
        return R.layout.activity_webview;
    }

    @Override
    protected void initView() {
        WebSettings webSettings = webview.getSettings();
        // 设置与Js交互的权限
        webSettings.setJavaScriptEnabled(true);
        // 设置允许JS弹窗
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
        //设置自适应屏幕,两者合用
        webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小
        webSettings.setLoadWithOverviewMode(true);// 缩放至屏幕的大小

        // webview只是载体,内容的渲染需要使用webviewChromClient类去实现,
        // 通过设置WebChromeClient对象处理JavaScript的对话框,不设置的话,网页对话框出不来
        webview.setWebChromeClient(new CustomWebViewChromeClient());

        webview.loadUrl("file:///android_asset/test_image.html");
    }

    @Override
    protected void setListener() {

    }

    @Override
    protected void processLogic() {

    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // TODO: add setContentView(...) invocation
        ButterKnife.bind(this);
    }

    @OnClick({R.id.button1, R.id.button2, R.id.button3, R.id.button4})
    void onClick(View view) {
        switch (view.getId()) {
            case R.id.button1:
                webview.loadUrl("javascript:callJS()");
                break;
            default:
        }

    }

    public class CustomWebViewChromeClient extends WebChromeClient {
        //设置响应js 的Alert()函数

        /**
         * 拦截js的警告框,没有返回值
         *
         * @param view
         * @param url     网页地址
         * @param message 代表 alert()里的内容(不是url)
         * @param result
         * @return
         */
        @Override
        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
            //如不想弹出网页上的对话框,可直接拦截,并自定义对话框
//            AlertDialog.Builder builder = new AlertDialog.Builder(WebViewActivity.this);
//            builder.setTitle("Alert")
//                    .setMessage(message)
//                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
//                        @Override
//                        public void onClick(DialogInterface dialog, int which) {
//                            result.confirm();
//                        }
//                    })
//                    .setCancelable(false)
//                    .create()
//                    .show();
//            return true;
            return super.onJsAlert(view, url, message, result);
        }
    }
}

在这里插入图片描述
在这里插入图片描述
特别注意:JS代码调用一定要在 onPageFinished() 回调之后才能调用,否则不会调用。

onPageFinished()属于WebViewClient类的方法,主要在页面加载结束时调用

方式2:通过 WebViewevaluateJavascript() 方法
  • 优点:该方法比第一种方法效率更高、使用简洁。
  1. 该方法的执行不会使页面刷新,而第一种方法(loadUrl )的执行则会。
  2. Android 4.4 后才可使用。

具体使用:

// 只需要将第一种方法的loadUrl()换成下面该方法即可
webview.evaluateJavascript("javascript:callJSReturnParams()", 
         new ValueCallback<String>() {
      @Override
      public void onReceiveValue(String value) {
	      //此处为 js 返回的结果
	      ToastUtils.toast(WebViewActivity.this, value);
	      //这里返回 Android调用了JS的callJSReturnParams方法
      }
});

JS代码:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		// JS代码
		<script>
		    // Android需要调用的方法
			function callJSReturnParams(){
			    return "Android调用了JS的callJSReturnParams方法";
			}
		</script>
	</head>

    <body>
		 <h1>你好!我是网页</h1>
	</body>
</html>
2.1.2 传参(拓展)

Android调用JS方法并传参过去

JS代码:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		// JS代码
		<script>
		    function callJSParams(msg){
				alert(msg);
			}
		</script>
	</head>

    <body>
		 <h1>你好!我是网页</h1>
		 <button type="button" onclick="callJSParams('你好')">测试按钮</button>
	</body>
</html>

Android调用JS代码:

private String msg = "Android调用了JS的callJSParams传参方法";
...
webview.loadUrl("javascript:callJSParams('" + msg + "')");
...

evaluateJavascript() 传参方法与 loadUrl() 类似,这里不贴了

2.1.3 两种方式对比 & 使用场景
调用方式优点缺点使用场景
使用 loadUrl()方便简洁效率低;
获取返回值麻烦
不需要获取返回值,
对性能要求较低时
evaluateJavascript()效率高向下兼容性差
(仅Android 4.4 以上可用)
Android 4.4 以上
需要获取返回值
使用建议

两种方法混合使用,即Android 4.4以下使用方法1,Android 4.4以上方法2

//建议混合使用,4.4以上使用evaluateJavascript方法调用JS方法,4.4一下使用loadUrl方法
//evaluateJavascript在4.4版本以上才可以使用
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
     webview.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
            @Override
            public void onReceiveValue(String value) {
                   //此处为 js 返回的结果
            }
     });
} else {
     webview.loadUrl("javascript:callJS()");
}

2.2 JS通过 WebView 调用 Android 代码

对于JS调用Android代码的方法有3种:

  1. 通过 WebViewaddJavascriptInterface() 进行对象映射
  2. 通过 WebViewClient shouldOverrideUrlLoading () 方法回调拦截 url
  3. 通过 WebViewClient onJsAlert()onJsConfirm()onJsPrompt() 方法回调拦截JS对话框的 alert()confirm()prompt() 消息
2.2.1 方法分析
方式1: 通过 WebViewaddJavascriptInterface() 进行对象映射

步骤1:定义一个与JS对象映射关系的Android类:JsObject

// 继承自Object类
    public class JsObject extends Object {
        // 定义JS需要调用的方法
        // 被JS调用的方法必须加入@JavascriptInterface注解
        @JavascriptInterface
        public void hello(String msg) {
            ToastUtils.toast(WebViewActivity.this, msg);

            //在主线程中刷新UI
//            runOnUiThread(new Runnable() {
//                @Override
//                public void run() {
                    tv1.setText("在主线程中刷新UI");
                    ToastUtils.toast(WebViewActivity.this, msg);
//                }
//            });
        }
    }

步骤2:将需要调用的JS代码以.html格式放到src/main/assets文件夹里

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<script>
			function callAndroid(){
				// 由于对象映射,所以调用test对象等于调用Android映射的对象
				test.hello("js调用了android中的hello方法");
			}
		</script>
	</head>
	
	<body>
		 <h1>你好!我是网页</h1>
		 <button type="button" onclick="callAndroid()">点击调用Android代码</button>
	</body>
</html>

步骤3:在Android里通过WebView设置Android类与JS代码的映射

在上面的 initView() 方法中设置 Java对象映射到JS对象中

// 通过addJavascriptInterface()将Java对象映射到JS对象
//参数1:Javascript对象名
//参数2:Java对象名
webview.addJavascriptInterface(new JsObject(), "test");

在这里插入图片描述
特点

  • 优点:使用简单,仅将Android对象和JS对象映射即可
  • 缺点:Android 4.2 以下存在严重的漏洞问题
方式2: 通过 WebViewClient shouldOverrideUrlLoading () 方法回调拦截 url
  • 具体原理:
  1. Android通过 WebViewClient 的回调方法shouldOverrideUrlLoading () 拦截 url
  2. 解析该 url 的协议
  3. 如果检测到是预先约定好的协议,就调用相应方法,即JS需要调用Android的方法
  • 具体使用:

步骤1:与JS约定所需要的Url协议

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<script>
			function callAndroidUrl(){
				//约定的url协议为:js://webview?arg1=111&arg2=222
				document.location = "js://webview?arg1=111&arg2=222";
			}
		</script>
	</head>
	
	<body>
		 <h1>你好!我是网页</h1>
		 <button type="button" onclick="callAndroidUrl()">点击调用Android代码</button>
	</body>
</html>

当该JS通过Android的webview.loadUrl("file:///android_asset/test_image.html")加载后,就会回调shouldOverrideUrlLoading (),接下来继续看步骤2:

步骤2:Android 通过 WebViewClient 复写 shouldOverrideUrlLoading ()

...
protected void initView() {
        ...
        webview.setWebViewClient(new CustomWebClient());
        ...

        webview.loadUrl("file:///android_asset/test_image.html");
    }
...

public class CustomWebClient extends WebViewClient {
        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
        }

        //7.0以下访问
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            return setUrl(Uri.parse(url));
        }

        //7.0以上访问
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
            return setUrl(request.getUrl());
        }

    }


/**
     * 解析js回调回来的url协议
     *
     * @param uri
     * @return
     */
    private boolean setUrl(Uri uri) {
        // 步骤1:根据协议的参数,判断是否是所需要的url
        // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
        // 假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)

        // 如果url的协议 = 预先约定的 js 协议,就解析往下解析参数
        if (uri.getScheme().equals("js")) {
            // 如果 authority  = 预先约定协议里的 webview,即代表都符合约定的协议
            // 所以拦截url,下面JS开始调用Android需要的方法
            if (uri.getAuthority().equals("webview")) {
                //  步骤2:
                // 执行JS所需要调用的逻辑
                // 解析协议上的参数并传递到Android上
                Map<String, String> params = new HashMap<>();
                StringBuffer stringBuffer = new StringBuffer();
                Set<String> collection = uri.getQueryParameterNames();
                for (String str : collection) {
                    params.put(str, uri.getQueryParameter(str));
                    stringBuffer.append(str + "=" + uri.getQueryParameter(str) + ", ");
                }

                ToastUtils.toast(WebViewActivity.this, stringBuffer.toString());
            }

            //url拦截处理,网页不会跳转
            return true;
        }

        return false;
    }

注:
shouldOverrideUrlLoading(WebView view, String url) 已经在 Android N 中过时,这里两者做好兼容。

特点

  • 优点:不存在方式1的漏洞
  • 缺点:JS 获取 Android 方法的返回值复杂

如果JS想要得到Android方法的返回值,只能通过 WebView 的 loadUrl ()去执行 JS
方法把返回值传递回去,相关的代码如下:

// Android:MainActivity.java
mWebView.loadUrl("javascript:returnResult(" + result + ")");

// JS:javascript.html
function returnResult(result){
    alert("result is" + result);
}
方式3:通过 WebViewClient onJsAlert()onJsConfirm()onJsPrompt() 方法回调拦截JS对话框的 alert()confirm()prompt() 消息
  • 在JS中,有三个常用的对话框方法:
方法作用返回值备注
alert()弹出警告框没有在文本中加入\n可换行
confirm()弹出确认框两个返回值返回布尔值;
通过该值可判断点击的是确认or取消:
true:点击了确认,false:点击了取消
prompt()弹出输入框任意设置返回值点击“确认”:返回输入框中的值;
点击“取消”:返回null
  • 原理:
    主要是 得到他们的消息内容 解析 即可

prompt() 方法为例:

  1. 常用的拦截是:拦截 JS的输入框(即prompt()方法)
  2. 因为只有prompt()可以返回任意类型的值,操作最全面方便、更加灵活;而alert()对话框没有返回值;confirm()对话框只能返回两种状态(确定 / 取消)两个值

步骤1:加载JS代码:

此JS代码也可在网页上进行测试,网页上的效果,这里不展示了

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<script>
			//弹出输入框,Android可任意设置返回值
			function clickPrompt(){
				// 调用prompt()
				var result = prompt("js://demo?arg1=111&arg2=222");
				alert("返回值:" + result);
			}
		</script>
	</head>
	
	<body>
		 <h1>你好!我是网页</h1>
		 <button type="button" onclick="clickPrompt()">点击按钮调用Android prompt()</button>
	</body>
</html>

步骤2:在Android通过WebChromeClient复写 onJsPrompt()

当使用webview.loadUrl("file:///android_asset/test_image.html")加载了上述JS代码后,就会触发回调onJsPrompt(),具体如下:

...
protected void initView() {
        ...
        // webview只是载体,内容的渲染需要使用webviewChromClient类去实现,
        // 通过设置WebChromeClient对象处理JavaScript的对话框,不设置的话,网页对话框出不来
        webview.setWebChromeClient(new CustomWebViewChromeClient());
        ...

        webview.loadUrl("file:///android_asset/test_image.html");
    }
...

public class CustomWebViewChromeClient extends WebChromeClient {
        /**
         * 拦截js的输入框
         *
         * @param view
         * @param url          网页地址
         * @param message      代表 promt()里的内容(不是url)  js://demo?arg1=111&arg2=222
         * @param defaultValue
         * @param result       代表输入框的返回值,任意值
         * @return
         */
        @Override
        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
            // 根据协议的参数,判断是否是所需要的url
            // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
            // 假定传入进来的 url = "js://demo?arg1=111&arg2=222"(同时也是约定好的需要拦截的)
            Uri uri = Uri.parse(message);
            // 如果url的协议 = 预先约定的 js 协议,就解析往下解析参数
            if (uri.getScheme().equals("js")) {
                // 如果 authority  = 预先约定协议里的 webview,即代表都符合约定的协议
                // 所以拦截url,下面JS开始调用Android需要的方法
                if (uri.getAuthority().equals("demo")) {
                    //处理逻辑,并回传值给Js
                    Map<String, String> map = new HashMap<>();
                    Set<String> collection = uri.getQueryParameterNames();

                    //参数result:代表消息框的返回值(输入值)
                    result.confirm("Android返回值");
                }

                return true;
            }

            return super.onJsPrompt(view, url, message, defaultValue, result);
        }

    }

在这里插入图片描述
confirm() 说明:

步骤1:加载JS代码:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<script>
			//弹出输入框,Android可任意设置返回值
			function clickPrompt(){
				// 调用prompt()
				var result = prompt("js://demo?arg1=111&arg2=222");
				alert("返回值:" + result);
			}
			
            //弹出确认框,Android只能返回两个值;确认:true;取消:false
			function clickConfirm(){
				var result = confirm("js://confirm?arg1=111&arg2=222");
				alert(result);
			}
		</script>
	</head>
	
	<body>
		 <h1>你好!我是网页</h1>
		 <button type="button" onclick="clickPrompt()">点击按钮调用Android prompt()</button>
		 <button type="button" onclick="clickConfirm()">点击按钮调用Android confirm()</button>
	</body>
</html>

步骤2:在Android通过WebChromeClient复写 onJsConfirm

public class CustomWebViewChromeClient extends WebChromeClient {
        //设置响应js 的Alert()函数

        /**
         * 拦截js的警告框,没有返回值
         *
         * @param view
         * @param url     网页地址
         * @param message 代表 alert()里的内容(不是url)
         * @param result
         * @return
         */
        @Override
        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
            //如不想弹出网页上的对话框,可直接拦截,并自定义对话框
//            AlertDialog.Builder builder = new AlertDialog.Builder(WebViewActivity.this);
//            builder.setTitle("Alert")
//                    .setMessage(message)
//                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
//                        @Override
//                        public void onClick(DialogInterface dialog, int which) {
//                            result.confirm();
//                        }
//                    })
//                    .setCancelable(false)
//                    .create()
//                    .show();
//            return true;
            return super.onJsAlert(view, url, message, result);
        }

        /**
         * 拦截js的输入框
         *
         * @param view
         * @param url          网页地址
         * @param message      代表 promt()里的内容(不是url)  js://demo?arg1=111&arg2=222
         * @param defaultValue
         * @param result       代表输入框的返回值,任意值
         * @return
         */
        @Override
        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
            // 根据协议的参数,判断是否是所需要的url
            // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
            // 假定传入进来的 url = "js://demo?arg1=111&arg2=222"(同时也是约定好的需要拦截的)
            Uri uri = Uri.parse(message);
            // 如果url的协议 = 预先约定的 js 协议,就解析往下解析参数
            if (uri.getScheme().equals("js")) {
                // 如果 authority  = 预先约定协议里的 webview,即代表都符合约定的协议
                // 所以拦截url,下面JS开始调用Android需要的方法
                if (uri.getAuthority().equals("demo")) {
                    //处理逻辑,并回传值给Js
                    Map<String, String> map = new HashMap<>();
                    Set<String> collection = uri.getQueryParameterNames();

                    //参数result:代表消息框的返回值(输入值)
                    result.confirm("Android返回值");
                }

                return true;
            }

            return super.onJsPrompt(view, url, message, defaultValue, result);
        }

        /**
         * 拦截js的确认框
         *
         * @param view
         * @param url     网页地址
         * @param message 代表 confirm()里的内容(不是url)  js://confirm?arg1=111&arg2=222
         * @param result  两个值;确认:true;取消:false
         * @return
         */
        @Override
        public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
            // js://confirm?arg1=111&arg2=222
            Uri uri = Uri.parse(message);
            if (uri.getScheme().equals("js")) {
                if (uri.getAuthority().equals("confirm")) {
                    Map<String, String> map = new HashMap<>();
                    Set<String> collection = uri.getQueryParameterNames();

                    result.confirm();
                }
                return true;
            }

            return super.onJsConfirm(view, url, message, result);
        }
    }

onJsAlert() 复写的方法同上,此处省略。

小结:

1. 拦截警告框(即alert()),则触发回调 onJsAlert()
2. 拦截确认框(即confirm()),则触发回调 onJsConfirm()
3. 拦截输入框(即prompt()),则触发回调 onJsPrompt()

2.2.2 三种方式的对比 & 使用场景
调用方式优点缺点使用场景
通过 WebView
addJavascriptInterface()
进行对象映射
方便简洁Android 4.2 以下存在漏洞问题Android 4.2 以上相对简单互调场景
通过 WebViewClient
shouldOverrideUrlLoading ()
方法回调拦截 url
不存在漏洞问题使用复杂:需要进行协议的约束;
从Native层往Web层传递值比较繁琐
不需要返回值情况下的互调场景
(IOS主要使用该方式)
通过 WebViewClient onJsAlert()
onJsConfirm()onJsPrompt() 方法
回调拦截JS对话框消息
不存在漏洞问题使用复杂,需要进行协议的约束能满足大多数情况下的互调场景

3. 总结

在这里插入图片描述

Android WebView 是一个能够在 Android 应用程序中嵌入网页的组件,它可以开发出能够与 Web 页面进行交互的应用。其中与 Web 页面进行交互的一种方法是与 JavaScript 进行交互。下面简要解释一下 Android WebViewJS 交互的方式。 1. 加载本地 HTML 文件 在 Android WebView 中加载本地 HTML 文件时,需要使用 loadUrl() 方法加载。HTML 文件中的 JavaScript 可以通过 WebView 提供的 addJavascriptInterface() 方法注册为 Java 中的一个对象,然后在 Java 中调用该对象的方法,即可实现 JS 与 Java 的交互。 2. 加载远程 Web 页面 在 Android WebView 中加载远程 Web 页面时,需要添加 WebViewClient 和 WebChromeClient,分别是用来管理 WebView 的网络请求和处理页面上的 JavaScript 弹窗等请求。 在远程 Web 页面上,JS 代码可以通过 WebView 提供的 addJavascriptInterface() 方法注册为 Java 中的一个对象,然后在 Java 中调用该对象的方法,即可实现 JS 与 Java 的交互。 同时,在 Android 中处理 JS 的事件需要通过 JavaScriptInterface 向 WebView 注册一个映射对象,来实现 JS、Java 相互调用的机制,静态 HTML 文件是通过 WebView 中的 evaluateJavascript() 方法来调用 JS,来实现双向通信和数据交互总结来说,Android WebViewJS 交互的方式主要是通过 WebView 提供的 addJavascriptInterface() 方法注册为 Java 中的一个对象,然后在 Java 中调用该对象的方法,来实现 JS 和 Java 的交互。同时,JS 也可以通过 WebView 的 evaluateJavascript() 方法来调用 Java 中的方法,实现双向通信和数据交互
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值