通過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劫持,提升解析速度和成功率