全部產品
Search
文件中心

HTTPDNS:Android端HTTPDNS+Webview+本地代理最佳實務

更新時間:Nov 07, 2025

通過Android SDK接入流程這篇文檔,您已經瞭解了Android SDK匯入、配置、解析IP、應用到網路程式庫和接入驗證的完整流程,本文主要介紹WebView接入HTTPDNS的具體方案。

1. 前言

WebView是Android系統提供的網頁顯示組件,用於在應用中嵌入網頁內容。期望在WebView載入網頁時,也能使用HTTPDNS,提升網路安全性與網路效能。

本文檔針對Android WebView情境下如何使用HTTPDNS解析出的IP進行網路請求,關於HTTPDNS本身的解析服務,請先查看Android 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 的整體流量覆蓋有限。

代理方案(推薦)

在本地啟動Proxy 伺服器,使用 AndroidX WebKit 的 ProxyController 為 WebView 配置統一的 HTTP/HTTPS/WebSocket 代理。這樣可以將核心的所有流量透明轉寄到應用可控的網路棧中,實現對所有協議和要求方法的攔截與 HTTPDNS 解析,避免了 shouldInterceptRequest() 的覆蓋率瓶頸。

3. 本地代理方案(推薦方案)

3.1 方案概述

本方案通過在應用內啟動一個本地HTTPProxy 伺服器,使用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", "Proxy 伺服器啟動成功: 127.0.0.1:" + port);
            return true;
        } catch (Exception e) {
            Log.e("Proxy", "Proxy 伺服器啟動失敗", e);
            return false;
        }
    }
    
    public void stopProxyServer() {
        if (proxyServer != null) {
            proxyServer.stop();
            isRunning = false;
            Log.i("Proxy", "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. 驗證

接入完成後,請參考驗證網路程式庫驗證成功文檔,通過劫持類比或錯誤注射測試等方式,驗證整合是否成功。

6. 方案對比與總結

維度 / 方案

代理方案

攔截方案

官方支援度

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服務。

建議開發人員根據實際業務需求選擇合適的接入策略,並進行充分的測實驗證,確保在提升網路安全性的同時不影響使用者體驗。