You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native Android端DNS SRV解析异常(Android 8及以上失效)

嘿,我刚好碰到过几乎一模一样的问题!你的核心问题是Android 8.0+系统里,React Native原生模块用dnsjava解析SRV记录返回null,但Android 7及以下完全正常,这大概率和Android 8开始收紧的网络安全策略、系统DNS解析行为变化,或是dnsjava库在高版本Android上的适配缺陷有关。下面给你一步步拆解排查和解决的方案:

1. 先解决Android 8+的网络安全限制

Android 8.0(API 26)开始默认限制明文网络请求,Android 9+(API 28)更是进一步加强了网络安全规则。虽然DNS查询走的是UDP 53端口,但dnsjava底层的实现可能触发了系统的明文流量拦截,导致请求根本发不出去。

快速修复步骤:

  • 在你的React Native Android项目的res/xml目录下创建network_security_config.xml文件,内容如下:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <!-- 允许目标域名的明文流量,这里填你的SRV记录所属域名 -->
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">symlink.de</domain>
    </domain-config>
    <!-- 如果上面的域名配置不够,直接允许所有明文流量(调试阶段可用,上线建议缩小范围) -->
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>
  • 打开AndroidManifest.xml,在<application>标签里添加这个配置:
android:networkSecurityConfig="@xml/network_security_config"
  • 别忘了确认已经在Manifest里申请了网络权限:
<uses-permission android:name="android.permission.INTERNET" />
2. 替换dnsjava,改用Android原生DNS解析API

dnsjava是个老牌库,对高版本Android的适配不算友好。Android 8.0开始提供了系统原生的android.net.DnsResolver API,专门用于DNS解析,兼容性更好,还能利用系统的DNS缓存和优化。

修改你的NativeModule代码:

把SymlinkDnsSrv.java里的resolveDnsSrvRecord方法改成下面这样(注意要在后台线程执行,避免阻塞React Native主线程):

@ReactMethod
public void resolveDnsSrvRecord(String domain, Promise promise) {
    // 切换到后台线程执行DNS解析操作
    new Thread(() -> {
        try {
            // 调用Android原生DNS解析SRV记录
            List<android.net.DnsRecord> systemRecords = DnsResolver.getInstance().query(
                    getReactApplicationContext(),
                    domain,
                    DnsRecord.TYPE_SRV,
                    DnsResolver.FLAG_NO_RETRY
            ).get(); // 同步等待结果,因为已经在后台线程

            // 把系统返回的DnsRecord转换成JS能识别的数组格式
            WritableArray resultArray = Arguments.createArray();
            for (android.net.DnsRecord record : systemRecords) {
                if (record.getType() == DnsRecord.TYPE_SRV) {
                    byte[] data = record.getData();
                    ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
                    
                    // 解析SRV记录的标准格式:优先级、权重、端口、目标域名
                    int priority = buffer.getShort() & 0xFFFF;
                    int weight = buffer.getShort() & 0xFFFF;
                    int port = buffer.getShort() & 0xFFFF;
                    String target = parseCompressedDomain(buffer, data);

                    WritableMap recordMap = Arguments.createMap();
                    recordMap.putInt("priority", priority);
                    recordMap.putInt("weight", weight);
                    recordMap.putInt("port", port);
                    recordMap.putString("target", target);
                    recordMap.putString("domain", domain);
                    resultArray.pushMap(recordMap);
                }
            }

            // 切回主线程返回结果给JS
            getReactApplicationContext().runOnUiThread(() -> promise.resolve(resultArray));
        } catch (Exception e) {
            getReactApplicationContext().runOnUiThread(() -> 
                promise.reject("DNS_RESOLVE_FAILED", "Failed to fetch SRV records", e)
            );
        }
    }).start();
}

// 辅助方法:解析DNS响应中的压缩格式域名
private String parseCompressedDomain(ByteBuffer buffer, byte[] data) throws IOException {
    StringBuilder sb = new StringBuilder();
    while (true) {
        int len = buffer.get() & 0xFF;
        if (len == 0) break;
        
        // 处理DNS域名压缩(最高两位为11表示指针)
        if ((len & 0xC0) == 0xC0) {
            int offset = ((len & 0x3F) << 8) | (buffer.get() & 0xFF);
            ByteBuffer copyBuffer = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
            copyBuffer.position(offset);
            sb.append(parseCompressedDomain(copyBuffer, data));
            break;
        }
        
        byte[] label = new byte[len];
        buffer.get(label);
        sb.append(new String(label, StandardCharsets.UTF_8)).append(".");
    }
    return sb.length() > 0 ? sb.substring(0, sb.length() - 1) : "";
}

如果需要兼容Android 7及以下,可以加个版本判断:API 26及以上用系统API,低版本继续用dnsjava。

3. 检查dnsjava的依赖和混淆配置

如果你坚持要用dnsjava,那得确保依赖和混淆配置没问题:

  • 在Module级的build.gradle里正确引入依赖:
dependencies {
    implementation 'dnsjava:dnsjava:3.5.2' // 用最新稳定版
}
  • 在proguard-rules.pro里添加混淆规则,避免dnsjava的类被混淆:
-keep class org.xbill.DNS.** { *; }
-dontwarn org.xbill.DNS.**
4. 调试技巧:打印详细错误日志

在NativeModule的catch块里添加日志,能帮你精准定位问题:

catch (Exception e) {
    Log.e("SymlinkDnsSrv", "DNS lookup failed: " + e.getMessage(), e);
    promise.reject(e);
}

然后通过Android Studio的Logcat查看具体的错误信息,比如是网络不通、权限问题还是dnsjava本身的解析错误。


内容的提问来源于stack exchange,提问作者doc.aicdev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 09:00:58