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