本文档介绍在Android客户端上使用Webview接入HTTPDNS的方案。
1. 前言
通过Android SDK接入,您已经了解如何在Android平台的Native场景中使用HTTPDNS,实现防劫持、调度精准、解析及时生效的能力。而Android上还有一个频繁发生网络请求的场景:WebView。
WebView是Android系统提供的网页显示组件,用于在应用中嵌入网页内容。期望在WebView加载网页时,也能使用HTTPDNS,提升网络安全性与网络性能。
本文档针对Android WebView场景下如何使用HTTPDNS解析出的IP进行网络请求,关于HTTPDNS本身的解析服务,请先查看上述SDK接入文档。
2. 技术现状与挑战
2.1 Android WebView的网络请求限制
在 Android 中,WebView 依赖系统内置的浏览器内核(基于 Chromium)加载网页,其网络请求链路与应用自身的网络库完全独立,域名解析也走内核自带的 DNS 流程,很多请求无法在 Java 层直接拦截,这种封闭性为接入 HTTPDNS 带来了天然限制。
2.2 技术方案演进
随着Android系统和WebView的不断发展,WebView集成HTTPDNS的技术方案也在持续演进:
拦截方案:
在 shouldInterceptRequest()
回调中拦截资源请求,交由应用内网络库(如 OkHttp)重新发起,并在其中集成 HTTPDNS 解析,从而绕过系统 DNS。
该方法实现简单,对部分 GET 请求资源和接口可生效,但存在明显局限:只能获取 GET 请求的完整信息,POST 等方法的请求体不会暴露,因此对 WebView 的整体流量覆盖有限。
代理方案(推荐):
在本地启动代理服务器,使用 AndroidX WebKit 的 ProxyController
为 WebView 配置统一的 HTTP/HTTPS/WebSocket 代理。这样可以将内核的所有流量透明转发到应用可控的网络栈中,实现对所有协议和请求方法的拦截与 HTTPDNS 解析,避免了 shouldInterceptRequest()
的覆盖率瓶颈。
3. 本地代理方案(推荐方案)
3.1 方案概述
本方案通过在应用内启动一个本地HTTP代理服务器,使用AndroidX WebKit的ProxyController将WebView的所有网络请求路由到本地代理。在代理层面实现HTTPDNS域名解析,然后将请求转发到真实服务器。
工作流程:
3.2 依赖集成
在 app/build.gradle
中添加依赖:
// 依赖版本需要按实际情况调整
dependencies {
implementation 'androidx.webkit:webkit:1.7.0'
implementation 'com.aliyun.ams:alicloud-android-httpdns:2.6.5'
implementation 'io.github.littleproxy:littleproxy:2.4.4'
implementation 'io.netty:netty-all:4.1.42.Final'
implementation 'com.google.guava:guava:30.1.1-android'
implementation 'org.slf4j:slf4j-android:1.7.30'
}
3.3 接入步骤
集成HTTPDNS WebView代理需要3个核心步骤:
步骤1:初始化HTTPDNS
在Application中初始化HTTPDNS服务:
public class WebviewProxyApplication extends Application {
public static String accountId = "your_account_id";
public static String secretKey = "your_secret_key"; // 可选
@Override
public void onCreate() {
super.onCreate();
initHttpDns();
}
private void initHttpDns() {
try {
InitConfig config = new InitConfig.Builder()
.setContext(this)
.setTimeout(5000)
.setEnableCacheIp(true)
.setEnableExpiredIp(true)
.setSecretKey(secretKey) // 可选,用于鉴权
.build();
HttpDns.init(accountId, config);
Log.i("HTTPDNS", "初始化成功");
} catch (Exception e) {
Log.e("HTTPDNS", "初始化失败", e);
}
}
}
步骤2:启动LittleProxy代理服务
创建ProxyService管理代理服务器:
public class ProxyService extends Service {
private HttpProxyServer proxyServer;
private HttpDnsResolver httpDnsResolver;
private boolean isRunning = false;
public boolean startProxyServer() {
if (isRunning) return true;
try {
// 创建HTTPDNS解析器
httpDnsResolver = new HttpDnsResolver(this);
// 启动LittleProxy服务器
int port = generateRandomPort();
proxyServer = DefaultHttpProxyServer.bootstrap()
.withPort(port)
.withAddress(new InetSocketAddress("127.0.0.1", port))
.withServerResolver(httpDnsResolver) // 使用HTTPDNS解析器
.withConnectTimeout(10000)
.withIdleConnectionTimeout(30000)
.start();
isRunning = true;
Log.i("Proxy", "代理服务器启动成功: 127.0.0.1:" + port);
return true;
} catch (Exception e) {
Log.e("Proxy", "代理服务器启动失败", e);
return false;
}
}
public void stopProxyServer() {
if (proxyServer != null) {
proxyServer.stop();
isRunning = false;
Log.i("Proxy", "代理服务器已停止");
}
}
public boolean isProxyRunning() {
return isRunning;
}
}
// HTTPDNS解析器实现
class HttpDnsResolver implements HostResolver {
private HttpDnsService httpDnsService;
public HttpDnsResolver(Context context) {
httpDnsService = HttpDns.getService(WebviewProxyApplication.accountId);
}
@Override
public InetSocketAddress resolve(String host, int port) throws UnknownHostException {
try {
// 使用HTTPDNS解析
HTTPDNSResult result = httpDnsService.getHttpDnsResultForHostSyncNonBlocking(
host, RequestIpType.auto);
if (result != null && result.getIps() != null && result.getIps().length > 0) {
String ip = result.getIps()[0];
Log.d("DNS", "HTTPDNS解析: " + host + " -> " + ip);
return new InetSocketAddress(ip, port);
}
// 降级到系统DNS
return new InetSocketAddress(InetAddress.getByName(host), port);
} catch (Exception e) {
Log.w("DNS", "解析失败,使用系统DNS: " + host);
return new InetSocketAddress(InetAddress.getByName(host), port);
}
}
}
步骤3:配置WebView代理
在Activity中配置WebView使用本地代理:
public class MainActivity extends AppCompatActivity {
private WebView webView;
private ProxyService proxyService;
private boolean isProxyRunning = false;
// 主线程Executor用于ProxyController
private final Executor mainExecutor = ContextCompat.getMainExecutor(this);
private void configureWebViewProxy() {
// 检查系统是否支持代理配置
if (!WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
Log.w("WebView", "当前系统不支持代理配置");
return;
}
if (isProxyRunning) {
String proxyAddress = proxyManager.getProxyAddress();
// 配置WebView使用本地代理
ProxyConfig proxyConfig = new ProxyConfig.Builder()
.addProxyRule(proxyAddress, ProxyConfig.MATCH_HTTP)
.addProxyRule(proxyAddress, ProxyConfig.MATCH_HTTPS)
.addDirect(ProxyConfig.MATCH_ALL_SCHEMES) // 其他协议直连
.build();
ProxyController.getInstance().setProxyOverride(proxyConfig, mainExecutor, () -> {
Log.i("WebView", "代理配置成功");
});
} else {
// 清除代理配置
ProxyController.getInstance().clearProxyOverride(mainExecutor, () -> {
Log.i("WebView", "代理配置已清除");
});
}
}
private void loadUrl(String url) {
// 确保代理配置生效后再加载URL
configureWebViewProxy();
webView.loadUrl(url);
}
}
3.4 完整实现参考
考虑创建本地代理服务、集成HTTPDNS、配置Webview等步骤有一定实现成本,我们在GitHub上开源了一份完整实现:HTTPDNS Webview Demo。
核心组件包括:
HttpDnsResolver: 实现LittleProxy的HostResolver接口,集成HTTPDNS解析
ProxyService: Android Service管理代理服务器生命周期
ProxyManager: 代理状态管理和服务绑定
MainActivity: UI交互和WebView代理配置
方案内置多层降级机制确保稳定性:系统版本不支持时自动跳过ProxyController配置,HTTPDNS解析失败时自动降级到系统DNS,解析得到IP不可用时自动切换等。
4. 传统拦截方案
对于不支持ProxyController的旧版本系统(WebView < 72),可以使用传统的拦截方案作为降级策略。
4.1 方案概述
传统拦截方案通过重写WebViewClient.shouldInterceptRequest()
方法,拦截WebView的网络请求,使用集成了HTTPDNS的OkHttp客户端重新发起请求。
主要特点:
仅支持GET请求拦截
需要手动处理Cookie等
实现复杂度较高,维护成本大
兼容性好,支持较低版本Webview
4.2 详细实现
由于传统拦截方案涉及较多技术细节和边缘情况处理,完整的实现代码和使用说明请参考:Android端HTTPDNS+OkHttp最佳实践。
5. 方案对比与总结
维度 / 方案 | 代理方案 | 拦截方案 |
官方支持度 | Google在AndroidX WebKit中引入的正式API,完全公开、长期可用 | 使用公开WebViewClient API,但功能受限于GET请求 |
生效版本 | Android 5.0+ WebView 72+ | Android 5.0+ |
协议覆盖 | HTTP/HTTPS/WebSocket | 仅限HTTP/HTTPS GET请求 |
实现复杂度 | 中:需实现本地代理、端口管理、双向转发 | 中:需集成OkHttp,处理请求重建和DNS解析 |
对业务代码侵入 | 低:WebView只需配置代理 | 低:仅需替换WebViewClient实现 |
Cookie/缓存/CORS | 代理层透明,不额外处理 | 开发者需手动处理Cookie |
维护成本 | 低:依赖官方API,版本升级风险小 | 低:基于稳定的WebViewClient API |
失效降级策略 | 支持:代理故障可回退到系统网络 | 需自行实现 |
推荐场景 | 现代应用开发,需要完整协议支持 | 快速集成,仅需处理GET请求的场景 |
综合考虑稳定性、开发维护成本和未来趋势,强烈推荐使用本地代理方案。该方案能够处理完整的各种请求类型,具备完整的协议支持、透明的集成体验和优雅的降级机制,能以最低成本为用户提供稳定可靠的HTTPDNS服务。
建议开发者根据实际业务需求选择合适的接入策略,并进行充分的测试验证,确保在提升网络安全性的同时不影响用户体验。