Android WebView基本用法及常见问题

Android WebView基本用法及常见问题_第1张图片
webview网页图

序言


WebView

extends AbsoluteLayout
implements ViewGroup.OnHierarchyChangeListener ViewTreeObserver.OnGlobalFocusChangeListener


java.lang.Object
↳ android.view.View
↳ android.view.ViewGroup
↳ android.widget.AbsoluteLayout
↳ android.webkit.WebView


Class Overview

A View that displays web pages. This class is the basis upon which you can roll your own web browser or simply display some online content within your Activity. It uses the WebKit rendering engine to display web pages and includes methods to navigate forward and backward through a history, zoom in and out, perform text searches and more.

概述
WebView是一个显示网页的视图。这个类你可以滚动自己的Web浏览器或者只显示你的活动中的一些在线内容的基础。它使用WebKit渲染引擎来显示网页,包括向前和向后浏览历史记录,放大和缩小,进行文本搜索等方法。

更新内容

Ver:v1.3.180209

  • Http和Https混合问题
  • 跳转浏览器加载
  • 自定义拦截

Ver:v2.3.1802011

  • 设置标题
  • 设置加载进度
  • 错误处理

正文


基本用法

默认情况下,WebView不支持JavaScript,web页面的错误也会被忽略,如果只是用Webview来显示网页而不用交互,默认配置就可以了。如果需要交互,就需要自定义配置了。

1. WebView配置

1)添加访问网络权限(AndroidManifest.xml)


2)WebView配置

//不现实水平滚动条
mWebView.setHorizontalScrollBarEnabled(false);
//不现实垂直滚动条
mWebView.setVerticalScrollBarEnabled(false);
//滚动条在WebView内侧显示
mWebView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
//滚动条在WebView外侧显示
mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
//获取触摸焦点
mWebView.requestFocusFromTouch();

3)WebSettings子类配置

//声明WebSettings子类
WebSettings webSettings = webView.getSettings();

//如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
webSettings.setJavaScriptEnabled(true);  

//支持插件
webSettings.setPluginsEnabled(true); 

//设置自适应屏幕,两者合用
webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小 
webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小

//缩放操作
webSettings.setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提
webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放
webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件

//其他细节操作
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存 
webSettings.setAllowFileAccess(true); //设置可以访问文件 
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口 
webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式
2. WebView缓存
  • 当加载 html页面时,WebView会在/data/data/包名目录下生成databasecache两个文件夹
  • 请求的URL记录保存在WebViewCache.db,而URL的内容是保存在WebViewCache文件夹下
  • 启用缓存
//优先使用缓存: 
    WebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); 
        //缓存模式如下:
        //LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
        //LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据
        //LOAD_NO_CACHE: 不使用缓存,只从网络获取数据
        //LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据

    //不使用缓存: 
    WebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
  • 离线加载
if (NetUtil.isConnected(getApplicationContext())) {//判断网络是否可连接
    webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);//根据cache-control决定是否从网络上取数据。
} else {
    webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);//没网,则从本地获取,即离线加载
}

webSettings.setDomStorageEnabled(true); // 开启 DOM storage API 功能
webSettings.setDatabaseEnabled(true); //开启 database storage API 功能
webSettings.setAppCacheEnabled(true); //开启 Application Caches 功能

String cacheDirPath = getFilesDir().getAbsolutePath() + APP_CACAHE_DIRNAME;
webSettings.setAppCachePath(cacheDirPath); //设置  Application Caches 缓存目录
3. 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();
4. 加载方式

1)加载远程网页
webView.loadUrl("http://www.baidu.com");
2)加载assets目录下的本地网页
webView.loadUrl("file:///android_asset/test.html");
3)加载手机本地网页
webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");
4)加载HTML代码片段
webView.loadData(data, "text/html", "utf-8");
webView.loadDataWithBaseURL(null, data, "text/html", "utf-8", null);

loadDataWithBaseURL()loadData()多两个参数,可以指定HTML代码片段中相关资源的相对根路径,也可以指定历史Url。两个方法的其余三个参数相同。其次,两个方法加载的HTML代码片段有些不同,loadData()中的HTML data中不能包含'#', '%', '\', '?'四种特殊字符

5. WebViewClient类
/**在开始加载网页时会回调*/
public void onPageStarted(WebView view, String url, Bitmap favicon)
/**在结束加载网页时会回调*/
public void onPageFinished(WebView view, String url)
/**拦截 url 跳转,在里边添加点击链接跳转或者操作*/
public boolean shouldOverrideUrlLoading(WebView view, String url)
/**加载错误的时候会回调,在其中可做错误处理,比如再请求加载一次,或者提示404的错误页面 */
public void onReceivedError(WebView view, int errorCode,String description, String failingUrl)
/**当接收到https错误时,会回调此函数,在其中可以做错误处理*/
public void onReceivedSslError(WebView view, SslErrorHandler handler,SslError error)
/** 在每一次请求资源时,都会通过这个函数来回调*/
public WebResourceResponse shouldInterceptRequest(WebView view,String url)
6. WebChromeClient类
/**处理 alert 弹出框 */
public boolean onJsAlert(WebView view, String url, String message, JsResult result)
/**处理 prompt 弹出框 */
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result)
/**处理 confirm 弹出框 */
public boolean onJsConfirm(WebView view, String url, String message, JsResult result)
/**获取网站的图标 */
public void onReceivedIcon(WebView view, Bitmap icon)
/**获取当前网页的标题 */
public void onReceivedTitle(WebView view, String title)
/**打印 Console 信息 */
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
7. 管理Cookies

1)Cookie设置

CookieSyncManager.createInstance(this);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
String cookie = "name=xxx;age=18";
cookieManager.setCookie(URL, cookie);
CookieSyncManager.getInstance().sync();

2)获取Cookie

CookieManager cookieManager = CookieManager.getInstance();
String cookie = cookieManager.getCookie(URL);

3)清除Cookie

CookieSyncManager.createInstance(context);
CookieManager cookieManager = CookieManager.getInstance();
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    cookieManager.removeAllCookies(null);
} else {
    cookieManager.removeAllCookie();
}
CookieSyncManager.getInstance().sync();
8. 清除Cache
//清除网页访问留下的缓存
clearCache(true);
//清除当前webview访问的历史记录,会清理除了当前访问记录的所有访问历史记录
clearHistory();
//这个仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据
clearFormData();
//清理Webview缓存数据库  
deleteDatabase("webview.db"); 
deleteDatabase("webviewCache.db"); 
9. 前进、后退和刷新
reload();//刷新
goBack();//后退
goForward();//前进
goBackOrForward(int steps);//以当前index为起始点前进或后退到历史记录中指定steps,若steps为负数则后退,正数则前进
canGoForward();//是否可以前进
canGoBack();//是否可以后退

在不做任何处理前提下,点击返回键,整个activity会销毁直接退出。如果想要不退出当前activity,只想返回上一个HTML页面,那么就需要在返回监听事件做处理:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (event.getAction() == KeyEvent.ACTION_DOWN) {
        if (keyCode == KeyEvent.KEYCODE_BACK && webview.canGoBack()) {
            webview.goBack();//后退
            //webview.goForward();//前进
            return true;
        }
    }
    return super.onKeyDown(keyCode, event);
}
10. 访问Https地址

WebView默认是不处理https请求的,页面显示空白,需要进行如下设置:

webView.setWebViewClient(new WebViewClient() {
    @Override
    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
        //super.onReceivedSslError(view, handler, error);
        handler.proceed(); //表示等待证书响应
        // handler.cancel(); //表示挂起连接,为默认方式
        // handler.handleMessage(null); //可做其他处理
    }
});

WebView加载一些别人的 Url时候,有时候会发生证书认证错误的情况,这时候我们希望能够正常的呈现页面给用户,我们需要忽略证书错误,需要调用WebViewClient类的onReceivedSslError方法,调用handler.proceed()来忽略该证书错误。

11. 判断WebView是否已经滚动到页面顶端或者底端
  • getScrollY()方法返回的是当前可见区域的顶端距整个页面顶端的距离,也就是当前内容滚动的距离
  • getHeight()或者getBottom()方法都返回当前WebView 这个容器的高度
  • getContentHeight()方法返回的是整个html 的高度,但并不等同于当前整个页面的高度。因为WebView 有缩放功能,所以当前整个页面的高度实际上应该是原始 Html 的高度再乘上缩放比例。 因此,准确的判断方法应该是:
if(webview.getContentHeight()* webview.getScale() == (webview.getHeight() + webview.getScrollY())) { 
    //已经处于底端
 }
12. 内存泄露

使用WebView的Activity或者Fragment在退出时候可能会抛出TextToSpeech异常

  • Error

android.app.ServiceConnectionLeaked: Activity com.xxx.XxxActivity has leaked ServiceConnection android.speech.tts.TextToSpeech$Connection@33ce66ae that was originally bound here

主要问题在WebView开启了JavaScript后TextToSpeech这个组件持有Activity的引用,这个组件的生命周期应该是比Activity的生命周期要长,所以它持有Activity的引用会造成内存泄露。

mWebView.getSettings().setJavaScriptEnabled(true);
  • Solution

① 动态创建WebView

ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
webView = new WebView(getApplicationContext());
webView.setLayoutParams(lp);
relativeLayout.addView(webView);

② 移除销毁WebView

@Override
protected void onDestroy() {
    if (mWebView != null) {
        //再次打开页面时,若界面没有消亡,会导致进度条不显示并且界面崩溃
        mWebView.stopLoading();
        mWebView.onPause();
        mWebView.clearCache(true)
        mWebView.clearHistory();
        //动态创建webview调用
        //ViewGroup parent = (ViewGroup) mWebView.getParent();
        //if (parent != null) {
        //  parent.removeView(mWebView);
        //}
        mWebView.removeAllViews();
        //先结束未结束线程,以免可能会导致空指针异常
        mWebView.destroy();
        mWebView = null;
        super.onDestroy();
    }
}
13.Http和Https混合问题

从Android 5.0开始,WebView默认不支持同时加载Https和Http混合模式。如果H5页面有混合http和https的链接,则需要通过配置来开启。

//5.0及以上webview不支持http和https混合模式,需要通过配置来开启混合模式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}

从Android 5.0以后,当一个安全的站点(Https)去加载一个非安全的站点(Http)时,需要配置Webview加载内容的混合模式,一共有如下三种模式:

MIXED_CONTENT_NEVER_ALLOW: Webview不允许一个安全的站点(Https)去加载非安全的站点内容(Http),比如,Https网页内容的图片是http链接。强烈建议App使用这种模式,因为这样更安全。
MIXED_CONTENT_ALWAYS_ALLOW:在这种模式下,WebView是可以在一个安全的站点(Https)里加载非安全的站点内容(Http),这是WebView最不安全的操作模式,尽可能地不要使用这种模式。
MIXED_CONTENT_COMPATIBILITY_MODE:在这种模式下,当涉及到混合式内容时,WebView会尝试去兼容最新Web浏览器的风格。一些不安全的内容(Http)能被加载到一个安全的站点上(Https),而其他类型的内容将会被阻塞。这些内容的类型是被允许加载还是被阻塞可能会随着版本的不同而改变,并没有明确的定义。这种模式主要用于在App里面不能控制内容的渲染,但是又希望在一个安全的环境下运行。

在Android 5.0以下,默认是采用的 MIXED_CONTENT_ALWAYS_ALLOW 模式,即总是允许WebView同时加载Https和Http;而从Android 5.0开始,默认用
MIXED_CONTENT_NEVER_ALLOW 模式,即总是不允许WebView同时加载Https和Http

14. 跳转浏览器加载

如果不想在 webview 中显示网页,而是直接跳转到浏览器的话,可以这样调用:

    Uri uri = Uri.parse("https://www.baidu.com"); 
    Intent intent = new Intent(Intent.ACTION_VIEW, uri); 
    startActivity(intent);
15. 自定义拦截

1)拦截某一个链接不执行此链接,执行指定跳转到其他activity页面

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if(!TextUtils.isEmpty(url)) {
        if("login".contains(url)) {
            Intent intent = new Intent(this,LoginActivity.class);
            startActivity(intent);
            return true;
        } else {
            //返回值为true时在WebView中打开,为false时调用浏览器打开
            return false;
        }
    }
    return super.shouldOverrideUrlLoading(view, url);
}

从API 21起弃用该方法,用shouldOverrideUrlLoading(WebView view, WebResourceRequest request)来替换

@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
    String url = request.getUrl().toString();
    if(!TextUtils.isEmpty(url)) {
        if("login".contains(url)) {
            Intent intent = new Intent(this,LoginActivity.class);
            startActivity(intent);
            return true;
        } else {
            //返回值为true时在WebView中打开,为false时调用浏览器打开
            return false;
        }
    }
    return super.shouldOverrideUrlLoading(view, request);
}

2)拦截某一个要跳转的url链接,并对请求的url链接做修改

webView.setWebViewClient(new WebViewClient() {
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view,  String url) {
        if (url.contains("logo")) {
            String result = "\n" +
                            "百度\n" +
                            "\n" +
                            "百度,百度一下,就知道了\n" +
                            "\n" +
                            "";
            WebResourceResponse response = new WebResourceResponse("text/html",
                    "utf-8",
                    new ByteArrayInputStream(result.getBytes()));
        } else {
            ...
        }
        return response;
    }
});

从API 21起弃用该方法,用shouldInterceptRequest(WebView view, WebResourceRequest request)来替换

webView.setWebViewClient(new WebViewClient() {
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view,  WebResourceRequest request) {
        String url = request.getUrl().toString();   
        if (url.contains("logo")) {
            String result = "\n" +
                            "百度\n" +
                            "\n" +
                            "百度,百度一下,就知道了\n" +
                            "\n" +
                            "";
            WebResourceResponse response = new WebResourceResponse("text/html",
                    "utf-8",
                    new ByteArrayInputStream(result.getBytes()));
        } else {
            ...
        }
        return response;
    }
});
16. 设置标题

在用WebView加载的页面中,如果页面内有多个页面跳转链接,就不能把标题写死,需要动态获取WebView当前页面的标题,可以通过对WebChromeClient.onReceivedTitle()方法的重写来实现。

mWebView.setWebChromeClient(new WebChromeClient() {
    @Override
    public void onReceivedTitle(WebView view, String title) {
        super.onReceivedTitle(view, title);
        //onReceivedTitle可以回调网页的title
        tv_title.setText(title);
    }
});
17. 设置加载进度

WebView在加载网页时,默认是没有加载进度的,我们不能准确了解网页的加载进度。为了提高用户体验,我们可以在WebView中加入进度条显示加载进度。

mWebView.setWebChromeClient(new WebChromeClient() {
    @Override
    public void onProgressChanged(WebView view, int newProgress) {
        //使用控件ProgressDialog来显示更新进度条示数
        if (newProgress == 100) {
            mProgressDlg.dismiss();
        } else {
            mProgressDlg.setProgress(newProgress);
        }
    }
    @Override
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
        super.onReceivedError(view, errorCode, description, failingUrl);
        //在error发生时也进行取消
        mProgressDlg.dismiss();
    }
});
18. 错误处理

某些时候网络不好或者没有网络,这时候设备就访问不到服务器了,加载不了Html页面。一般情况下,当我们的设备无网络情况下加载一个Html时,会自行弹出Android默认的错误页面,这样用户体验会很差。主要有2种方式实现,一是加载一个本地的Html页面,告知网络异常,二是通过布局的方式自定义一个错误提示界面,加载到主界面。

mWebView.setWebViewClient(new WebViewClient() {
    @Override
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
        super.onReceivedError(view, errorCode, description, failingUrl);
        //网络异常,WebView加载我们自定义的页面
        view.loadUrl("file:///android_asset/error.html");
    }
});
参考

WebView官方文档,开启传送门

结语


至此,关于WebView的基本属性及用法都整理在这儿,有需要的朋友可供参考。

你可能感兴趣的:(Android WebView基本用法及常见问题)