最近在做一个鸿蒙端的 Flutter 项目改造,遇到了一个非常典型的问题:原本在 Android 上跑得好好的局域网设备发现功能,迁移到鸿蒙设备之后直接“沉默”了。界面里迟迟刷不出设备列表,抓日志一看,多播 DNS 查询发出去了,但没有任何响应。排查到最后,问题根源锁定在 Flutter 生态里常用的 mdns_dart 三方库上——这个库依赖的底层 Socket 行为,在鸿蒙环境下并不完全兼容。
这篇文章就把整个鸿蒙化适配过程拆开讲清楚。从 mdns_dart 的工作原理、鸿蒙网络栈的差异点,到具体的适配实现、报文收发、缓存治理,再到几个非常容易踩的坑,都会一一整理出来。如果你也在做 Flutter 应用的鸿蒙迁移,或者需要在鸿蒙上做局域网服务发现,这篇内容可以直接当作一份避坑手册来用。
1. 先搞清楚 mdns_dart 在鸿蒙上为什么不工作
1.1 mdns_dart 的工作原理
mdns_dart 是 Dart 语言实现的 mDNS(Multicast DNS)客户端库。它的核心作用,就是在局域网内通过多播方式发现服务。举个例子,智能家居 App 要发现客厅里的音箱,传统做法是让音箱跑一个 HTTP 服务,然后 App 扫描网段里的 IP 再逐个探测端口,这样又慢又容易漏。而 mDNS 的做法是让设备主动“报名字”:设备启动后在局域网内宣告 living-room-speaker._hap._tcp.local 这条记录,App 只需要发起一次多播查询,就能收到所有设备的响应。
从协议实现上看,mdns_dart 做的事情其实很简单:构造 DNS 查询报文,通过 UDP 多播发送到 224.0.0.251:5353,然后监听同一个端口接收响应报文,最后解析出服务实例名、IP、端口和 TXT 附加信息。整个过程依赖一个关键能力——发送和接收多播 UDP 数据报。
mdns_dart 底层用的是 Dart 的 RawDatagramSocket,它比普通的 DatagramSocket 更底层,允许开发者手动设置多播 TTL、加入多播组、绑定特定网卡。这个选择在 Android 和 iOS 上都没问题,因为 Flutter 引擎把 Dart 的 Socket 操作几乎原样映射到了系统调用上。
1.2 鸿蒙网络栈与 Android 的差异点
问题出在鸿蒙的 Socket 实现与 Android 并不完全一样。mdns_dart 在启动时会执行两个关键操作:一个是 socket.setMulticastTTL(255),另一个是 socket.joinMulticast(groupAddress, interface)。在 Android 上,这两个调用最终会落到 Linux 内核的 setsockopt(IP_MULTICAST_TTL) 和 setsockopt(IP_ADD_MEMBERSHIP)。而鸿蒙的分布式软总线架构在网络层做了重构,它对多播 Socket 的支持走的是一套独立的 Socket 能力接口,与 Android 的 POSIX 标准行为存在差异。
另一个直接的差异点是系统服务占用。在鸿蒙上,5353 端口有可能已经被系统组件或其他应用的多播服务占用,Dart 的 RawDatagramSocket 打开端口时如果设置了 reusePort,在 Android 上可以正常工作,但在鸿蒙早期的实现里,这个标志位的处理逻辑并不一致,导致端口绑定直接失败或者收不到数据。
还有一个容易被忽略的问题:Android 平台在应用层需要显式申请 CHANGE_WIFI_MULTICAST_STATE 权限并获取多播锁(Multicast Lock),否则 Wi-Fi 下收不到多播包。鸿蒙虽然没有完全照搬这套机制,但在部分机型、部分系统版本上,应用仍然需要在权限配置和网络会话管理上做额外处理。
1.3 到底是哪些环节断了
我当时排查问题时做了个简单的分层测试。第一步,用鸿蒙原生 API 写一个最小化多播发送程序,确认设备本身能发出多播包;第二步,在 Flutter 侧用 mdns_dart 发起查询,同时用抓包工具观察 5353 端口的数据流向。
结果发现,mdns_dart 的报文其实已经发出去了,问题出在接收链路。鸿蒙系统在接收多播数据时,会做一层基于 socket 会话的过滤,如果应用没有按照鸿蒙的接口规范绑定核心网络(Core Network)或者没有正确配置网络会话参数,多播响应包不会进入到应用的接收缓冲区。
换句话说,适配的核心不是修改 mDNS 协议逻辑,而是要把 RawDatagramSocket 这一层替换成鸿蒙原生 Socket 接口,并且保持上层 API 不变。这才是适配工作的正确打开方式——不要一上来就想着改协议解析。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 适配前的准备:方案选型与工程改造
2.1 适配思路:Fork、代理层还是自研
既然 mdns_dart 底层网络通道不兼容,摆在我面前有三条路。
第一条路是直接 Fork mdns_dart,把底层 Socket 替换成鸿蒙
