第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 部署验证步骤
- 集成代码:将
observer.h/subject.c加入工程,并实现至少两个观察者(如日志观察者、业务逻辑观察者)。 - 初始化:在
main()中调用ble_observer_init(),并分别注册观察者。 - 连接测试:使用手机APP(如nRF Connect)连接设备,触发连接/数据事件。
- 检查日志:确认所有注册的观察者都收到了对应事件,且无重复调用。
- 动态注销测试:在运行中注销某个观察者,再次触发事件,确认该观察者不再收到通知。
常见错误:观察者未注销便释放了其内存(如局部变量失效),导致野指针调用。建议使用静态分配或确保观察者生命周期覆盖整个使用范围。
3.6 易错点与优化建议
| 易错点 | 说明 | 优化建议 |
|---|---|---|
| 重复注册 | 同一观察者被多次注册,导致多次通知 | 注册前检查列表中是否已存在,如示例中subject_register已加入去重逻辑 |
| 回调中执行阻塞操作 | 在observer.on_notify中调用delay或打印大量日志会阻塞事件分发 | 将耗时操作放入任务队列,使用消息异步传递 |
| 线程安全问题 | ISR或多个任务同时调用subject_register/unregister/notify | 加锁(如互斥量),或仅在单一线程中操作Subject |
| 内存碎片 | 动态分配观察者列表,频繁注册注销可能产生碎片 | 使用静态数组 + 移位删除,或使用链表管理 |
| 事件数据生命周期 | 传递给观察者的data指针在下一次协议栈回调后失效 | 在观察者内部拷贝数据,或使用引用计数 |
3.7 观察者模式与BLE事件分发结构图
性能优化提示:如果观察者数量很少(≤4),可采用数组遍历而非链表;若事件频率很高(如每毫秒触发DATA_RX),建议使用位图掩码实现快速分发,避免遍历全部观察者。