WebView 实践

最近更新时间:2024-11-04 15:58:31

我的收藏
Android 系统提供了 API 以实现 WebView 中的网络请求拦截与自定义逻辑注入。我们可以通过该 API 拦截 WebView 的各类网络请求,截取 URL 请求的 HOST,调用 HTTPDNS 解析该 HOST,通过得到的 IP 组成新的 URL 来进行网络请求。示例如下:
注意:
由于 shouldInterceptRequest(WebView view, WebResourceRequest request) 中 WebResourceRequest 没有提供请求 body 信息,所以只能成功拦截 get 请求,无法拦截 post 请求。
WebSettings webSettings = mWebView.getSettings();
// 使用默认的缓存策略,cache 没有过期就用 cache
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
// 加载网页图片资源
webSettings.setBlockNetworkImage(false);
// 支持 JavaScript 脚本
webSettings.setJavaScriptEnabled(true);
// 支持缩放
webSettings.setSupportZoom(true);
mWebView.setWebViewClient(new WebViewClient() {
// API 21及之后使用此方法
@SuppressLint("NewApi")
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
if (request != null && request.getUrl() != null && request.getMethod().equalsIgnoreCase("get")) {
String scheme = request.getUrl().getScheme().trim();
String url = request.getUrl().toString();
Log.d(TAG, "url:" + url);
if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")) {
try {
URL oldUrl = new URL(url);
URLConnection connection = oldUrl.openConnection();
// 获取 HTTPDNS 域名解析结果
String ips = MSDKDnsResolver.getInstance().getAddrByName(oldUrl.getHost());
String[] ipArr = ips.split(";");
if (2 == ipArr.length && !"0".equals(ipArr[0])) { // 通过 HTTPDNS 获取 IP 成功,进行 URL 替换和 HOST 头设置
String ip = ipArr[0];
String newUrl = url.replaceFirst(oldUrl.getHost(), ip);
connection = (HttpURLConnection) new URL(newUrl).openConnection(); // 设置 HTTP 请求头 Host 域名
connection.setRequestProperty("Host", oldUrl.getHost());
}
String contentType = connection.getContentType();
Log.d(TAG, "contentType:" + connection.getContentType());
if (contentType != null) {
String mimeType = contentType.split(";")[0];
// encoding 可从 contentType 中获取,demo 中不展开处理
String encoding = "UTF-8";
return new WebResourceResponse(mimeType, encoding, connection.getInputStream());
}
return null;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}

// API < 21时,shouldInterceptRequest(WebView view, String url) 仅能获取 URL,无法获取请求方法、头部信息等,拦截请求可能无法获取正确响应。放弃拦截。
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
return null;
}
});
// 加载web资源
mWebView.loadUrl(targetUrl);
若拦截资源返回重定向:
需发起二次请求;
原请求资源中存在cookie时,需进行cookie管理,获取请求cookie重新写入redirect请求。
cookieManager.removeAllCookies(null);
// Cookie获取
List<String> cookies = conn.getHeaderFields().get("Set-Cookie");
if (cookies != null) {
for (String cookieStr: cookies) {
if (!TextUtils.isEmpty(cookieStr)) {
// cookie值若带domain,需与url一致才能写入成功
cookieManager.setCookie(url, cookieStr.split(";")[0]); }
}
}

if (needRedirect(code)) {
String location = conn.getHeaderField("Location"); if (location == null) {
location = conn.getHeaderField("location"); }
if (location != null) { if (!(location.startsWith("http://") || location.startsWith("https://"))) { // 补全重定向完整url URL originalUrl = new URL(url); location = originalUrl.getProtocol() + "://" + originalUrl.getHost() + location; } Log.e(TAG, "location: " + location + "; path: " + path);

// 发起二次请求
// Cookie写入
conn.setRequestProperty("Cookie", cookieManager.getCookie(location));
...
} else {
// 无法获取location信息,放弃拦截
return null; } } else { Log.e(TAG, "redirect finish"); ... }
注意
拦截 HTTPS 请求中涉及证书校验与 SNI 问题处理,请参见 HttpURLConnection 接入