Android-hybrid开发之webView所有请求拦截及修改功能

hybrid本身的意思是混合的,其实用在这里,就是指的是原生和Web开发混合起来,各展所长。

  • 最近在做Android hybrid方面的研究和开发。有一些关于WebView开发的心得体会,特分享于大家。
  • 之前我在这方面有两篇相关博客,分别介绍了Android中webview与javascript交互方法以及Android JS Debug技巧。这两篇文章对一些WebView的基本操作、使用以及调试进行了总结。
  • 今天我会对在开发Web离线包遇到的问题、对webView请求请求拦截以及调整这些方面做介绍。

hybrid离线包

因为hybrid方案使用webView加载,所以速度上有点慢,我们采用在本地使用离线包的形式、这样加载来提升速度,从而不受网络的影响。

这样做就需要使用 file:///协议来加载本地离线web页面,这样使用起来发现会导致一个问题,服务端去拿存储进去的cookie值,在大部分Android手机和部分iPhone手机拿不到,

1
2
// 加载本地文件
mWebView.loadUrl("file:///android_asset/test.html");

经过分析,发现应该是因为协议的问题,我们用的是file:///协议,而用http协议就没有这个问题。

这样的话离线就遇到这样的问题,要么服务器端做一个兼容,不用cookie,这样很麻烦。或者是使用JS调用原生方法,每一个网络请求都起客户端进行封装,这样也是要改动非常多。

最终发现webView有这样一个方法shouldInterceptRequest,这个方法会在每一个请求执行前,进行拦截,然后开发者可以任意处理后,再返回一个处理后的网络请求WebResourceResponse,这个方法真是太酷了。

  • 与是我们就可以将本地file协议先伪装成http协议,先随便请求一个网络地址,这个地址是什么不重要,只要首页一样就行了
1
private String mLocalUrlPrefix = "http://test.com/app/";
1
mWebView.loadUrl(mLocalUrlPrefix + currentVersion + "/test.html");
  • 加载后,在此处进行拦截所有的请求,然后做处理,将所有的请求全部转换为本地文件
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public WebResourceResponse shouldInterceptRequest(final WebView webView, WebResourceRequest webResourceRequest) {
final String url = webResourceRequest.getUrl().toString();
webResourceRequest.getRequestHeaders().put(HJ_USER_AGENT, RunTimeManager.instance().getUserAgent());
if (url.contains(mLocalUrlPrefix)) {
return handleRequest(url);
} else {
return getWebResourceResponse(url);
}
}
  • 其中 WebResourceResponse 主要是由三个部分组成
1
2
3
4
5
public WebResourceResponse(String mimeType, String encoding, InputStream data) {
mMimeType = mimeType;
mEncoding = encoding;
mInputStream = data;
}

其中 mimeType为请求文件的类型、encoding为文件的编码、data为文件的inputStream

mimeType这个可以根据文件后缀来映射,或者用第三方开源的工具,encoding我们一般就用utf-8,文件流就直接读取就行了。

例如这样读取:

1
2
3
4
5
6
7
8
9
10
11
if (TextUtils.equals(version, OfflineHtmlManager.DEFAULT_VERSION)) {
inputStream = AssetUtils.getFromAssets(this, fileName);
} else {
try {
String path = getApplicationContext().getCacheDir().getAbsolutePath() + "/" + fileName;
File file = new File(path);
inputStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
  • 这样就完美的将本地web页面file协议请求伪装成了http协议的请求,这样cookie的问题就解决了。

webView中的所有网络请求都要添加自定义header

  • 肯定有很多产品会希望webView中的所有网络请求都要添加自定义header,但webView只提供了一种添加header的方法
1
2
3
public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
throw new RuntimeException("Stub!");
}

但这种方法只能在url中添加,其它页面中的请求就添加不上了,那怎么办呢?

此时我们一思考就会发现,用上面shouldInterceptRequest这个拦截所有请求的方法就能解决这个问题。
我们在所有网络请求到达时,拦截,然后用http请求的方法,先添加header,然后去请求这个文件流,然后返回组装成webView需要的WebResourceResponse是不是很赞,哈哈。

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
try {
if (TextUtils.equals(method, HttpGet.METHOD_NAME)) {
httpRequest = new HttpGet(url);
} else if (TextUtils.equals(method, HttpPost.METHOD_NAME)) {
httpRequest = new HttpPost(url);
} else if (TextUtils.equals(method, HttpHead.METHOD_NAME)) {
httpRequest = new HttpHead(url);
} else if (TextUtils.equals(method, HttpPut.METHOD_NAME)) {
httpRequest = new HttpPut(url);
} else if (TextUtils.equals(method, HttpDelete.METHOD_NAME)) {
httpRequest = new HttpDelete(url);
} else if (TextUtils.equals(method, HttpTrace.METHOD_NAME)) {
httpRequest = new HttpTrace(url);
} else if (TextUtils.equals(method, HttpOptions.METHOD_NAME)) {
httpRequest = new HttpOptions(url);
}
DefaultHttpClient client = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader(MY_USER_AGENT, RunTimeManager.instance().getUserAgent());
HttpResponse httpResponse = client.execute(httpPost);
InputStream responseInputStream = httpResponse.getEntity().getContent();
String[] temp = url.split("\\.");
String suffix = temp[temp.length - 1];
String encoding = UTF_8;
// get file suffix
suffix = suffix.replace("?","-");
String[] suffixStrings = suffix.split("-");
suffix = suffixStrings[0];
// According to the suffix to get content type.
String type = Html5MimeUtil.getMimeContentType(this, Html5MimeUtil.MIME_TXT_NAME).get(suffix);
return new WebResourceResponse(type, encoding, responseInputStream);
} catch (ClientProtocolException e) {
//return null to tell WebView we failed to fetch it WebView should try again.
return null;
} catch (IOException e) {
//return null to tell WebView we failed to fetch it WebView should try again.
return null;
}

Cookie问题

  • 在使用第三方微博登录时,发现当用户没有安装微博时,微博web端会在登陆成功后清除整个应用webViewcookie,这个就导致此时我们的cookie丢失,失效的问题,怎么解决呢?
  • 其实仔细研究发现webView也为我们提供了非常有用的cookie设置和cookie读取问题

  • 我们可以首先要读取cookie,放在内存中

1
2
3
4
if (cookieManager.hasCookies()) {
mCookie = cookieManager.getCookie(LoginActivity.DAMAIN);
}
  • 然后在微博将cookie清除后,将cookie再保存进去
    1
    2
    3
    4
    5
    6
    7
    8
    private void saveCookies() {
    if (!TextUtils.isEmpty(mCookie)) {
    CookieSyncManager.createInstance(mContext);
    CookieManager.getInstance().setAcceptCookie(true);
    CookieManager.getInstance().setCookie(LoginActivity.DAMAIN, mCookie);
    CookieSyncManager.getInstance().sync();
    }
    }

这样问题就方便地解决了。

总结

  • hybrid是一种很好的方案,webView提供的功能很强大,后续有更多有好的想法会持续分享给大家。
  • 这是一个种很好的方案,两个平台解决方案都是想通的,iOS里的这个方法叫cachedResponseForRequest.
任康可 wechat
欢迎您扫一扫上面的二维码,订阅我的微信公众号!
坚持原创技术分享,您的支持将鼓励我继续创作!