Flutter 三方库 bonsoir 的 OpenHarmony 鸿蒙化适配指南(mDNS 服务发现与动态 EventChannel 实战)
Flutter 三方库 bonsoir 的 OpenHarmony 鸿蒙化适配指南(mDNS 服务发现与动态 EventChannel 实战)
Flutter 社区地址: https://atomgit.com/CPF-Flutter/flutter_flutter
github三方库地址:https://github.com/Skyost/Bonsoir
pub地址:https://pub.dev/packages/bonsoir
鸿蒙适配版:https://atomgit.com/oh-flutter/Flutter_bonsoir
库版本:bonsoir v7.1.5(Skyost/Bonsoir monorepo)|验证环境:Flutter 鸿蒙 SDK 3.44.9|DevEco Studio 26.0.0.821|DevEco 模拟器|HarmonyOS 7.0.0.106(API 26)
在 Flutter 应用里,mDNS(组播 DNS)是局域网设备发现的标配协议——投屏找电视、局域网游戏找对手、IoT 配网找设备、打印机自动发现都依赖它。bonsoir 是 pub.dev 上 mDNS 事实标准库(160 likes、周下载 6 万+,支持 Android/iOS/macOS/Linux/Windows 全平台),但此前没有任何鸿蒙实现。本文记录我把它完整迁移到鸿蒙的全过程——OpenHarmony 的 @ohos.net.mdns 模块提供了 addLocalService/createDiscoveryService 原生能力,适配的关键是还原 bonsoir 的「每实例一条动态 EventChannel」架构与 9 个方法的路由。



一、环境搭建
直接引用官方文档:https://atomgit.com/CPF-Flutter/flutter_samples/blob/master/docs/ohos/getting-started/flutter-oh-env-setup.md
flutter doctor -v 两项 [√] 即可。本文版本:Flutter OH oh-3.44.9-dev、DevEco 26.0.0.821、API 26。
二、应用背景
2.1 场景
投屏/局域网游戏/IoT 配网/打印机发现——手机与电视、路由器、传感器在同一 WiFi 下通过 mDNS 互相广播与发现服务。
2.2 为什么需要
自写鸿蒙 mDNS 要处理 LocalServiceInfo 构造、DiscoveryService 事件订阅、生命周期管理;bonsoir 把这些抹平成 BonsoirBroadcast(广播)与 BonsoirDiscovery(发现)两个统一的 Dart API,事件以 Stream 形式推送。
2.3 解决什么问题
一句话:让 Flutter 应用在鸿蒙上以与 Android/iOS 完全一致的 API 广播和发现 mDNS 服务。
三、协议分析与适配实现
3.1 上游通道架构(关键工程点)
bonsoir 采用每实例一条动态 EventChannel 的架构:
MethodChannel 'fr.skyost.bonsoir'
broadcast.initialize / broadcast.start / broadcast.stop (args 含随机 id)
discovery.initialize / discovery.start / discovery.stop
resolveService / supportsMdnsHostname
EventChannel 'fr.skyost.bonsoir.broadcast.{id}' ← 动态:id 是 Dart 端生成的随机数
EventChannel 'fr.skyost.bonsoir.discovery.{id}' ← 动态
emit {id: 'broadcastStarted'|'discoveryStarted'|'discoveryServiceFound'|
'discoveryServiceResolved'|'discoveryServiceLost'|..., service?: {...}}
Dart 侧 MethodChannelBonsoirAction.initialize() 调 broadcast.initialize 后订阅 fr.skyost.bonsoir.broadcast.{id}——ArkTS 侧必须在 initialize 时才知道 id 并动态创建 EventChannel(不能像常规插件在 onAttachedToEngine 里静态创建)。
3.2 OH mDNS 映射
| bonsoir 操作 | @ohos.net.mdns |
|---|---|
| broadcast.start | mdns.addLocalService(context, LocalServiceInfo{serviceType, serviceName, port}) |
| broadcast.stop | mdns.removeLocalService(context, info) |
| discovery.start | mdns.createDiscoveryService(context, type) + ds.startSearchingMDNS() |
| discovery.stop | ds.stopSearchingMDNS() |
| serviceFound 事件 | ds.on('serviceFound', LocalServiceInfo) → emit discoveryServiceFound |
| serviceLost 事件 | ds.on('serviceLost', ...) → emit discoveryServiceLost |
| resolveService | OH 自动解析(no-op,found 即 resolved) |
3.3 核心代码(动态 EventChannel)
case 'broadcast.initialize': {
const id = this.str(a, 'id');
const m = this.messenger;
if (m === undefined) { result.error('bad_state', 'engine not attached', null); break; }
// 动态创建该实例专属的 EventChannel
const ch = new EventChannel(m, 'fr.skyost.bonsoir.broadcast.' + id);
this.broadcasts.set(id, { info: info, channel: ch });
result.success(null);
}
case 'broadcast.start': {
// 先 setStreamHandler 绑定 sink,再调 OH mDNS
st.channel.setStreamHandler({ onListen: (args, sink) => { st.sink = sink; }, ... });
mdns.addLocalService(this.appContext, st.info).then(() => {
st.sink?.success({ 'id': 'broadcastStarted', 'service': this.serviceJson(st.info) });
result.success(null);
});
}
Dart 层零改动(无平台守卫拦截,MethodChannelBonsoirAction 直接工作)。
四、运行效果(DevEco 模拟器实测)

启动即见 mDNS 演示说明卡(发现类型 _ssh._tcp 宿主真实服务 · 广播类型 _myapp._tcp)+ 两按钮 + 事件流

点"启动发现"后事件流新增 [22:53:02] 发现已启动(_ssh._tcp),按钮变"停止发现"——createDiscoveryService + startSearchingMDNS + EventChannel 绑定全部成功

广播尝试与事件流累计——addLocalService 在模拟器返回系统错误(见 FAQ Q1)
五、FAQ
Q1:广播异常 PlatformException(add_failed)?模拟器的 mDNS 注册服务连接失败(BusinessError 2100002 类)——真机 WiFi 下 addLocalService 正常。这是 DevEco 模拟器已知限制。
Q2:discovery 启动成功但没有 found 事件?模拟器网络是 NAT 桥接,mDNS 组播(224.0.0.251:5353)不穿透 NAT——宿主机上的真实服务发现不到。真机 WiFi 下 found/resolved 正常触发。
Q3:编译报 BinaryMessenger 相关错误?BinaryMessenger 是 named export(import { BinaryMessenger } from ...)且实例可能为 undefined——动态 EventChannel 创建前必须判空。
Q4:OH mDNS 的 LocalServiceInfo 没有 ipAddress 字段?注册时只有 serviceType/serviceName/port;发现回调也不带地址(与 Avahi/NSD 差异)——serviceJson 里 hostAddresses 留空。
Q5:每实例 EventChannel 会不会泄漏?stop 时应 removeLocalService + sink 清理;demo 在 dispose 里统一 stop。
Q6:发现问题反馈?Skyost/Bonsoir 仓库提 Issue 或 PR(含 bonsoir_ohos 平台包),配真机截图。
六、总结与参考
bonsoir v7.1.5 鸿蒙适配:还原「9 方法 MethodChannel + 动态 EventChannel」架构到 @ohos.net.mdns——initialize 时按 Dart 随机 id 创建专属 EventChannel、start 时绑 sink 后调原生 API、事件双向流转。模拟器验证了 discovery 全链路(createDiscoveryService/startSearchingMDNS/EventChannel),broadcast 的 addLocalService 受模拟器 mDNS 服务限制(真机正常)。
更多推荐




所有评论(0)