Bootstrap

Android之深入WebView


WebView

目录
  1. 思维导图
  2. WebView 的基本使用
    • WebView
    • WebSettings
    • WebViewClient
    • WebChromeClient
  3. WebView 与 JS 交互
    • Android 去调用 JS 代码
    • JS 调用 Android 代码
  4. WebView 常见问题汇总
  5. WebView 优化
  6. 参考
思维导图

在这里插入图片描述

基本使用

WebView 是一个基于 webkit 引擎,展示 web 页面的空间。WebView 在低版本和高版本采用了不同的 webkit 内核版本,4.4 ( API 19 ) 之后直接使用了 Chrome。

WebView 类
    /**
     * 返回键后退网页
     * 如果又重写了 onBackPressed 方法,只会回调 onKeyDown
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && mWebView.canGoBack()) {
            mWebView.goBack();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }
	mWebView.onPasue();
    //清除缓存数据
    mWebView.clearCache(true);	//清除缓存
    mWebView.clearHistory();	//清除浏览记录
    mWebView.clearFormData();	//清除自动填充的表单数据
WebSetting 类

对 WebView 进行配置和管理。

    private void setWebViewSettings(WebView webView){
        WebSettings webSettings=webView.getSettings();
        webSettings.setJavaScriptEnabled(true); //支持 JS
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过 JS 打开新的窗口
        //设置自适应屏幕
        webSettings.setUseWideViewPort(true);
        webSettings.setLoadWithOverviewMode(true);

        webSettings.setLoadsImagesAutomatically(true);  //设置自动加载图片
        webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);    //不使用缓存
		//...
    }
WebViewClient 类

处理各种通知和请求事件等等。

//在当前 WebView 打开页面,而不是系统浏览器
//如果不需要转发处理,只需要传递一个 WebViewClent 实例,根本不需要重写 shouldOverrideUrlLoading 方法     
public class MyWebViewClient extends WebViewClient {

    private Context mContext;

    public MyWebViewClient(Context context) {
        mContext = context;
    }

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
        //当前 WebView 处理
        if (request.getUrl().getHost().equals("https://www.example.com")) {
            return false;
        }
        //如果需要转发处理
        mContext.startActivity(new Intent(Intent.ACTION_VIEW, request.getUrl()));
        return true;
    }

    @Override
    public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
        switch (error.getErrorCode()){
            case WebViewClient.ERROR_CONNECT:   //连接失败
                view.loadUrl("file:///android_asset/error.html");
                break;
        }
    }

    @Override
    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
        handler.proceed();  //等待证书响应 
        //handler.cancel();   //挂起连接 默认行为
    }
}

//使用WebViewClient
mWebView.setWebViewClient(new MyWebViewClient(MainActivity.this));
WebChromeClient 类

辅助 WebView 处理 JS 的对话框、网站标题等等。

public class MyWebChromeClient extends WebChromeClient {

    /**
     * 网页加载进度
     */
    @Override
    public void onProgressChanged(WebView view, int newProgress) {
        super.onProgressChanged(view, newProgress);
    }

    /**
     * 网页标题加载完毕回调
     */
    @Override
    public void onReceivedTitle(WebView view, String title) {
        super.onReceivedTitle(view, title);
    }

    /**
     * 拦截输入框
     */
    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        return super.onJsPrompt(view, url, message, defaultValue, result);
    }

    /**
     * 拦截确认框
     */
    @Override
    public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
        return super.onJsConfirm(view, url, message, result);
    }

    /**
     * 拦截弹框
     */
    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
        return super.onJsAlert(view, url, message, result);
    }
}

//使用WebChromeClient 
mWebView.setWebChromeClient(new MyWebChromeClient());
WebView 与 JS 交互

WebView 与 JS 交互分为俩种情况,一种是JS 调用 Android 代码,另一种是JS 调用 Android 方法。

Android 调用 JS 代码
  • webView.loadUrl(url)
  • webView.evaluateJavascript()

首先选准备一个静态文件:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Title</title>
    <script>
   function callJS(){
      alert("Android调用了 JS 的 callJS() 方法");
   }
    </script>
    <p3>
        WebView 与 JS 交互!
    </p3>
</head>
</html>

第一种方式:loadUrl()

        mWebView.loadUrl("javascript:callJS()");
        mWebView.setWebChromeClient(new WebChromeClient(){
            @Override
            public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
                AlertDialog dialog=new AlertDialog.Builder(WebViewContactActivity.this)
                        .setTitle("Title")
                        .setPositiveButton("确认", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                result.confirm();
                            }
                        })
                        .setCancelable(false)
                        .setMessage(message)
                        .create();
                dialog.show();
                return true;
            }
        });

可以看到,WebView 只是载体,内容的渲染还的通过 WebChromeClient 承载。

第二种方式:evaluateJavascript()

        mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
            @Override
            public void onReceiveValue(String value) {
                //JS 返回的结果
                Toast.makeText(WebViewContactActivity.this, "value " + value, Toast.LENGTH_SHORT).show();
            }
        });

只是把上面的 loadUrl 换成 evaluateJavascript 方法而已。但是这种方法比第一种方式效率高,因为该方法的执行不会使页面刷新。

两种方法的对比:

调用方式优点缺点使用场景
loadUrl方便简洁效率低不需要获取返回值,对性能要求较低时
evaluatedJavascript效率高向下兼容性差( API > 19 )API > 19

当然也可以通过 Build.VERSION 来进行判断执行。

JS 调用 Android 代码
  • 通过 WebView.addJavascriptInterface 进行对象映射
  • 通过 WebViewClient.shouldOverrideUrlLoading 方法回调拦截 url
  • 通过 WebChromeClient 的 onJsAlert、onJsConfirm、onJsPrompt 方法回调拦截 JS 对话框 alert、confirm、prompt 消息

第一种方式:WebView.addJavascriptInterface 进行对象映射

首先先准备好资源文件,用于模拟 WebView 加载的网页:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Demo</title>
    <script>
         function callAndroid(){
            test.hello("js调用了android中的hello方法");
         }
      </script>
</head>
<body>
<p2>JS 调用 Android 方法</p2>
<button type="button" id="button1" onclick="callAndroid()">点击按钮调用 Android 的 hello 方法</button>
</body>
</html>

然后定义一个 JS 对象映射关系的 Android 类:

public class JSObject extends Object {

    private Context mContext;

    public JSObject(Context context) {
        mContext = context;
    }

    @JavascriptInterface
    public void hello(String msg){
        Toast.makeText(mContext, "JS 调用了 Android 的 hello 方法", Toast.LENGTH_SHORT).show();
    }
}

最后就是通过 WebView 设置 Android 类与 JS 代码的映射:

mWebView.loadUrl("file:///android_asset/js_to_android.html");
mWebView.addJavascriptInterface(new JSObject(this),"test");

第二种方式:WebViewClient.shouldOverrideUrlLoading 方法回调拦截 url

Android 通过 WebViewClient 的回调方法 shouldOverrideUrlLoading 拦截 url,解析该 url 协议,如果检测到是预先约定好的协议,就调用 Android 相应的方法。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Demo</title>
    <script>
         function callAndroid(){
            document.location = "js://webview?arg1=2333&arg2=222";
         }
      </script>
</head>
<body>
<p2>JS 调用 Android 方法</p2>
<button type="button" id="button1" onclick="callAndroid()">点击按钮调用 Android 的方法</button>
</body>
</html>
mWebView.loadUrl("file:///android_asset/js_call_android.html");
        mWebView.setWebViewClient(new WebViewClient(){
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
                if ("js".equals(request.getUrl().getScheme())){
                    if ("webview".equals(request.getUrl().getAuthority())){
                        Toast.makeText(WebViewContactActivity.this, "JS 调用 Android 方法,参数一为:"+request.getUrl().getQueryParameter("arg1"), Toast.LENGTH_SHORT).show();
                    }
                    return true;
                }
                return super.shouldOverrideUrlLoading(view, request);
            }
        });

第三种方式:通过 WebChromeClient 的 onJsAlert、onJsConfirm、onJsPrompt 方法回调拦截 JS 对话框的消息

这里只示例 onJsPrompt 的回调,因为这个方法可以返回任意类型的值。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Demo</title>
    <script>
         function callAndroid(){
            var result=prompt("js://demo?arg1=111&arg2=222");
            alert("demo " + result);
         }
      </script>
</head>
<body>
<p2>JS 调用 Android 方法</p2>
<button type="button" id="button1" onclick="callAndroid()">点击按钮调用 Android 的方法</button>
</body>
</html>
       mWebView.loadUrl("file:///android_asset/js_call_android_demo.html");
        mWebView.setWebChromeClient(new WebChromeClient(){
            @Override
            public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
                Uri uri=Uri.parse(message);
                if ("js".equals(uri.getScheme())){
                    if ("demo".equals(uri.getAuthority())){
                        result.confirm("JS 调用了 Android 的方法");
                    }
                    return true;
                }
                return super.onJsPrompt(view, url, message, defaultValue, result);
            }
        });

三种方式的比较:

调用方式优点缺点使用场景
WebView.addJavascriptInterface 对象映射方便简洁Android 4.2 一下存在漏洞Android 4.2 以上相对简单的应用场景
WebViewClient.shouldOverrideUrlLoading 回调拦截不存在漏洞使用复杂,需要协议约束不需要返回值情况下
WebChormeClient.onJsAlert / onJsConfirm / onJsPrompt 方法回调拦截不存在漏洞使用复杂,需要协议约束能满足大多数场景
WebView 常见问题
  1. WebView 销毁

        @Override
        protected void onDestroy() {
            super.onDestroy();
            if (mWebView != null) {
                mWebView.loadDataWithBaseURL("", null, "text/html", "utf-8", null);
                mWebView.clearHistory();
                ((ViewGroup) mWebView.getParent()).removeView(mWebView);
                mWebView.destroy();
                mWebView = null;
            }
        }
    
  2. Android P 阻止加载任何 http 的请求

    Mainfest 中加入:

    android:usesCleartextTraffic="true"
    
  3. Android 5.0 之后 WebView 禁止加载 http 与 https 混合内容

    if (Build.VERSION.SDK_INT>Build.VERSION_CODES.LOLLIPOP){
              mWebView.getSettings().
                  setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
            }
    
  4. WebView 开启硬件加速导致的问题

    比如不能打开 PDF,播放视频花屏等等。

    关闭硬件加速,或者直接用第三方库吧。

WebView 优化
  1. 给 WebView 加一个加载进度条

    重写 WebChromeClient 的 onProgressChanged 方法。

  2. 提高 HTML 网页加载速度,等页面 finsh 在加载图片

    public void int () {
        if(Build.VERSION.SDK_INT >= 19) {
            webView.getSettings().setLoadsImagesAutomatically(true);
        } else {
            webView.getSettings().setLoadsImagesAutomatically(false);
        }
    }
    
  3. 自定义 WebView 错误页面

    重写 WebViewClient 的 onReceivedError 方法。

参考

大牛总结的非常到位,非常值得学习和借鉴。https://www.jianshu.com/p/b9164500d3fb

;