React Native Android端DNS SRV解析异常(Android 8及以上失效)
嘿,我刚好碰到过几乎一模一样的问题!你的核心问题是Android 8.0+系统里,React Native原生模块用dnsjava解析SRV记录返回null,但Android 7及以下完全正常,这大概率和Android 8开始收紧的网络安全策略、系统DNS解析行为变化,或是dnsjava库在高版本Android上的适配缺陷有关。下面给你一步步拆解排查和解决的方案:
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" />
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。
如果你坚持要用dnsjava,那得确保依赖和混淆配置没问题:
- 在Module级的
build.gradle里正确引入依赖:
dependencies { implementation 'dnsjava:dnsjava:3.5.2' // 用最新稳定版 }
- 在
proguard-rules.pro里添加混淆规则,避免dnsjava的类被混淆:
-keep class org.xbill.DNS.** { *; } -dontwarn org.xbill.DNS.**
在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

