第3章 观察者模式在BLE回调中的实现

3.1 原理与设计模式意图

BLE协议栈的典型回调场景(如广播状态变化、连接建立、数据到达、MTU更新)天然适合使用观察者模式。该模式定义了对象之间的一对多依赖关系:当一个主体(Subject)的状态发生变化时,所有依赖它的观察者(Observer)都会自动收到通知并更新。

  • 解耦:BLE核心层不需要知道谁在监听事件,只需要维护一份观察者列表。
  • 动态注册/注销:应用模块可以按需订阅特定事件,避免全局回调的耦合。
  • 多客户端通知:同一个BLE事件(如连接断开)可以被多个观察者同时处理,便于分层架构(协议层、业务层、UI层)。
核心意图:将“事件源”与“事件消费者”分离,使双方各自可以独立变化和扩展,是BLE应用层解耦的基石。

3.2 核心代码片段:观察者模式实现与BLE回调注册

3.2.1 观察者接口与主题类(C语言风格)

// observer.h
#ifndef __OBSERVER_H__
#define __OBSERVER_H__

#include <stdint.h>

// 事件类型枚举
typedef enum {
    BLE_EVT_ADV_START         = 0x01,
    BLE_EVT_ADV_STOP          = 0x02,
    BLE_EVT_CONNECT           = 0x04,
    BLE_EVT_DISCONNECT        = 0x08,
    BLE_EVT_DATA_RX           = 0x10,
    BLE_EVT_MTU_UPDATE        = 0x20
} ble_event_t;

// 事件数据结构
typedef struct {
    ble_event_t   event;
    uint16_t      conn_handle;
    uint8_t      *data;
    uint16_t      data_len;
} ble_event_data_t;

// 观察者接口
typedef struct observer {
    void (*on_notify)(struct observer *self, const ble_event_data_t *evt);
} observer_t;

// 主题(Subject)
typedef struct {
    observer_t   **list;
    uint8_t        capacity;
    uint8_t        count;
} subject_t;

void subject_init(subject_t *sub, uint8_t max);
void subject_register(subject_t *sub, observer_t *obs);
void subject_unregister(subject_t *sub, observer_t *obs);
void subject_notify(subject_t *sub, const ble_event_data_t *evt);

#endif
// subject.c
#include "observer.h"
#include <stdlib.h>
#include <string.h>

void subject_init(subject_t *sub, uint8_t max) {
    sub->list = (observer_t**)malloc(max * sizeof(observer_t*));
    sub->capacity = max;
    sub->count = 0;
}

void subject_register(subject_t *sub, observer_t *obs) {
    if (sub->count >= sub->capacity) return;
    for (int i = 0; i < sub->count; i++) {
        if (sub->list[i] == obs) return; // 避免重复注册
    }
    sub->list[sub->count++] = obs;
}

void subject_unregister(subject_t *sub, observer_t *obs) {
    int found = -1;
    for (int i = 0; i < sub->count; i++) {
        if (sub->list[i] == obs) { found = i; break; }
    }
    if (found < 0) return;
    for (int i = found; i < sub->count - 1; i++) {
        sub->list[i] = sub->list[i+1];
    }
    sub->count--;
}

void subject_notify(subject_t *sub, const ble_event_data_t *evt) {
    for (int i = 0; i < sub->count; i++) {
        observer_t *obs = sub->list[i];
        if (obs->on_notify) {
            obs->on_notify(obs, evt);
        }
    }
}

3.2.2 在BLE回调中使用观察者模式

// ble_callback_wrapper.c
#include "observer.h"
#include "ble_common.h"   // 假设有nrf_ble_xx等头文件

static subject_t g_ble_subject;

// 初始化主题(通常在app_init时调用)
void ble_observer_init(void) {
    subject_init(&g_ble_subject, 10);
}

// 注册/注销接口(对外暴露)
void ble_observer_register(observer_t *obs) {
    subject_register(&g_ble_subject, obs);
}
void ble_observer_unregister(observer_t *obs) {
    subject_unregister(&g_ble_subject, obs);
}

// ---- 原生BLE回调函数(由协议栈调用)----
// 广播开始回调示例(仅假设)
static void on_adv_start(void) {
    ble_event_data_t evt;
    evt.event     = BLE_EVT_ADV_START;
    evt.conn_handle = 0xFFFF;
    evt.data      = NULL;
    evt.data_len  = 0;
    subject_notify(&g_ble_subject, &evt);
}

// 连接事件回调
static void on_connect(uint16_t conn_handle) {
    ble_event_data_t evt;
    evt.event     = BLE_EVT_CONNECT;
    evt.conn_handle = conn_handle;
    evt.data      = NULL;
    evt.data_len  = 0;
    subject_notify(&g_ble_subject, &evt);
}

// 数据接收回调
static void on_data_rx(uint16_t conn_handle, uint8_t *data, uint16_t len) {
    ble_event_data_t evt;
    evt.event     = BLE_EVT_DATA_RX;
    evt.conn_handle = conn_handle;
    evt.data      = data;
    evt.data_len  = len;
    subject_notify(&g_ble_subject, &evt);
}

// 将原生回调挂接到协议栈(以下仅为示意)
void ble_stack_init(void) {
    // 实际代码中会调用类似 sd_ble_gap_adv_set_configure(...) 并注册回调
}

3.2.3 具体观察者实例(如日志模块、业务模块)

// log_observer.c (日志观察者)
#include "observer.h"
#include <stdio.h>

static void log_on_notify(observer_t *self, const ble_event_data_t *evt) {
    (void)self;
    switch (evt->event) {
        case BLE_EVT_CONNECT:
            printf("[LOG] 设备已连接, handle=0x%04X\n", evt->conn_handle);
            break;
        case BLE_EVT_DISCONNECT:
            printf("[LOG] 设备已断开, handle=0x%04X\n", evt->conn_handle);
            break;
        case BLE_EVT_DATA_RX:
            printf("[LOG] 收到数据, len=%d\n", evt->data_len);
            break;
        default:
            break;
    }
}

observer_t log_observer = { .on_notify = log_on_notify };

// 在main task中注册
void app_init(void) {
    ble_observer_init();
    ble_observer_register(&log_observer);
}

3.3 配置/参数/命令

参数/API说明典型值
subject_init(&sub, max)初始化主题,分配最大观察者容量max = 10 ~ 20
subject_register(&sub, obs)注册观察者,重复注册会被忽略obs 指针需保持有效
subject_unregister(&sub, obs)注销观察者未注册时无操作
subject_notify(&sub, evt)向所有注册的观察者分发事件evt 结构体需在栈区创建
ble_observer_register()对外封装,内部调用主题注册模块初始化时调用
配置建议:在低功耗嵌入式平台(如nRF52840),建议将观察者数量上限设为8~16,避免动态内存碎片。若使用RTOS,可在临界区保护 subject_notify。

3.4 调试日志分析

典型Log输出(开启观察者模式日志打印):

    0.000 [DBG] subject_notify: 分发事件 BLE_EVT_ADV_START (句柄=0xFFFF)
    0.001 [LOG] 广播已开始
    2.345 [DBG] subject_notify: 分发事件 BLE_EVT_CONNECT (句柄=0x0001)
    2.346 [LOG] 设备已连接, handle=0x0001
    2.346 [DBG] subject_notify: 分发事件 BLE_EVT_DATA_RX (句柄=0x0001, len=20)
    2.347 [LOG] 收到数据, len=20

通过日志可以验证:

  • 每个BLE事件只会触发一次 subject_notify。
  • 所有观察者按注册顺序依次被调用。
  • 观察者自身的处理是同步的,不宜在回调中执行阻塞操作。

3.5 部署验证步骤

  1. 集成代码:将 observer.h / subject.c 加入工程,并实现至少两个观察者(如日志观察者、业务逻辑观察者)。
  2. 初始化:在 main() 中调用 ble_observer_init(),并分别注册观察者。
  3. 连接测试:使用手机APP(如nRF Connect)连接设备,触发连接/数据事件。
  4. 检查日志:确认所有注册的观察者都收到了对应事件,且无重复调用。
  5. 动态注销测试:在运行中注销某个观察者,再次触发事件,确认该观察者不再收到通知。
常见错误:观察者未注销便释放了其内存(如局部变量失效),导致野指针调用。建议使用静态分配或确保观察者生命周期覆盖整个使用范围。

3.6 易错点与优化建议

易错点说明优化建议
重复注册同一观察者被多次注册,导致多次通知注册前检查列表中是否已存在,如示例中subject_register已加入去重逻辑
回调中执行阻塞操作在observer.on_notify中调用delay或打印大量日志会阻塞事件分发将耗时操作放入任务队列,使用消息异步传递
线程安全问题ISR或多个任务同时调用subject_register/unregister/notify加锁(如互斥量),或仅在单一线程中操作Subject
内存碎片动态分配观察者列表,频繁注册注销可能产生碎片使用静态数组 + 移位删除,或使用链表管理
事件数据生命周期传递给观察者的data指针在下一次协议栈回调后失效在观察者内部拷贝数据,或使用引用计数

3.7 观察者模式与BLE事件分发结构图

观察者模式在BLE回调中的结构 观察者模式(BLE事件分发) BLE协议栈事件源 (GAP/GATT回调) Subject 主题 (观察者列表管理) 日志观察者 on_notify: printf 业务观察者 on_notify: 发送消息 UI观察者 on_notify: 更新界面 多客户端通知 同一BLE事件可同时 被多个观察者订阅 解耦:添加/删除观察者不影响协议栈 & 其他观察者
性能优化提示:如果观察者数量很少(≤4),可采用数组遍历而非链表;若事件频率很高(如每毫秒触发DATA_RX),建议使用位图掩码实现快速分发,避免遍历全部观察者。