全部產品
Search
文件中心

HTTPDNS:Android端HTTPDNS+gRPC最佳實務

更新時間:Dec 04, 2025

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

1. 背景說明

gRPC 是 Google 開源的高效能 RPC 架構,基於 HTTP/2 協議,在微服務架構中廣泛使用。它預設通過系統 DNS 解析網域名稱,同時提供了 NameResolver 擴充機制,允許開發人員自訂解析邏輯。
在 Android 端,如果你的網路架構使用的是 gRPC,就可以通過實現自訂 NameResolver,將 HTTPDNS 優雅地整合到 gRPC 中,從而替換系統 DNS 進行網域名稱解析。

說明:本方案適用於使用 grpc-okhttp 作為傳輸層的gRPC用戶端(Android推薦方式)。

2. 實現原理

gRPC的網域名稱解析流程如下:

ManagedChannel
    ↓
NameResolver (自訂存取點)
    ↓
LoadBalancer
    ↓
Transport (grpc-okhttp)
    ↓
TCP串連

通過自訂NameResolver,在網域名稱解析階段接入HTTPDNS,將解析得到的IP列表返回給gRPC,由gRPC的LoadBalancer進行負載平衡和容錯移轉。

3. 整合步驟

3.1 添加依賴

在 build.gradle 中添加gRPC依賴:

dependencies {
    // EMAS HTTPDNS
    implementation 'com.aliyun.ams:alicloud-android-httpdns:x.x.x'
    // gRPC Android
    implementation 'io.grpc:grpc-okhttp:x.x.x'
    implementation 'io.grpc:grpc-stub:x.x.x'
    implementation 'io.grpc:grpc-protobuf-lite:x.x.x'
}

3.2 初始化HTTPDNS

在Application中初始化HTTPDNS服務:

public class MyApplication extends Application {
    @Overridepublic void onCreate() {
        super.onCreate();
        
        // 初始化HTTPDNS
        InitConfig config = new InitConfig.Builder()
                .setContext(this)
                .setSecretKey("your_secret_key")  // 可選
                .setEnableCacheIp(true, 24 * 60 * 60 * 1000)  // 啟用緩衝
                .setEnableExpiredIp(true)  // 允許使用到期IP
                .setTimeoutMillis(2000)
                .build();
        
        HttpDns.init("your_account_id", config);
    }
}

3.3 實現自訂NameResolver

建立 EmasHttpDnsNameResolver 類:

import io.grpc.EquivalentAddressGroup;
import io.grpc.NameResolver;
import io.grpc.Status;
import com.alibaba.sdk.android.httpdns.HttpDns;
import com.alibaba.sdk.android.httpdns.HttpDnsService;
import com.alibaba.sdk.android.httpdns.HTTPDNSResult;
import com.alibaba.sdk.android.httpdns.RequestIpType;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;

public class EmasHttpDnsNameResolver extends NameResolver {

    private final String host;
    private final int port;
    private Listener2 listener;

    public EmasHttpDnsNameResolver(String host, int port) {
        this.host = host;
        this.port = port;
    }

    @Overridepublic String getServiceAuthority() {
        return host;
    }

    @Overridepublic void start(Listener2 listener) {
        this.listener = listener;
        resolve();
    }

    @Overridepublic void refresh() {
        resolve();
    }

    private void resolve() {
        List<EquivalentAddressGroup> addressGroups = new ArrayList<>();

        try {
            // 調用HTTPDNS解析網域名稱
            HttpDnsService httpDnsService = HttpDns.getService("your_account_id");
            HTTPDNSResult result = httpDnsService.getHttpDnsResultForHostSyncNonBlocking(
                host, 
                RequestIpType.auto
            );

            // HTTPDNS返回為空白,降級到系統DNS
            if (result == null || 
                (result.getIps() == null || result.getIps().length == 0) &&
                (result.getIpv6s() == null || result.getIpv6s().length == 0)) {
                
                InetAddress[] systemAddrs = InetAddress.getAllByName(host);
                for (InetAddress addr : systemAddrs) {
                    addressGroups.add(new EquivalentAddressGroup(
                        new InetSocketAddress(addr, port)
                    ));
                }
            } else {
                // 添加IPv4地址
                if (result.getIps() != null) {
                    for (String ip : result.getIps()) {
                        addressGroups.add(new EquivalentAddressGroup(
                            new InetSocketAddress(ip, port)
                        ));
                    }
                }
                
                // 添加IPv6地址
                if (result.getIpv6s() != null) {
                    for (String ip : result.getIpv6s()) {
                        addressGroups.add(new EquivalentAddressGroup(
                            new InetSocketAddress(ip, port)
                        ));
                    }
                }
            }

            // 返回解析結果
            ResolutionResult resolutionResult = ResolutionResult.newBuilder()
                    .setAddresses(addressGroups)
                    .build();

            listener.onResult(resolutionResult);

        } catch (Exception e) {
            listener.onError(Status.UNAVAILABLE.withDescription(
                "HTTPDNS resolve failed: " + e.getMessage()
            ));
        }
    }

    @Overridepublic void shutdown() {
        // 清理資源
    }
}

3.4 實現NameResolverProvider

建立 EmasHttpDnsResolverProvider 類:

import io.grpc.NameResolver;
import io.grpc.NameResolverProvider;
import java.net.URI;

public class EmasHttpDnsResolverProvider extends NameResolverProvider {

    private static final String SCHEME = "emashttpdns";

    @Overrideprotected boolean isAvailable() {
        return true;
    }

    @Overrideprotected int priority() {
        return 5;
    }

    @Overridepublic NameResolver newNameResolver(URI targetUri, NameResolver.Args args) {
        if (!SCHEME.equals(targetUri.getScheme())) {
            return null;
        }

        String host = targetUri.getHost();
        int port = targetUri.getPort() == -1 ? 443 : targetUri.getPort();

        return new EmasHttpDnsNameResolver(host, port);
    }

    @Overridepublic String getDefaultScheme() {
        return SCHEME;
    }
}

3.5 註冊NameResolver

在Application中註冊自訂NameResolver:

public class MyApplication extends Application {
    @Overridepublic void onCreate() {
        super.onCreate();
        
        // 初始化HTTPDNS(見3.2)
        // ...
        
        // 註冊自訂NameResolver
        NameResolverRegistry.getDefaultRegistry()
                .register(new EmasHttpDnsResolverProvider());
    }
}

3.6 使用gRPC用戶端

建立gRPC Channel時,使用自訂scheme:

// 建立Channel
ManagedChannel channel = OkHttpChannelBuilder
        .forTarget("emashttpdns://your-grpc-server.com:443")
        .overrideAuthority("your-grpc-server.com")  // 重要:保證TLS SNI正確
        .useTransportSecurity()  // 啟用TLS
        .build();

// 建立Stub並調用
YourServiceGrpc.YourServiceBlockingStub stub = 
    YourServiceGrpc.newBlockingStub(channel);

YourResponse response = stub.yourMethod(request);

4. 驗證整合

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

5. 總結

通過實現gRPC的NameResolver介面,可以優雅地將HTTPDNS整合到gRPC用戶端中,具有以下優勢:

  • 實現簡潔:只需實現NameResolver介面即可接入HTTPDNS服務

  • 通用性強:適用於各種gRPC情境,相容認證校正、SNI等安全機制

  • 高可用性:支援多IP自動負載平衡和容錯移轉,HTTPDNS解析失敗時自動降級到系統DNS

  • 效能最佳化:避免DNS劫持,提升解析速度和成功率