WebView资源拦截解析
在Android自带的WebView中,如果需要对访问的URL或者资源进行拦截,主要涉及到WebViewClient中的三个方法:onPageStarted、shouldOverrideUrlLoading、shouldInterceptRequest。 首先来分析onPageStarted方法和shouldOverrideUrlLoading方法,分别在两个方法以及onPageFinished方法中打印log。
情形一
当用户使用WebView的loadUrl方法开启一个网页时,其中onPageStarted方法会执行,而shouldOverrideUrlLoading则不会执行,如下图:
情形二
当用户继续点击网页内的链接时,onPageStarted和shouldOverrideUrlLoading均会执行,并且shouldOverrideUrlLoading要先于onPageStarted方法执行,如下图:
情形三
当用户点击网页中的链接后,点击back,返回历史网页时,onPageStarted会执行,而shouldOverrideUrlLoading不会执行,如下图:
综上所述,当需要对访问的网页进行策略控制时,需要在onPageStarted方法中进行拦截,如下示例代码:
@Override public void onPageStarted(WebView view, String url, Bitmap favicon) { Log.d(TAG, "onPageStarted url is " + url); boolean res = checkUrl(url); //根据对URL的检查结果,进行不同的处理, //例如,当检查的URL不符合要求时, //可以加载本地安全页面,提示用户退出 if (!res) { //停止加载原页面 view.stopLoading(); //加载安全页面 view.loadUrl(LOCAL_SAFE_URL); } }
然后,来分析一下shouldInterceptRequest(WebView view, String url),此方法从Android API 11(3.0)开始提供,位于WebViewClient 内,当用户使用WebView的loadUrl方法打开网页、点击网页中的链接、返回历史网页时,所有资源的加载均会调用shouldInterceptRequest方法,测试访问“http://firefly.cmbc.com.cn/appstore/apps.html”如下图:
可以看到网页中加载的html、js、css、gif、png、webp等资源,全部会调用此方法。因此,开发者可以在此方法中对网页中加载的资源进行拦截,构造WebResourceResponse对象返回,以此来加载本地缓存的资源。WebResourceResponse的构造函数如下:
1.
其中:
参数 | 说明 |
mimeType | String类型,资源的MIME类型,例如text/html,image/png,text/css |
encoding | String类型,资源Response的编码格式,例如UTF-8 |
data | InputStream类型,资源Response的输入流,不能是StringBufferInputStream |
2.
其中:
参数 | 说明 |
mimeType | String类型,资源的MIME类型,例如text/html,image/png,text/css |
encoding | String类型,资源Response的编码格式,例如UTF-8 |
statusCode | int类型,状态码,必须在[100,299][400,599]之间,3XX的不支持 |
reasonPhrase | String类型,描述状态码的语言,例如“确定”,必须是非空的 |
responseHeaders | Map类型,资源Response的头部 |
data | InputStream类型,资源Response的输入流,不能是StringBufferInputStream |
进行资源替换时,可以将网页资源,例如html、css、js、图片等存放在本地,在shouldInterceptRequest对WebView加载的资源进行拦截,当符合某种策略时,替换为本地的资源,资源的MIME类型可以采用以下方法获取:
MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(url))
获取本地资源的InputStream有两种方式:
方式一
使用打开文件的方式,获取FileInputStream,如下,替换网页中的jpg图片资源,示例代码如下:
@Override public WebResourceResponse shouldInterceptRequest(WebView view, String url) { Log.d(TAG, "shouldInterceptRequest : " + url); if (url.toLowerCase().endsWith(".jpg")) { try { String picName = url.substring(url.lastIndexOf("/")); File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/www/netease" + picName); FileInputStream fis = new FileInputStream(file); WebResourceResponse response = new WebResourceResponse( MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(url)) , "UTF-8", fis); Log.d(TAG, "shouldInterceptRequest : replace " + MimeTypeMap.getFileExtensionFromUrl(url)); return response; } catch (IOException e) { e.printStackTrace(); } } return super.shouldInterceptRequest(view, url); }
当本地文件不存在时,会抛出IOException,使用return null或者return super. shouldInterceptRequest(view, url)来返回,此时,WebView会加载原来的网络资源,这样可以不影响用户使用,增强用户体验。
方式二
@Override public WebResourceResponse shouldInterceptRequest(WebView view, String url) { Log.d(TAG, "shouldInterceptRequest : " + url); Uri uri = Uri.parse(url); String localPath = "file://" + Environment.getExternalStorageDirectory().getAbsoluteFile() + "/www" + uri.getPath(); File file = new File(localPath); try { URL localUri = new URL(localPath); if (localUri != null) { InputStream is = localUri.openConnection().getInputStream(); WebResourceResponse resourceResponse = new WebResourceResponse( MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(url)) , "UTF-8", is); Log.d(TAG, "replace " + MimeTypeMap.getFileExtensionFromUrl(url)); return resourceResponse; } } catch (IOException e) { e.printStackTrace(); } return super.shouldInterceptRequest(view, url); }
采用URLConnection的getInputStream的方式得到InputStream,注意,此时需要访问本地资源时,需要以file://开头,这样构造的URL对象才能访问到本地资源,同样的,当本地资源不存在时,会抛出IOExcetption,WebView将会加载原来的网络资源。
注意
shouldInterceptRequest方法是在非UI线程中调用的,因此,不要在此方法中进行任何UI操作。