Android 网络编程系列(3)WebView 详解

前言

在上一篇关于WebView的文章中,介绍了 WebView 的基本使用方法、WebView 页面处理和历史记录以及和 JS 调用本地代码的相关内容。今天就在上一篇文章的基础上,补充一些 WebView 的更深刻的东西。html

主要从以下几点来进行补充:前端

  1. 加载本地的 HTML 页面
  2. WebView 一些方法的讲解
  3. WebView 与 WebViewClient、WebChromeClient 的关系和做用
  4. WebViewClient 和 WebChromeCient 的方法介绍
  5. 错误码的处理
  6. 利用 WebView 来下载文件
  7. 远程注入漏洞的解决方案

加载本地 HTML 页面

前一篇文章中说到使用 WebView 须要开启 Internet 的权限,这个说法实际上是不许确的。开启权限只是由于咱们使用 loadUrl() 方法来获取 URL 的内容时须要联网。当咱们选择加载本地的 HTML 页面(好比 assets 目录下的 HTML 文件或者使用 loadData() 方法加载一段 HTML 代码),并不须要网络权限。android

加载 assets 目录下的 HTML 文件的方式和加载网页是一致的,都是使用 WebView 的 loadUrl() 方法。web

mWebView.loadUrl("file:///android_asset/index.html");复制代码

WebView 部分方法详解

上篇文章在介绍 WebView 的历史记录时,列举了部分相关的方法并作了简单的描述。这部分主要补充几个 load 方法的介绍。浏览器

void loadData (String data, String mimeType, String encoding)缓存

加载指定的 data 数据,有时须要加载的不是一个完整的网页,而是一个 HTML 片断。安全

data 表示给定编码的 String 类型 HTML 文本。mimeType 表示 data 的 MIMIE 类型,好比 “text/html”。encoding 表示 data 的编码格式。bash

这个方法中有两个地方须要注意:网络

  • JavaScript 有同源限制。意味着在此页面中运行的脚本没法访问任何不是经过 data 加载的数据。避免同源限制能够采用 loadDataWithBaseURL() 方法。
  • encoding 指定了 data 是使用 base64 编码仍是使用 URL 编码。若是 data 是使用 base64 编码的,encoding 的值必定为 “base64”,若是是其余值的编码方式(包括 null),则默认使用 ASCII 编码方式。对于超出 ASCII 范围的字符好比 ‘#’,‘%’,'\','?',应该转化为 ”%+原字符的十六进制值“ 如 ‘%23’、‘%25’、‘%27’、‘%3f’。

示例以下:异步

String htmlString = "<html><title>this is title</title><body>this is web content.</body></html>";
mWebView.loadData(htmlString, "text/html","utf-8");复制代码

void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl)

这个方法比上面那个方法只多了两个参数,一个是 baseUrl,一个是 historyUrl。baseUrl 指定了 data 数据的基准地址。咱们知道,在 HTML 代码中一般会有超连接或者图片,而这些连接或者图片地址一般使用的都是相对路径,若是没有一个基准,WebView 就没法访问到这些资源。

下面咱们在 assets 目录下模拟网站资源的相对路径,来帮助咱们更好地理解:

assets 目录下建立了 web 目录,里面有一张图片名为 logo.png

String htmlStr = "this is our logo:<img src="/logo.png"/>"
mWebView.loadData(htmlStr, "text/html","utf-8");
mWebView.loadDataWithBaseUrl("file:///android_asset/web",htmlstr,"text/html","utf-8",null);复制代码

使用 loadData() 方法没法加载图片,只有使用 loadDataWithBaseUrl() 方法才能加载。

void loadUrl(String url)

加载指定 URL 的内容。

void loadUrl(String url, Map additionalHttpHeaders)

加载指定的 URL 的内容,并携带 http header 数据。

void reload()

从新加载当前页面。页面中全部资源会从新加载。

WebSettings getSettings()

获取对这个 WebView 进行设置的 WebSettings 对象。

WebSettings 是管理 WebView 设置状态的类。当首次建立 WebView 时,它会得到一组默认设置。这些默认设置能够经过 WebSettings 的任何 getter 方法调用返回。从 WebView.getSettings() 获取的WebSettings 对象与 WebView 的使用寿命相关。若是 WebView 已被破坏,WebSettings 上的任何方法调用都将抛出一个 IllegalStateException 异常。

上一篇文章中提到 WebView 自己不支持 JavaScript,须要经过 WebSettings 来设置,即:

mWebView.getSettings().setJavaScriptEnabled(true);复制代码

这里介绍其余几个经常使用的 WebSettings 的 setter 方法,来对 WebView 进行设置。

setCacheMode(int mode)
设置是否支持缓存及缓存模式,默认值为 LOAD_DEFAULT。
setDefaultFontSize(int size)
设置默认的字体大小。有效值在 1~72 之间,默认值为 16。
setSupportZoom(boolean support)
设置是否支持缩放,默认支持。
setDisplayZoomControls(boolean enabled)
设置是否显示缩放按钮。默认值为 true。

WebView 与 WebViewClient、WebChromeClient 的关系和做用

在上篇文章中介绍了经过覆盖 WebViewClient 的shouldOverrideUrlLoading() 方法来使用当前应用展现页面,而不是经过手机浏览器来打开。这里就系统地讲讲 WebView 和 WebViewClient、WebChromeClient 的关系以及它们各自的做用。

WebView 在加载网页时会产生各类事件,并回调给应用程序以便在网页加载过程进行一些额外的处理。若是全部的工做都由 WebView 自己来完成,那么它的工做量就太大了,所以 Android 中引入了WebVIewClient 和 WebChromeClient 类来帮助 WebView 分担这些事件处理的回调。

WebView 负责加载网页以及网页渲染。
WebViewClient 的主要职责是帮助 WebView 处理各类通知、回调事件。
WebChromeClient 的做用是监听网页加载进度以及对网页标题,网页图标、JS 对话框进行处理等等。

WebViewClient 和 WebChromeCient 的方法介绍

WebViewClient 部分方法说明
//网页开始加载时的回调函数
void onPageStarted(WebView view, String url, Bitmap favicon)
//网页加载结束时的回调函数
void    onPageFinished(WebView view, String url)
//网页加载出错时的回调函数
void onReceivedError(WebView view, int errorCode, String description, String failingUrl)
//选择使用应用程序进行页面展现时须要覆盖该方法
boolean shouldOverrideUrlLoading(WebView view, String url)复制代码
WebChromeClient 部分方法说明
//接收到页面 title 时回调,获取到 title 能够设置到应用的 ActionBar 上。
void onReceivedTitle(WebView view, String title)
//接收到页面的图标时回调
void onReceivedIcon(WebView view, Bitmap icon)
//当页面加载进度发生变化时回调,能够设置一个 ProgressBar 来显示加载进度
void onProgressChanged(WebView view, int newProgress)
//当页面弹出 Javascript 警报对话框时调用
//客户端能够设置是否对这个对话框进行处理
boolean    onJsAlert(WebView view, String url, String message, JsResult result)
//当页面弹出 Javascript 确认对话框时调用
//客户端能够设置是否对这个对话框进行处理
boolean    onJsConfirm(WebView view, String url, String message, JsResult result)
//当页面弹出 Javascript 提示对话框时调用
//客户端能够设置是否对这个对话框进行处理
boolean    onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result)复制代码

错误码的处理

在浏览网页时常常会遇到 404 错误,也就是访问的页面不存在,这里就简单介绍一下如何处理 WebView 中的 404 错误。对错误码的处理须要覆盖 WebViewClient 类中的 onReceiveError() 方法,对于 404 错误,有两个经常使用的处理方法:

第一,加载一个 assets 目录下的写好的 404 页面

class MyWebViewClient extends WebViewClient{
    @Override
    public void onReceivedError(WebView view, int errorCode, 
        String description, String failingUrl) {
        view.loadUrl("file:///android_asset/404.html");
    }
}
mWebView.setWebViewClient(new MyWebViewClient());复制代码

第二,使用 Android 中的 ImageView 或者 TextVIew 来显示错误信息。

TextView tvError = (TextView) findViewById(R.id.tv_error);
class MyWebViewClient extends WebViewClient{
    @Override
    public void onReceivedError(WebView view, int errorCode, 
        String description, String failingUrl) {
        mWebView.setVisibility(View.GONE);
        tvError.setVisibility(View.VISIBLE);
        tvError.setText("something goes wrong");
    }
}
mWebView.setWebViewClient(new MyWebViewClient());复制代码

利用 WebView 下载文件

使用 WebView 进行文件下载一样有两种经常使用的方法,一种是使用隐式 Intent,调用系统浏览器进行下载任务;一种是获取到下载文件的 URL,建立线程进行异步下载。

咱们这里就简单介绍一下使用隐式 Intent 的方法来下载文件,第二种方法将在后面一篇关于 HttpUrlConnection 的文章中进行介绍。

WebView 有一个 setDownloadListener() 方法,当 URL 的内容不能被 WebView 引擎渲染时(文件),应该被下载。须要新建一个 DownloadListener 的实现类。在该实现类的 onDownloadStart() 方法中,能够获取到当前下载文件的 URL。以后就能够经过隐式 Intent 来调用系统浏览器进行下载。

class MyDownloadListener implements DownloadListener{

    @Override
    public void onDownloadStart(String url, String userAgent,
        String contentDisposition,
        String mimetype, long contentLength) {
        if(url.endsWith(".apk")){
            Uri uri = Uri.parse(url);
            Intent i = new Intent(Intent.ACTION_VIEW, uri);
            startActivity(i);
        }
    }
}
mWebView.loadUrl("http://www.wandoujia.com/");
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.setDownloadListener(new MyDownloadListener());复制代码

上述代码实现了在豌豆荚网站中下载 apk 文件的效果。

远程注入漏洞的解决方案

上篇文章中讲到了在 Android 4.2 版本以前使用 addJavascriptInterface() 方法会存在安全隐患,攻击者能够经过反射机制来对客户端进行攻击。那么在 4.2 以前,这个漏洞有没有什么解决方案呢?

答案是有的,在网上看了一些资料,找到了对应的方案。在上面的 WebChromeClient 的方法介绍时提到了一个 onJsPrompt() 方法,这个方法在 JS 调用 prompt 方法时会在本地回调。经过这个方法,JS 能把信息(文本)传递到 Java,而 Java 也能把信息(文本)传递到 JS 中。客户端和网页前端的开发者之间协商好一个小型的协议,经过 onJsPrompt() 将远程调用所需的信息按照协议封装成文本发给客户端,客户端根据协议解析这段文本,提取出所需的信息,利用反射机制进行本地的方法调用,若是有返回值就将返回值经过onJsPrompt() 传递给 JS。就可以实现 JS 远程调用 Java 代码了。

这样的解决方案我也只是只知其一;不知其二,因此只能介绍到这里,在参考资料中会有更加详细的介绍。

结束语

这篇文章结束后,对于 WebView 的一些常见的方法和这些方法背后的原理都略做介绍。因为是在学习阶段,不少知识和原理并不能作到多么深刻,可是经过写这两篇关于 WebView 的文章,去看了不少的博客,研究了一番官方文档,收获仍是很是大的。接下来就要去学习 HttpUrlConnection 的相关用法,下篇文章见。

参考文章

如下是在学习过程当中看的一些博客,不断向这些做者学习,努力深刻知识点,而后写出尽可能高质量的博客。

blog.csdn.net/leehong2005…
Android WebView 的 Js 对象注入漏洞解决方案
blog.csdn.net/typename/ar…
blog.csdn.net/typename/ar…
blog.csdn.net/typename/ar…
Android WebView 开发详解(共3篇)
www.jianshu.com/p/93cea79a2…JS 与 WebView 交互存在的一些问题

相关文章
相关标签/搜索