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 没有过期就用 cachewebSettings.setCacheMode(WebSettings.LOAD_DEFAULT);// 加载网页图片资源webSettings.setBlockNetworkImage(false);// 支持 JavaScript 脚本webSettings.setJavaScriptEnabled(true);// 支持缩放webSettings.setSupportZoom(true);mWebView.setWebViewClient(new WebViewClient() {// API 21及之后使用此方法@SuppressLint("NewApi")@Overridepublic 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,无法获取请求方法、头部信息等,拦截请求可能无法获取正确响应。放弃拦截。@Overridepublic 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"); ... }
注意