个人知识总结:View篇--WebView

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. Note that, in order for your Activity to access the Internet and load web pages in a WebView, you must add the permissions to your Android Manifest file.

以上是Google api对于WebView的简介,WebView是Google里面最特殊最极具特色的一个控件,主要用于加载和显示网页信息。而且近年来混合式开发的app越来越多,WebView的使用也越来越多,也越来越重要。WebView本身是没有什么特殊属性的,大部分都是在Java页面来设置的,在xml里面是没有什么属性来额外设置的。

WebView的基础方法使用
//加载网址相关
// 加载URL指定的网页
public void loadUrl(String url);
// 携带http headers加载URL指定的网页
public void loadUrl(String url, Map additionalHttpHeaders);
// 使用POST请求加载指定的网页
public void postUrl(String url, byte[] postData);
// 重新加载当前网页
public void reload();
// 加载内容
public void loadData(String data, String mimeType, String encoding);
// 使用baseUrl加载内容
public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl);

//网页导航
// 是否可后退
public boolean canGoBack();
// 是否可前进
public boolean canGoForward();
// 是否可前进/后退steps页,大于0表示前进小于0表示后退
public boolean canGoBackOrForward(int steps);
// 后退一页
public void goBack();
// 前进一页
public void goForward();
// 前进/后退steps页,大于0表示前进小于0表示后退
public void goBackOrForward(int steps);

// 其他常用的方法
// 获取基础设置WebSettings
public WebSettings getSettings();
// 获取当前页面的URL
public String getUrl();
// 获取当前页面的标题
public String getTitle();
// 获取当前页面的加载进度
public int getProgress();
// 清除当前webview访问的历史记录
public void clearHistory();
// 清除网页缓存,由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序
public void clearCache(boolean includeDiskFiles);
// 清除自动完成填充的表单数据
public void clearFormData();
// 类似 Activity 生命周期,页面进入后台不可见状态后,停止网页内的其他活动,如音频、视频
public void onPause();
// 在调用 onPause()后,可以调用该方法来恢复 WebView 的运行。
public void onResume();
// 和onPause方法的作用基本上一致,不过pauseTimers方法可以暂停应用内所有的WebView
public void pauseTimers();
// 和onResume方法的作用基本上一致,不过resumeTimers方法可以恢复应用内所有的WebView
public void resumeTimers();
WebSettings的一些属性介绍

提到WebView就不得不提WebSettings,WebSettings主要通过WebView的getSettings方法获取WebSettings对象,来给对应的WebView设置一些基本显示属性。

// 存储(storage)
// 启用HTML5 DOM storage API,默认值 false
settings.setDomStorageEnabled(true); 
// 启用Web SQL Database API,这个设置会影响同一进程内的所有WebView,默认值 false
// 此API已不推荐使用,参考:https://www.w3.org/TR/webdatabase/
settings.setDatabaseEnabled(true);  
// 启用Application Caches API,必需设置有效的缓存路径才能生效,默认值 false
// 此API已废弃,参考:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Using_the_application_cache
settings.setAppCacheEnabled(true); 
settings.setAppCachePath(context.getCacheDir().getAbsolutePath());

// 定位(location)
settings.setGeolocationEnabled(true);

// 是否保存表单数据
settings.setSaveFormData(true);
// 是否当webview调用requestFocus时为页面的某个元素设置焦点,默认值 true
settings.setNeedInitialFocus(true);  

// 是否支持viewport属性,默认值 false
// 页面通过``自适应手机屏幕
settings.setUseWideViewPort(true);
// 是否使用overview mode加载页面,默认值 false
// 当页面宽度大于WebView宽度时,缩小使页面宽度等于WebView宽度
settings.setLoadWithOverviewMode(true);
// 布局算法
settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);

// 是否支持Javascript,默认值false
settings.setJavaScriptEnabled(true); 
// 是否支持多窗口,默认值false
settings.setSupportMultipleWindows(false);
// 是否可用Javascript(window.open)打开窗口,默认值 false
settings.setJavaScriptCanOpenWindowsAutomatically(false);

// 资源访问
settings.setAllowContentAccess(true); // 是否可访问Content Provider的资源,默认值 true
settings.setAllowFileAccess(true);    // 是否可访问本地文件,默认值 true
// 是否允许通过file url加载的Javascript读取本地文件,默认值 false
settings.setAllowFileAccessFromFileURLs(false);  
// 是否允许通过file url加载的Javascript读取全部资源(包括文件,http,https),默认值 false
settings.setAllowUniversalAccessFromFileURLs(false);

// 资源加载
settings.setLoadsImagesAutomatically(true); // 是否自动加载图片
settings.setBlockNetworkImage(false);       // 禁止加载网络图片
settings.setBlockNetworkLoads(false);       // 禁止加载所有网络资源

// 缩放(zoom)
settings.setSupportZoom(true);          // 是否支持缩放
settings.setBuiltInZoomControls(false); // 是否使用内置缩放机制
settings.setDisplayZoomControls(true);  // 是否显示内置缩放控件

// 默认文本编码,默认值 "UTF-8"
settings.setDefaultTextEncodingName("UTF-8");
settings.setDefaultFontSize(16);        // 默认文字尺寸,默认值16,取值范围1-72
settings.setDefaultFixedFontSize(16);   // 默认等宽字体尺寸,默认值16
settings.setMinimumFontSize(8);         // 最小文字尺寸,默认值 8
settings.setMinimumLogicalFontSize(8);  // 最小文字逻辑尺寸,默认值 8
settings.setTextZoom(100);              // 文字缩放百分比,默认值 100

// 字体
settings.setStandardFontFamily("sans-serif");   // 标准字体,默认值 "sans-serif"
settings.setSerifFontFamily("serif");           // 衬线字体,默认值 "serif"
settings.setSansSerifFontFamily("sans-serif");  // 无衬线字体,默认值 "sans-serif"
settings.setFixedFontFamily("monospace");       // 等宽字体,默认值 "monospace"
settings.setCursiveFontFamily("cursive");       // 手写体(草书),默认值 "cursive"
settings.setFantasyFontFamily("fantasy");       // 幻想体,默认值 "fantasy"


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    // 用户是否需要通过手势播放媒体(不会自动播放),默认值 true
    settings.setMediaPlaybackRequiresUserGesture(true);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    // 5.0以上允许加载http和https混合的页面(5.0以下默认允许,5.0+默认禁止)
    settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    // 是否在离开屏幕时光栅化(会增加内存消耗),默认值 false
    settings.setOffscreenPreRaster(false);
}

if (isNetworkConnected(context)) {
    // 根据cache-control决定是否从网络上取数据
    settings.setCacheMode(WebSettings.LOAD_DEFAULT);
} else {
    // 没网,离线加载,优先加载缓存(即使已经过期)
    settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
}
WebView的网页加载事件监听

WebView的加载事件监听主要有两个WebViewClient和WebChromeClient,WebViewClient主要是监听网页加载的状态(开始加载、加载进度、加载完成...等);WebChromeClient主要是监听网页的其他属性(是否使用手机的默认浏览器,网页加载的标题...等)。

WebViewClient
// 拦截页面加载,返回true表示宿主app拦截并处理了该url,否则返回false由当前WebView处理
// 此方法在API24被废弃,不处理POST请求
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    return false;
}

// 拦截页面加载,返回true表示宿主app拦截并处理了该url,否则返回false由当前WebView处理
// 此方法添加于API24,不处理POST请求,可拦截处理子frame的非http请求
@TargetApi(Build.VERSION_CODES.N)
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
    return shouldOverrideUrlLoading(view, request.getUrl().toString());
}

// 此方法废弃于API21,调用于非UI线程
// 拦截资源请求并返回响应数据,返回null时WebView将继续加载资源
// 注意:API21以下的AJAX请求会走onLoadResource,无法通过此方法拦截
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
    return null;
}

// 此方法添加于API21,调用于非UI线程
// 拦截资源请求并返回数据,返回null时WebView将继续加载资源
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
    return shouldInterceptRequest(view, request.getUrl().toString());
}

// 页面(url)开始加载
public void onPageStarted(WebView view, String url, Bitmap favicon) {
}

// 页面(url)完成加载
public void onPageFinished(WebView view, String url) {
}

// 将要加载资源(url)
public void onLoadResource(WebView view, String url) {
}

// 这个回调添加于API23,仅用于主框架的导航
// 通知应用导航到之前页面时,其遗留的WebView内容将不再被绘制。
// 这个回调可以用来决定哪些WebView可见内容能被安全地回收,以确保不显示陈旧的内容
// 它最早被调用,以此保证WebView.onDraw不会绘制任何之前页面的内容,随后绘制背景色或需要加载的新内容。
// 当HTTP响应body已经开始加载并体现在DOM上将在随后的绘制中可见时,这个方法会被调用。
// 这个回调发生在文档加载的早期,因此它的资源(css,和图像)可能不可用。
// 如果需要更细粒度的视图更新,查看 postVisualStateCallback(long, WebView.VisualStateCallback).
// 请注意这上边的所有条件也支持 postVisualStateCallback(long ,WebView.VisualStateCallback)
public void onPageCommitVisible(WebView view, String url) {
}

// 此方法废弃于API23
// 主框架加载资源时出错
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
}

// 此方法添加于API23
// 加载资源时出错,通常意味着连接不到服务器
// 由于所有资源加载错误都会调用此方法,所以此方法应尽量逻辑简单
@TargetApi(Build.VERSION_CODES.M)
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
    if (request.isForMainFrame()) {
        onReceivedError(view, error.getErrorCode(), error.getDescription().toString(), request.getUrl().toString());
    }
}

// 此方法添加于API23
// 在加载资源(iframe,image,js,css,ajax...)时收到了 HTTP 错误(状态码>=400)
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
}


// 是否重新提交表单,默认不重发
public void onFormResubmission(WebView view, Message dontResend, Message resend) {
    dontResend.sendToTarget();
}

// 通知应用可以将当前的url存储在数据库中,意味着当前的访问url已经生效并被记录在内核当中。
// 此方法在网页加载过程中只会被调用一次,网页前进后退并不会回调这个函数。
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
}

// 加载资源时发生了一个SSL错误,应用必需响应(继续请求或取消请求)
// 处理决策可能被缓存用于后续的请求,默认行为是取消请求
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
    handler.cancel();
}

// 此方法添加于API21,在UI线程被调用
// 处理SSL客户端证书请求,必要的话可显示一个UI来提供KEY。
// 有三种响应方式:proceed()/cancel()/ignore(),默认行为是取消请求
// 如果调用proceed()或cancel(),Webview 将在内存中保存响应结果且对相同的"host:port"不会再次调用 onReceivedClientCertRequest
// 多数情况下,可通过KeyChain.choosePrivateKeyAlias启动一个Activity供用户选择合适的私钥
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
    request.cancel();
}

// 处理HTTP认证请求,默认行为是取消请求
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
    handler.cancel();
}

// 通知应用有个已授权账号自动登陆了
public void onReceivedLoginRequest(WebView view, String realm, String account, String args) {
}
// 给应用一个机会处理按键事件
// 如果返回true,WebView不处理该事件,否则WebView会一直处理,默认返回false
public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
    return false;
}

// 处理未被WebView消费的按键事件
// WebView总是消费按键事件,除非是系统按键或shouldOverrideKeyEvent返回true
// 此方法在按键事件分派时被异步调用
public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
    super.onUnhandledKeyEvent(view, event);
}

// 通知应用页面缩放系数变化
public void onScaleChanged(WebView view, float oldScale, float newScale) {
}
WebChromeClient
// 获得所有访问历史项目的列表,用于链接着色。
public void getVisitedHistory(ValueCallback callback) {
}

// 
WebView使用时的一些小问题
1.WebView什么时候算是加载完成?

虽然在WebViewClient回调里面有onPageFinished()方法,可是在我们自己使用的时候就会发现,其实当onPageFinished()方法回调的时候,webview并没有完全的加载完成,而有些操作,特别是与js交互的时候又需要在网页加载完成的情况下在进行,应该怎么办呢?
来看一下当一个webview加载网页时的回调方法:

05-09 10:52:29.711 24061-24061/com.zl.webview.demo.myapplication D/MainActivity: onProgressChanged(0)
05-09 10:52:29.741 24061-24061/com.zl.webview.demo.myapplication D/MainActivity: onPageStarted
05-09 10:52:29.966 24061-24061/com.zl.webview.demo.myapplication D/MainActivity: onProgressChanged(22)
05-09 10:52:30.081 24061-24061/com.zl.webview.demo.myapplication D/MainActivity: onProgressChanged(53)
05-09 10:52:30.126 24061-24061/com.zl.webview.demo.myapplication D/MainActivity: onReceivedTitle
05-09 10:52:30.611 24061-24061/com.zl.webview.demo.myapplication D/MainActivity: onProgressChanged(100)
05-09 10:52:30.636 24061-24061/com.zl.webview.demo.myapplication D/MainActivity: onPageFinished

通过打印出来的方法顺序可以看到,确实是onPageFinished()方法是最后在回调的,那么是不是就应该把网页加载完成的处理就放在onPageFinished()方法里面呢?来再让我们看一下Android 4.4版本的手机会怎样:

05-09 10:53:17.096 24061-24061/com.zl.webview.demo.myapplication D/MainActivity: onProgressChanged(0)
05-09 10:53:17.111 24061-24061/com.zl.webview.demo.myapplication D/MainActivity: onPageStarted
05-09 10:53:17.181 24061-24061/com.zl.webview.demo.myapplication D/MainActivity: onProgressChanged(22)
05-09 10:53:17.246 24061-24061/com.zl.webview.demo.myapplication D/MainActivity: onReceivedTitle
05-09 10:53:17.381 24061-24061/com.zl.webview.demo.myapplication D/MainActivity: onProgressChanged(72)
05-09 10:53:17.436 24061-24061/com.zl.webview.demo.myapplication D/MainActivity: onProgressChanged(100)
05-09 10:53:17.466 24061-24061/com.zl.webview.demo.myapplication D/MainActivity: onPageFinished
05-09 10:53:17.512 24061-24061/com.zl.webview.demo.myapplication D/MainActivity: onPageFinished

可以看到在Android 4.4版本的手机上,onPageFinished()方法是回调了两次的,所以如果你有什么两次调用并不影响的操作,可以放到onPageFinished()方法里面,如果两次调用会影响的话,就放到onProgrossChanged()方法里面,当进度之大于等于100的时候在调用方法就好了。
是自有Android4.4版本的手机才会出现onPageFinished()方法调用两次的情况,因为在Android4.4版本之前WebView使用webkit内核,而从Android4.4版本开始改为使用chrome内核,因为是第一次使用chrome内核所以有些瑕疵,而之后的版本都还没有发现这个问题。

2.WebView标题获取的方式

说到WebView的标题,可能大多数人一下子就想到了onReciviedTitle()方法,因为这个回调方法直接就返回了网页的title,完全可以直接用。当然这样想并没有错,但是当网页内部有跳转的时候标题发生了变化,需要回退怎么办,来我们再看一下,当WebView调用了goBack()方法后,回调会怎么走:

05-09 10:55:35.983 14678-14678/com.zl.webview.demo.myapplication D/MainActivity: onProgressChanged(0)
05-09 10:55:36.003 14678-14678/com.zl.webview.demo.myapplication D/MainActivity: onPageStarted
    onProgressChanged(42)
05-09 10:55:36.033 14678-14678/com.zl.webview.demo.myapplication D/MainActivity: onProgressChanged(100)
05-09 10:55:36.053 14678-14678/com.zl.webview.demo.myapplication D/MainActivity: onPageFinished

可以看到,当WebView回退到上一个网页的时候,并不会再回调onReciviedTitel()方法,所以标题在这种情况下并不会在发生变化了,其实将标题的改变放到onPageFinished()方法里面就好了,直接调用WebView自带的getTitle()方法就行了。

3.关于WebView里面广告过滤的问题

首先我们得先弄面白为什么会在网页中出现广告,如果是前端网页设计本身就有的招租的广告位,那就没有什么大惊小怪的,但如果是自己冒出来的那就要注意了,你的网址被劫持了,一般网址被劫持费为三种情况:

  • dns劫持:
    DNS是在作为域名(www.baidu.com)和IP地址(192.168.1.1)相互映射的一个分布式数据库,就是我们的浏览器,会将域名拿到DNS去解析出ip地址来访问,DNS劫持是指在劫持的网络范围内拦截域名解析的请求,分析请求的域名,把审查范围以外的请求放行,否则返回假的IP地址或者什么都不做使请求失去响应,其效果就是对特定的网络不能反应或访问的是假网址。
    通俗讲,DNS帮我们指向了另一个地址,或者让我无法访问
  • http劫持:
    HTTP劫持是在使用者与其目的网络服务所建立的专用数据通道中,监视特定数据信息,提示当满足设定的条件时,就会在正常的数据流中插入精心设计的网络数据报文,目的是让用户端程序解释“错误”的数据,并以弹出新窗口的形式在使用者界面展示宣传性广告或者直接显示某网站的内容。
    通俗讲,你要去别人家的首页,他会给你别人家的首页,但却在别人家首页某个部位加个小广告,还可以控制关掉。。 让人无奈。现在很多免费WIFI植入广告的手段,为了博取更多点击量。
  • 第三方jar包植入广告,这种情况就没法弄了

通常情况下,网址被劫持了,将http换成https就可以了,但是有时候公司需求什么的乱七八糟的原因,不愿意使用https的网址,这是就只能够在网页加载的时候想办法过滤广告了,我这里有两种思路参考:

  1. 对加载的域名进行判断,不是公司常用的域名,就不做加载的操作
  2. 对加载的网址进行判断,如果是常见的那些个广告的域名,就不加载(在百度上可以找到常见的广告域名)
    不管用哪一种思路都需要使用到WebViewClient毁掉里面的shouldInterceptRequest()方法,不同的是,两种思路的判断不同。
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
        url = url.toLowerCase();
        if () {//在这里判断URL里面使用的是公司常见的域名或者是常见的广告的域名来京行不通的处理
            return super.shouldInterceptRequest(view, url);//正常加载
        }else{
            return new WebResourceResponse(null,null,null);//含有广告资源屏蔽请求
        }
    }
4.关于WebView的内存泄漏问题

关于这个问题,我很难给出一个清晰的描述,你在谷歌浏览器里搜 WebViewlead memory 能搜到很多结果 甚至还有给谷歌提交的issue,我也无法给出一个清晰的答案。在所有版本的手机上都会出现内存泄露,但是我自己通过Android Studio的monkey结果来看,有时,WebView内存泄露的情况还是很严重的,尤其是当你加载的页面比较复杂的时候。解决方案,我查了很多也用了很多,但是都不太理想,最后看了下微信和qq的做法,试了一下是目前效果最好的,就是在Activity销毁之前,尽可能的去释放掉WebView所持有的一些东西,并将WebView置空;还有一个方法就是另开一个进程,将WebView的加载显示相关全部都放在另一个进程里面处理,你只需要在需要交互的时候,进程之间交互就行了,不过这样对于复杂的项目来大范围的修改太过于麻烦了,需要视情况而定。

其实WebView在使用过程中,还有其他的一些问题,不过大部分问题都不麻烦,都可以轻松解决,当然更大的问题也不好解决,需要去深入的了解WebView的底层。

你可能感兴趣的:(个人知识总结:View篇--WebView)