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.startmdns.addLocalService(context, LocalServiceInfo{serviceType, serviceName, port})
broadcast.stopmdns.removeLocalService(context, info)
discovery.startmdns.createDiscoveryService(context, type) + ds.startSearchingMDNS()
discovery.stopds.stopSearchingMDNS()
serviceFound 事件ds.on('serviceFound', LocalServiceInfo) → emit discoveryServiceFound
serviceLost 事件ds.on('serviceLost', ...) → emit discoveryServiceLost
resolveServiceOH 自动解析(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)+ 两按钮 + 事件流

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

broadcast 生命周期
广播尝试与事件流累计——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 服务限制(真机正常)。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐