最近在做的一个项目,涉及到关于WebView的监控,因此花了些时间查阅资料,并通过在页面加载完成后插入JS代码成功监控到了所需的信息,目前功能已全部完成,因此打算写下来做个总结,这一篇先记录一下对WebView的基本使用以及注意事项,下一篇详细讲述一下对WebView监控的实现。

基本使用

主要就是开启Javascript支持,设置WebViewClient和WebChromeClient等操作,下面代码中都有注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
webview = (WebView) findViewById(R.id.webview);
WebSettings setting = webview.getSettings();
setting.setJavaScriptEnabled(true);// 如果访问的页面中有JavaScript,或者需要通过js和页面交互,则必须设置支持Javascript
webview.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 重写这个方法用来指定url,比如只有特定的url才在webview里打开,否则还是启动浏览器去打开,返回true或者false
return false;
}
// 如果要显示进度条,可以通过onPageStarted和onPageFinished这两个方法实现,在onPageFinished结束即可
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
}
});
webview.setWebChromeClient(new WebChromeClient() {
// 可以通过拦截JS中的如下3个提示方法,也就是几种样式的对话框(在JS中有三个常用的对话框方法alert、comfirm、prompt),
// 得到他们的消息内容,然后解析即可。这样可达到修改弹出框样式与app风格统一,或者与本地代码进行交互的目的,
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
return super.onJsPrompt(view, url, message, defaultValue, result);
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
}
});
//webView.loadUrl(file:///android_asset/test.html);//如果是加载本地文件则用这种写法
webview.loadUrl(url); //最后调用loadUrl加载网页

最后别忘了在AndroidManifest.xml中添加网络权限:

1
<uses-permission android:name="android.permission.INTERNET" />

拦截请求

WebView 调用 loadUrl 后,会首先根据URL获取响应,然后再将响应显示到页面上,但我们可以在获取响应过程中重新改变请求URL或者直接将响应替换掉,具体的替换可以在 WebViewClient 中shouldInterceptRequest(WebView view, WebResourceRequest request)方法实现:

1
2
3
4
5
6
7
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String response = "<html><title>测试拦截</title><body>替换成了我自己的内容</body></html>";
WebResourceResponse webResourceResponse =
new WebResourceResponse("text/html", "utf-8", new ByteArrayInputStream(response.getBytes()));
return webResourceResponse;
}

这里需要注意几点:

  1. 另外还有一个同名不同参方法shouldInterceptRequest (WebView view, String url) ,但是在 API 21已经过时了,因此这里没有去覆盖了;
  2. shouldInterceptRequest 方法是在非UI线程中,因此当需要与 View 交互要小心。但是一想,既然是在非UI线程中,那么我们其实也可以在其中做网络请求,比如获取 http://www.google.com 的响应数据,将请求的百度替换为 google等;
  3. 通过拦截请求,也可以做到在网页内容中删除某些内容以使在手机上显示好看,只需将响应中的部分内容删除之后再交由WebView去显示即可,这个就跟具体的业务相关了。

JS代码与本地代码交互

在平时与WebVew打交道的过程中,其实我们或多或少都用过如下三种方式或之一来实现WebView中JS与本地代码的交互:

  1. 利用WebViewClient接口回调方法拦截url
    这种方式就是为WebVew添加 WebViewClient 回调接口,在 shouldOverrideUrlLoading 回调方法中拦截url,然后解析这个url的协议,如果发现是我们约定好的协议就开始解析参数执行具体逻辑;

  2. 利用WebChromeClient回调接口的三个方法拦截消息
    前面基本使用真的代码中也已经注释过了,为WebVew添加 添加 WebChromeClient 接口后,可以拦截JS中的几个提示方法,也就是几种样式的对话框,在JS中有三个常用的对话框方法:

    • alert 是弹出警告框,在文本里面加入\n就可以换行。
    • confirm 弹出确认框,会返回布尔值,通过这个值可以判断点击时确认还是取消。true表示点击了确认,false表示点击了取消。
    • prompt 弹出输入框,点击确认返回输入框中的值,点击取消返回null。
      这三种对话框都是可以在本地拦截到的,那么这种方法的原理就是拦截这些方法,得到他们的消息内容,然后解析即可;
  3. 通过addJavascriptInterface方法进行添加对象映射
    addJavascriptInterface方法需要两个参数,第一个参数:Android本地对象;第二个参数:JS代码中需要使用的对象。所以这里其实就相当于一个映射关系,把Android中的本地对象和JS中的对象关联即可,然后就可以通过JS对象.方法名调用到这个Android本地对象中的方法。

Android4.2 之后,为了修复之前的安全漏洞,需要为Android本地对象中的方法加上注解约束@JavascriptInterface,只有加上这个注解的方法才会被JS调用到。关于漏洞的严重性以及解决的一些办法,可以参考这篇文章 http://blog.csdn.net/leehong2005/article/details/11808557

注意如果代码进行了混淆,则需要在proguard.cfg文件中添加对JSObject 的处理,这里不再展开。

1
2
3
4
5
6
public class JSObject {
@JavascriptInterface
public void getMsg(String msg) {
//handleMsg(msg);
}
}

1
webview.addJavascriptInterface(new JSObject(), "myObj");

然后就可以在JS代码中通过myObj.getMsg(“this is a msg”)调用到JSObject中的getMsg方法了。

另外,如果是要在Android代码中调用JS方法或者注入JS代码,则可以通过loadUrl这个方法:

1
2
webView.loadUrl("javascript:jsMethod()");
webview.loadUrl("javascript:xxx")

但是,当需要注入一整个JS文件的时候,就要通过在WebViewClient的onPageFinished方法中去想办法了,可以有两种办法

  1. 当webview加载完之后,读取整个js文件中的内容,然后将整个文件内容以字符串的形式,通过webview.loadUrl(“javascript:fileContentString”)注入,代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
URL url = new URL("js文件的网络地址");
in = url.openStream();
byte buff[] = new byte[1024];
ByteArrayOutputStream fromFile = new ByteArrayOutputStream();
FileOutputStream out = null;
do {
int numread = in.read(buff);
if (numread <= 0) {
break;
}
fromFile.write(buff, 0, numread);
} while (true);
String wholeJS = fromFile.toString();
1
2
3
4
5
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
webview.loadUrl("javascript:" + wholeJS);
}
  1. 页面加载完之后,直接向webview对应的html中加入script便签,并包含要注入的JS的url地址,代码如下所示。当注入完JS之后想要立即调用其中的方法,可以为加入的script标签添加onload事件,确保该script已加载完成之后,在onload方法里调用JS文件中的方法
1
2
3
4
String js = "var script = document.createElement('script');" +
" script.src = 'http://locahost:8080/xxx/xxx.js';" +
" script.onload = function() { xxx(); };" +
" document.body.appendChild(script);";
1
2
3
4
5
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
webview.loadUrl("javascript:" + js);
}

WebView缓存

WebView的缓存可以分为页面缓存和数据缓存。

  1. 页面缓存是指加载一个网页时的html、JS、CSS等页面或者资源数据,这些缓存资源是由于浏览器的行为而产生,开发者只能通过配置HTTP响应头影响浏览器的行为才能间接地影响到这些缓存数据。他们的索引存放在/data/data/package_name/databases下。他们的文件存放在/data/data/package_name/cache/xxxwebviewcachexxx下。文件夹的名字在2.x和4.x上有所不同,但都文件夹名字中都包含webviewcache;

  2. 数据缓存分为两种:AppCache和DOM Storage(Web Storage)。他们是因为页面开发者的直接行为而产生。所有的缓存数据都由开发者直接完全地掌控。

AppCache使我们能够有选择的缓存web浏览器中所有的东西,从页面、图片到脚本、css等等。尤其在涉及到应用于网站的多个页面上的css和JavaScript文件的时候非常有用。其大小目前通常是5M,在WebView上需要手动开启(setAppCacheEnabled),并设置路径(setAppCachePath)和容量(setAppCacheMaxSize)

如果需要存储一些简单的用key/value对即可解决的数据,DOM Storage是非常完美的方案。根据作用范围的不同,有Session Storage和Local Storage两种,分别用于会话级别的存储(页面关闭即消失)和本地化存储(除非主动删除,否则数据永远不会过期)。在WebView中可以手动开启DOM Storage(setDomStorageEnabled)并设置存储路径(setDatabasePath)

假设activity中已经有了一个WebView的实例对象,可以参考如下代码最WebSettings 进行设置:

1
2
3
4
5
6
7
8
9
WebSettings webseting = webview.getSettings();
setting.setJavaScriptEnabled(true);
webseting.setDomStorageEnabled(true);
webseting.setAppCacheMaxSize(1024*1024*8); //设置缓冲大小,这里设的是8M
String appCacheDir = getApplicationContext().getCacheDir().getAbsolutePath();
webseting.setAppCachePath(appCacheDir);
webseting.setAllowFileAccess(true);
webseting.setAppCacheEnabled(true);
webseting.setCacheMode(WebSettings.LOAD_DEFAULT);

处理WebView中页面需要的其他权限

比如从安卓5.0起WebView开始支持WebRTC,如果用WebView加载一个通过WebRTC进行音视频通讯的页面,我们会发现即使Manifest当中请求了摄像头和麦克风权限,并且系统中也已授予,但还是无法调用到,页面无法显示内容,这时候我们其实还需要重写WebChromeClient中的一个方法:

1
2
3
public void onPermissionRequest(PermissionRequest request) {
request.grant(request.getResources());
}

错误处理

由于服务器各种原因导致出错,最平常的比如404错误,通常情况下浏览器会显示一个错误提示页面,用来提示用户页面出错了,但这个页面一般比较简陋,也有可能与我们的页面风格不搭, 这个时候我们就需要为WebView加载一个本地的错误提示页面。

  1. 首先需要编写一个html文件,比如error.html,用来出错的时候展示给用户,然后将该文件放置到代码根目录的assets文件夹下;

  2. 随后我们需要复写WebViewClient的onRecievedError方法,该方法传回了错误码,根据错误类型可以进行不同的错误分类处理。

1
2
3
4
5
6
7
8
9
10
11
webview.setWebViewClient(new WebViewClient(){
@Override
public void onReceivedError(WebView view, int errorCode,
String description, String failingUrl) {
switch(errorCode) {
case HttpStatus.SC_NOT_FOUND:
view.loadUrl("file:///android_assets/error.html");
break;
}
}
});

当然,出错的时候也可以选择隐藏掉webview,而显示native的错误处理控件,这个也只需要在onReceivedError进行相应的处理即可。

处理返回键监听

1
2
3
4
5
6
7
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {
webView.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}

参考链接:

http://blog.csdn.net/jiangwei0910410003/article/details/52687530
http://www.cnblogs.com/rayray/p/3680500.html