两台鸿蒙设备从"素不相识"到"互相发现",中间发生了什么?本文带你从应用层一路追到蓝牙芯片,看清发现的每一步。

不是概览,是源码级的深挖——但尽量讲得有意思。

为什么需要"发现"?

分布式软总线的三大能力是发现、组网、传输。发现是第一条链,没有发现就没有后续一切。

但"发现"这件事比想象中复杂。设备之间的通信方式五花八门——Wi-Fi、蓝牙、BLE、USB、NFC——每种方式的发现机制完全不同。如果让业务层自己去适配每种链路的发现 API,代码量爆炸。

软总线发现模块要解决的问题就是:屏蔽链路差异,提供统一的"发布-订阅"发现能力。 业务只管说"我要找有 XX 能力的设备",至于底层走 BLE 还是 Wi-Fi,不用管。

四层架构:从应用到芯片的发现之旅

发现模块内部是四层架构,数据从上往下走是发布,从下往上走是发现:

┌─────────────────────────────────────────────────┐
│  应用层                                          │
│  PublishLNN() / RefreshLNN()                    │
└────────────────────┬────────────────────────────┘
                     │ 策略模式:按媒介分发
                     ▼
┌─────────────────────────────────────────────────┐
│  L1: Discovery Manager                          │
│  发布/订阅生命周期 · 四媒介调度 · 能力位图分发      │
│  g_publishInfoList / g_discoveryInfoList        │
│  g_capabilityList[bitmap]                       │
└────────────────────┬────────────────────────────┘
                     │ 函数指针接口
                     ▼
┌─────────────────────────────────────────────────┐
│  L2: 媒介实现层                                  │
│  BLE: 广播/扫描 · TLV编解码 · 消息驱动            │
│  CoAP: Wi-Fi组播 · HTTP风格请求                  │
│  USB / NFC                                      │
└────────────────────┬────────────────────────────┘
                     │ 适配接口
                     ▼
┌─────────────────────────────────────────────────┐
│  L3: 硬件适配层                                  │
│  广播通道池[21] · 扫描通道池[4] · 协议策略分发     │
└────────────────────┬────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────┐
│  L4: OHOS 系统蓝牙/Wi-Fi 框架                    │
│  BleStartAdvEx / BleStartScanEx / CoAP*        │
└─────────────────────────────────────────────────┘

每一层职责分明:

层干什么关键文件
L1管理所有发布/订阅的生命周期,按媒介分发请求,按能力位图分发发现结果disc_manager.c
L2各媒介的具体发现逻辑(BLE 广播/扫描、CoAP 组播等)disc_ble.c / disc_coap.c
L3管理硬件通道池,处理协议差异broadcast_mgr.c
L4调用系统蓝牙/Wi-Fi API,和硬件打交道OHOS BT Framework

四种媒介:各显神通

发现支持四种媒介,设为 AUTO 时 BLE + CoAP 同时跑,任一成功即可:

BLE — 近场发现主力

蓝牙低功耗广播+扫描。设备 A 通过 BLE 广播自己的能力信息,设备 B 扫描到广播包后解析出 A 的信息。

优点:功耗极低,不需要 Wi-Fi 网络,手机和平板都支持。
缺点:广播包大小有限(31 字节),大数据需要 GATT 连接后续传。

CoAP — 局域网快速发现

基于 Wi-Fi 的 CoAP(Constrained Application Protocol)协议。设备在局域网内组播自己的信息,其他设备收到组播包后解析。

优点:速度快,带宽大,能携带更多数据。
缺点:需要设备在同一 Wi-Fi 局域网内。

USB — 有线安全发现

USB 连接枚举发现。设备通过 USB 物理连接后,在枚举阶段交换信息。

优点:安全可靠,需要物理连接。
缺点:需要有线连接,场景受限。

NFC — 碰一碰发现

NFC 近场通信发现。两台设备靠近触碰时交换信息。

优点:极简交互,碰一下就行。
缺点:需要 NFC 硬件支持,距离极近。

AUTO 模式

medium = DM_AUTO 时,L1 会同时启动 BLE 和 CoAP 两条发现通道,哪条先成功就用哪条。这是最常用的模式——在有 Wi-Fi 的环境下走 CoAP 快速发现,没有 Wi-Fi 时退化为 BLE 发现。

发布-订阅:电台与收音机

发现模块的核心交互模式是发布-订阅,像广播电台和收音机:

发布端(电台)

设备调 PublishLNN 发布自己的能力:

PublishLNN(pkgName, publishInfo, publishCb)

publishInfo 里填什么?

字段说明常用值
publishId发布标识,停止时用自定义整数
mode主动/被动DISCOVER_MODE_ACTIVE
medium媒介AUTO / BLE / COAP
freq频率LOW / MID / HIGH
capability能力字符串"ddmpCapability"
ranging是否测距true

发布后,软总线在后台持续广播,直到调 StopPublishLNN 停止。

订阅端(收音机)

设备调 RefreshLNN 订阅感兴趣的能力:

RefreshLNN(pkgName, subscribeInfo, refreshCb)

subscribeInfo 里填你要找什么能力、用什么媒介、什么频率。然后等回调 OnDeviceFound 把发现的设备推给你。

能力匹配

发现是按"能力"匹配的。发布端说"我有 ddmpCapability",订阅端说"我要找 ddmpCapability",两边对上了就发现成功。

能力不止一个,软总线用能力位图来高效管理。

能力位图:位运算的高效匹配

软总线用位图(bitmap)表示设备能力,每个能力占一位:

位能力含义
2CASTPLUS投屏能力
4DVKITDV 设备能力
6OSD分布式屏幕能力
7SHARE共享能力

发现匹配时不用字符串比较,直接按位与:

if (publishCapability & subscribeCapabilityMask) {
    // 能力匹配,上报发现结果
}

BLE 模块只关心部分能力,用 g_concernCapabilityMask = CAST | DVKIT | OSD 过滤掉不关心的。

L1 维护一个 g_capabilityList[bitmap] 数组,发现到设备后遍历所有订阅了对应能力的业务,逐一分发。这是观察者模式——发布者不关心谁订阅了,管理器负责按能力位图分发。

BLE 发现深挖:广播、扫描与 TLV

BLE 是最常用的发现媒介,也是实现最复杂的。深入看看。

广播:把信息塞进 31 字节

BLE 广播包最大只有 31 字节,要塞下设备 ID、能力信息、距离标识等,必须紧凑编码。软总线用 TLV(Type-Length-Value) 格式编码:

[T][L][V][T][L][V]...

每个字段 3 字节起步:1 字节类型 + 1 字节长度 + N 字节数据。disc_ble_utils.c 负责编码和解码。

如果 31 字节塞不下(比如设备名太长),先放关键信息,剩余的通过 GATT 连接后续获取。

扫描:过滤与解析

设备 B 在扫描时,会收到周围所有的 BLE 广播包。不是每个包都是软总线的——可能是蓝牙耳机、可能是 Beacon。所以需要过滤:

收到广播包
  → 解析 TLV
  → 检查是否有软总线标识
  → 检查能力是否匹配
  → 通过 → 上报 OnDeviceFound
  → 不通过 → 丢弃

消息驱动:Handler-Looper 模型

BLE 发现模块内部不用线程直接操作硬件,而是用 Handler-Looper 模型:

外部接口调用
  → PostMessage(PUBLISH_ACTIVE_SERVICE)
  → 消息入队
  → Looper 串行处理
  → DiscBleMsgHandler 分发到具体处理函数

共定义了 16 种消息类型(DiscBleMessage),包括发布、扫描、停止、能力更新等。

为什么不用多线程? BLE 硬件资源有限,并发操作广播通道会冲突。用 Looper 串行处理,天然避免了竞态问题。

通道池:21 个广播 + 4 个扫描

L3 适配层管理着固定大小的通道池:

  • 广播通道:21 个(g_advChannel[21])
  • 扫描通道:4 个(g_scanChannel[4])

通道池大小固定,不可动态扩展。多个业务同时发布时,从池里分配通道;停止时归还。如果池满了,新的发布请求会被拒绝。

关键参数

参数值含义
广播间隔48msBLE 广播包发送间隔
消息超时6000msBLE 消息处理超时
广播启停间隔50ms启停广播之间的最小间隔

48ms 的广播间隔是个权衡——太快费电,太慢发现延迟高。BLE 规范建议 20ms~10240ms,48ms 偏快,优先保证发现速度。

端到端数据流:一次发现的完整旅程

发布流程(设备 A 发布能力)

应用调 PublishLNN(pkg, info)
  │
  ▼ L1: DiscMgr
  InnerPublishService()
  → AddDiscInfoToPublishList()        // 加入发布列表
  → CallInterfaceByMedium(BLE)        // 策略模式:按媒介分发
  │
  ▼ L2: DiscBle
  BleStartActivePublish()
  → PostMessage(PUBLISH_ACTIVE_SERVICE)  // 不直接操作硬件
  │
  ▼ Looper
  DiscBleMsgHandler()
  → StartActivePublish()
  → StartAdvertiser()                 // 从通道池分配广播通道
  │
  ▼ L3: BroadcastAdapter
  RegisterBroadcaster()
  → StartBroadcasting()
  → SoftbusStartAdv()
  │
  ▼ L4: OHOS BT
  BleStartAdvEx()                     // 调系统蓝牙 API,芯片开始广播

发现流程(设备 B 扫描到 A)

OHOS BT 芯片收到广播包
  │
  ▼ L4 → L3
  WrapperScanResultCb()
  → ParseScanResult(TLV)              // 解析 TLV 编码
  → BcReportScanDataCallback()
  │
  ▼ L2: DiscBle
  BleScanResultCallback()
  → ScanFilter()                      // 过滤非软总线广播
  → ProcessDistributePacket()
  → GetDeviceInfoFromDisAdvData()     // TLV 解码出设备信息
  → RangeDevice()                     // 测距(如果启用)
  │
  ▼ L1: DiscMgr
  DiscOnDeviceFound()
  → 遍历 g_capabilityList[bitmap]     // 按能力位图分发
  │
  ▼ 应用层
  OnDeviceFound(device)               // 回调业务

从芯片收到广播包到业务回调,经过 4 层处理,每层职责清晰:L3 解析协议,L2 过滤解码,L1 分发,应用层收结果。

设计模式:发现模块的工程智慧

发现模块用了好几种经典设计模式,值得品味:

策略模式:四媒介调度

L1 用 DiscoveryFuncInterface 函数指针数组实现策略模式:

CallInterfaceByMedium(BLE)  → DiscBle 的实现
CallInterfaceByMedium(COAP) → DiscCoap 的实现
CallInterfaceByMedium(USB)  → DiscUsb 的实现

新增媒介只需注册一套实现,L1 调度代码不用改。

观察者模式:能力位图分发

L1 维护 g_capabilityList[bitmap],每个能力位对应一组订阅者。发现到设备后,按能力位图遍历订阅者,逐一分发。发布者不关心谁订阅了,解耦干净。

Handler-Looper:串行消息驱动

L2 BLE 模块用消息队列驱动,外部接口只 PostMessage,Looper 串行处理。天然避免了多线程竞态,BLE 硬件操作不会冲突。

通道池:固定大小资源管理

L3 用固定大小的通道池管理广播/扫描通道,避免动态分配的开销和碎片。池满了直接拒绝,简单粗暴但可靠。

引用计数:能力生命周期

L2 用 capCount[pos] 引用计数管理能力。多个业务订阅同一能力时,计数累加;取消订阅时递减;减到 0 才真正停止广播。避免了一个业务取消订阅就把广播停了,影响其他业务。

线程安全:回调不能持锁

发现模块有一条铁律:回调不能持锁。

L1 的 DiscOnDeviceFound 持有 discovery 锁来遍历订阅者列表,但在调用 OnDeviceFound 回调之前必须先解锁。为什么?因为回调里可能调管理器接口(比如查设备信息),如果持锁回调就会死锁。

L3 更谨慎——先拷贝回调函数指针到栈变量,解锁后再用栈变量调用。这样即使回调里出问题,也不影响锁的状态。

资源管理:发现模块的洁癖

发现模块对资源管理很严格,有五条清理机制:

机制触发条件清理什么
引用计数归零capCount 减到 0停止对应能力的广播
消息超时6 秒未处理丢弃超时消息
进程死亡DiscMgrDeathCallback清除该进程所有发布/订阅
BT 关闭收到 BLE_TURN_OFF停止一切 BLE 操作,重置通道状态
通道归还停止发布/扫描通道归还通道池

特别是 BT 关闭处理——收到 BLE_TURN_OFF 后,所有广播通道状态重置,所有扫描停止,不残留任何状态。下次 BT 打开时干干净净从头来。

发现之后:连接模块接棒

发现到设备后,业务拿到的是 DeviceInfo(含设备 ID、名称、能力等),但要真正通信还需要建立连接。这时连接模块接棒。

连接模块支持七种连接类型:

类型说明
BLE蓝牙低功耗连接
BR经典蓝牙连接
SLE星闪连接
TCPTCP 连接
WiFi DirectWi-Fi P2P 直连
Proxy代理连接(中继转发)
General通用连接

连接模块有两个关键设计:

引用计数:同一对设备间多次连接自动复用,返回相同 connectionId。ConnDisconnectDevice 减引用计数,减到 0 才真正物理断开。避免了一个业务断开就把连接断了,影响其他复用同一连接的业务。

BR 频连控制:短时间内多次经典蓝牙连接会被拒绝,防止蓝牙芯片过载。本地测试时可设 isDisableBrFrequentConnectControl = true 禁用。

小结

软总线发现机制的核心设计思想:用分层架构屏蔽链路差异,用发布-订阅解耦发现双方,用能力位图高效匹配,用 Handler-Looper 串行化硬件操作,用通道池管理有限资源。

业务看到的只是 PublishLNN 和 RefreshLNN 两个接口,背后是四层架构、四种媒介、16 种消息、21 个广播通道、4 个扫描通道的精密协作。

下次当你看到两台鸿蒙设备"自动发现"彼此时,想想那个 48ms 间隔的 BLE 广播包,和它从芯片到应用层的四层旅程。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐