对于现在的App来说,内嵌一些web网页是比较常见的了,如果只是简单的使用,那是很简单的,直接使用webview加载url就可以了,但是有时还是会涉及到各种不同的需求,这时就要求我们去设置一些参数以及会做不同的处理。

这里会从四个方面来说:

1、WebView的简单使用;

2、WebView使用WebSettings的各种设置;

3、WebView使用WebViewClient各种方法的作用;

4、WebView使用WebViewChrome各种方法的作用;

WebView使用

1、对于WebView的使用首先是来看他是如何加载各种资源的:

//方式1. 加载一个网页:
  webView.loadUrl("http://www.baidu.com/");

  //方式2:加载apk包中的html页面
  webView.loadUrl("file:///android_asset/web/test.html");

  //方式3:加载手机本地的html页面,这里需要注意在高版本中文件权限申请,如果没有权限会报ERR_ACCESS_DENIED
   webView.loadUrl("file://"+Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "test.html");

   // 方式4: 加载 HTML 页面的一小段内容
  webView.loadDataWithBaseURL(null,content,"text/html","utf-8",null);

目前这里只是加载了资源文件,并没有做任何的设置,显示效果肯定是不如意的,比如显示的界面可以左右滑动,这肯定不是我们想要的效果,我们想要的肯定是显示的界面正好适配手机屏幕,这就需要使用到WebViewSettings了,后面会讲到。

2、管理WebView的生命周期:

为了配合Activiyt的生命周期,WebView也有相对应的生命周期,接下来就一起来看看:

//激活WebView为活跃状态,能正常执行网页的响应
webView.onResume() ;

//当页面被失去焦点被切换到后台不可见状态,需要执行onPause
//通过onPause动作通知内核暂停所有的动作,比如DOM的解析、plugin的执行、JavaScript执行。
webView.onPause();

//当应用程序(存在webview)被切换到后台时,这个方法不仅仅针对当前的webview而是全局的全应用程序的webview
//它会暂停所有webview的layout,parsing,javascripttimer。降低CPU功耗。
webView.pauseTimers()
//恢复pauseTimers状态
webView.resumeTimers();

//销毁Webview
//在关闭了Activity时,如果Webview的音乐或视频,还在播放。就必须销毁Webview
//但是注意:webview调用destory时,webview仍绑定在Activity上
//这是由于自定义webview构建时传入了该Activity的context对象
//因此需要先从父容器中移除webview,然后再销毁webview:
rootLayout.removeView(webView); 
webView.destroy();

3、清理缓存:

//清除网页访问留下的缓存
//由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序.
Webview.clearCache(true);

//清除当前webview访问的历史记录
//只会webview访问历史记录里的所有记录除了当前访问记录
Webview.clearHistory();

WebViewSettings常见设置

上面讲到WebView并没有说到它的设置,比如如何将加在的资源正好可以适配手机的屏幕,如何才能对WebView进行缩放,又要如何缓存等等,这一系列的问题都是可以通过WebViewSettings来进行设置,下来就一起来看看:

1、如何获取WebViewSettings:

WebView webView = findViewById(R.id.wb_content);
WebSettings webSettings = webView.getSettings();

2、适配手机屏幕:

// 设置网页自动适配
//if(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT){//小于4.4(不包括4.4)用这个
//      webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
//}
// 这个是设置WebView是否支持ViewPort属性,
// ViewPort是在html中配置,主要为了适配各种屏幕,如果html中有配置这个属性,那么即使不设置这个属性也是会适配屏幕的
// 注意:当html中有这个属性时,WebView设置缩放属性是不起作用的
webSettings.setUseWideViewPort(true);
// 当html中没有配置ViewPort这个属性时,同时还需要设置下面这个属性才能适配屏幕
webSettings.setLoadWithOverviewMode(true);

html配置ViewPort如下:

简书ViewPort配置:
<meta name="viewport" content="width=device-width, initial-scale=1.0,user-scalable=no">
CSDN ViewPort配置:
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">

关于ViewPort的讲解可以自行百度。

3、设置字体内容的大小:

//设置字体的大小,100是默认大小,200是字体放大了一倍
webSettings.setTextZoom(100);

4、设置缓存模式:

// LOAD_DEFAULT:系统默认的缓存模式,有缓存且缓存没有过期,那么就使用缓存,否则就从网络上获取
// LOAD_CACHE_ELSE_NETWORK:只要有缓存,不管缓存有没有过期都是使用缓存,没有缓存就从网络上获取
// LOAD_NO_CACHE:不管有没有缓存都是从网络上获取
// LOAD_CACHE_ONLY:只从缓存中获取,不会从网络上获取
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);

对于缓存还存在其他几种设置,不过对于android来说,只需要设置其功能开启就行,一般都是在h5中做缓存处理:

4.1、Dom Storage(Web Storage)缓存:

webSettings.setDomStorageEnabled(true);

4.2、Web SQL Database 缓存:

webSettings.setDatabaseEnabled(true);
//设置缓存,可不用设置,使用系统默认路径
webSettings.setDatabasePath("path");

4.3、Application Cache(AppCache)缓存:

webSettings.setAppCacheEnabled(true);
// 不设置,使用系统默认的路径
webSettings.setAppCachePath("path");

4.4、indexed Database(IndexedDb)缓存:

webSettings.setJavaScriptEnabled(true);

这里只是说到了开启缓存的一些设置,对于这些缓存的具体含义可以参考:


5、使页面具有缩放功能,前提是html中没有配置ViewPort属性:

// 使页面具有缩放功能,在缩放的同时显示缩放按钮
webSettings.setBuiltInZoomControls(true);
// 多方功能开始时,隐藏缩放按钮(按钮看起来很丑)
webSettings.setDisplayZoomControls(false);

6、是否允许WebView加载不安全的链接:

// MIXED_CONTENT_NEVER_ALLOW:不安全的链接全部不允许加载,android 5.1默认
// MIXED_CONTENT_ALWAYS_ALLOW:不安全的链接总是允许加载,android 4.4及以下默认
// MIXED_CONTENT_COMPATIBILITY_MODE:不安全的链接询问用户书否允许加载
// 注意:android 5.1默认禁止http和https混合调用,需用下面设置开启
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);

 7、开启前端代码和android本地代码互调:

webSettings.setJavaScriptEnabled(true);

WebViewClient使用

WebViewSettings中的设置,并没有涉及到网络加载的一些逻辑,对于网络加载,必定是耗时的,这时为了更好的交互效果,肯定是需要一个加载的显示动画的,而且,有时想对网页中的部分链接进行拦截,这时又该怎么做呢?这时就该WebViewClient上场了。

1、当已经加载一个网页了,这时点击网页上的连接数时,这时就会跳转到系统的浏览器,这肯定不是我们想见到的,我们肯定是想让新加载的网页还是在我们的应用中显示,这时就需要设置WebViewClient:

webView.setWebViewClient(new WebViewClient());

这样设置后,点击网页中的链接时,就不会将事件交由系统去处理,而是会让我们的应用来处理,也就说新的点击事件将会交由我们的WebView来处理;

2、网页开始加载和结束加载回调:

webView.setWebViewClient(new WebViewClient(){
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                super.onPageStarted(view, url, favicon);
                Log.d(TAG, "onPageStarted: 我开始加载玩网页了,你可以显示加载动画了");
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                Log.d(TAG, "onPageFinished: 加载网页结束了,你可以取消加载动画了,但图片资源未必加载完成了");
            }
        });

3、当我点击网页上的链接时,我想自己处理,而不是交由应用的WebView去处理:

webView.setWebViewClient(new WebViewClient(){
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                if (url.equals("targetUrl")){
                    Log.d(TAG, "shouldOverrideUrlLoading: 这里就需要你自己做逻辑处理了,系统并不会去加载资源");
                    return true;
                } else {
                    Log.d(TAG, "shouldOverrideUrlLoading: 返回false,意味着应用不做处理,交由WebView去处理");
                    return false;
                }
            }
        });

这里的返回值就说明了点击链接的事件是交由谁去处理,返回true,WebView不会处理,需要我们自己处理,返回false的话WebView会去处理,这是我们就不需要做多余的处理了;

4、加载Web网页时,加载的并不只是一个请求,会有很多的请求去执行,比如一张图片就是一个请求,一份Css资源也是一个请求等,这时,如果想对这些url进行拦截,这也是可以做到的:

webView.setWebViewClient(new WebViewClient(){
            @Nullable
            @Override
            public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
                if (url.equals("targetUrl")) {
                    InputStream data = null;
                    WebResourceResponse response = new WebResourceResponse("text/html","utf-8",null);
                    Log.d(TAG, "shouldInterceptRequest: 这里data返回的是null,可以根据需要返回," +
                            "这样就不会再去网络上加载对应url资源了,也就不会再去执行下面的onLoadResource()方法了");
                    return response;
                } else {
                    Log.d(TAG, "shouldInterceptRequest: 没有拦截,会调用到下面的onLoadResource()方法");
                    return null;
                }
            }

            @Override
            public void onLoadResource(WebView view, String url) {
                super.onLoadResource(view, url);
                Log.d(TAG, "onLoadResource: 需要去加载的资源都会执行到这里");
            }
        });

5、对于网页的加载,有时候各种出错的问题都是会有的,我这里总结下我测试过的一些错误处理:

webView.setWebViewClient(new WebViewClient(){
            @Override
            public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
                super.onReceivedError(view, errorCode, description, failingUrl);
                if (errorCode == -1) {
                    Log.d(TAG, "onReceivedError: 加载本地文件时,文件没有找到");
                } else if (errorCode == -2) {
                    Log.d(TAG, "onReceivedError: 没有连接网络或是找不到服务器");
                }
            }

            @Override
            public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
                super.onReceivedHttpError(view, request, errorResponse);
                if (errorResponse.getStatusCode() == 404) {
                    Log.d(TAG, "onReceivedHttpError: 只有在android 6.0以上的系统才发触发到这里");
                }
            }
        });

注意:这里对于404的处理,目前只在6.0以上的系统才能接收到,所以针对6.0以下的系统是需要我们另想办法的。

WebChromeClient使用

WebChromClient是WebView的一个辅助类,可以获取h5页面的的标题,图片以及对话框,利用这个对话框的特性,可以方便android与前端的交互,有好些框架就是利用了这个特性,同时,还可以监听到h5页面的加载进度。

1、网页加载进度:

webView.setWebChromeClient(new WebChromeClient(){
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                super.onProgressChanged(view, newProgress);
                Log.d(TAG, "onProgressChanged: 加载进度 = "+newProgress);
            }
        });

2、获取网页标题:

webView.setWebChromeClient(new WebChromeClient(){

            @Override
            public void onReceivedTitle(WebView view, String title) {
                super.onReceivedTitle(view, title);
                Log.d(TAG, "onReceivedTitle: 网页的标题是 = "+title);
            }
        });

3、对于h5中的对话框,可以分为三种,一是Alert;二是confirm;三是prompt,当在h5页面中执行这三种对话框时,WebChromeClient也会有相应的方法执行,这是我们就可以做我们自己的逻辑了:

webView.setWebChromeClient(new WebChromeClient(){
            @Override
            public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
                Log.d(TAG, "onJsAlert: h5页面执行Alert对话框了");
                return super.onJsAlert(view, url, message, result);
            }

            @Override
            public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
                Log.d(TAG, "onJsConfirm: h5页面执行confirm对话框了");
                return super.onJsConfirm(view, url, message, result);
            }

            @Override
            public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
                Log.d(TAG, "onJsPrompt: h5页面执行prompt对话框了");
                return super.onJsPrompt(view, url, message, defaultValue, result);
            }
        });

这三个方法返回值都是boolean值,返回true意味着我们做了处理,返回false则说明我们没有处理,可根据实际需求进行处理。

总结:

对于WebView的使用,涉及到的四个类都已经说到了,再做个总结 https://www.jianshu.com/p/3c94ae673e2a

如何避免内存泄露

@Override
protected void onDestroy() {
        if (mWebView != null) {
            mWebView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
            mWebView.clearHistory();

            ((ViewGroup) mWebView.getParent()).removeView(mWebView);
            mWebView.destroy();
            mWebView = null;
        }
        super.onDestroy();
    }