【android bluetooth 协议分析 03】【蓝牙扫描详解 4】【BR/EDR扫描到设备后如何上报给app侧】
一、引言
在之前的文章中我介绍了如下内容, 建议先阅读一下。
- 【android bluetooth 协议分析 03】【蓝牙扫描详解 2】【app触发蓝牙扫描后,协议栈都做了那些事情】
- 【android bluetooth 协议分析 03】【蓝牙扫描详解 3】【Bluetooth 中 EIR、IR、BLE 普通广播与扩展广播详解】
- 【android bluetooth 协议分析 01】【HCI 层介绍 30】【hci_event和le_meta_event如何上报到btu层】
本节在上面文章的基础之上继续讨论:
- 当我们发起 BR/EDR
Inquiry
时, 如何将Extended Inquiry Result(EIR)
上报到 app 侧。
1.1 发起扫描
下面是 btsnoop 中发起扫描时的命令
107 2025-01-05 21:10:10.458102 host controller HCI_CMD 9 Sent Inquiry Frame 107: 9 bytes on wire (72 bits), 9 bytes captured (72 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI Command - InquiryCommand Opcode: Inquiry (0x0401)Parameter Total Length: 5LAP: 0x9e8b33Inquiry Length: 10 (12.8 sec)Num Responses: 0[Pending in frame: 108][Command-Pending Delta: 2.938ms][Response in frame: 1130][Command-Response Delta: 12318.94ms]
1.2 扫描到 Beats Flex 设备
下面是 btsnoop 中,当我们扫描到一个外设后, 上报的 hci_event 事件。
567 2025-01-05 21:10:13.070497 controller host HCI_EVT 258 Rcvd Extended Inquiry Result Beats Flex Frame 567: 258 bytes on wire (2064 bits), 258 bytes captured (2064 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI Event - Extended Inquiry ResultEvent Code: Extended Inquiry Result (0x2f)Parameter Total Length: 255Number of responses: 1BD_ADDR: Apple_01:e1:07 (74:8f:3c:01:e1:07)Page Scan Repetition Mode: R1 (0x01)Reserved: 0x00Class of Device: 0x240418 (Audio/Video:Headphones - services: Rendering Audio).101 0001 0101 0001 = Clock Offset: 0x5151RSSI: -45 dBmExtended Inquiry Response DataDevice ID / Security Manager TK ValueDevice Name: Beats FlexUnused
本篇就来讨论, 当收到 hci_event : Extended Inquiry Result
事件后, 如何上报到app 侧。
本篇会基于 【android bluetooth 协议分析 01】【HCI 层介绍 30】【hci_event和le_meta_event如何上报到btu层】 拿 EIR 上报 从 btu 层 -> app 层 向上求索。
二、btu 层的处理
假设你已经清楚了 hci_event 如何上报到 btu 层。
2.1 btu_hcif_process_event
void btu_hcif_process_event(UNUSED_ATTR uint8_t controller_id,const BT_HDR* p_msg) {...uint8_t* p = (uint8_t*)(p_msg + 1) + p_msg->offset;uint8_t hci_evt_code, hci_evt_len;uint8_t ble_sub_code;STREAM_TO_UINT8(hci_evt_code, p);STREAM_TO_UINT8(hci_evt_len, p);...switch (hci_evt_code) {
...case HCI_EXTENDED_INQUIRY_RESULT_EVT /*0x2F*/:btm_process_inq_results(p, hci_evt_len, BTM_INQ_RESULT_EXTENDED);break;...}
}
567 2025-01-05 21:10:13.070497 controller host HCI_EVT 258 Rcvd Extended Inquiry Result Beats Flex Frame 567: 258 bytes on wire (2064 bits), 258 bytes captured (2064 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI Event - Extended Inquiry ResultEvent Code: Extended Inquiry Result (0x2f) // 这里也是 0x2fParameter Total Length: 255Number of responses: 1BD_ADDR: Apple_01:e1:07 (74:8f:3c:01:e1:07)Page Scan Repetition Mode: R1 (0x01)Reserved: 0x00Class of Device: 0x240418 (Audio/Video:Headphones - services: Rendering Audio).101 0001 0101 0001 = Clock Offset: 0x5151RSSI: -45 dBmExtended Inquiry Response DataDevice ID / Security Manager TK ValueDevice Name: Beats FlexUnused
2.2 btm_process_inq_results
是蓝牙协议栈中处理 BR/EDR Inquiry Result(设备搜索结果) 的核心函数:
btm_process_inq_results(...)
,用于处理控制器发来的设备搜索事件,并更新本地的设备缓存数据库(Inquiry DB)。
功能总结:
-
解析搜索结果(包括 RSSI、设备类别等信息)
-
更新设备缓存数据库
-
触发回调通知上层发现新设备
/********************************************************************************* Function btm_process_inq_results** Description This function is called when inquiry results are received* from the device. It updates the inquiry database. If the* inquiry database is full, the oldest entry is discarded.** Parameters inq_res_mode - BTM_INQ_RESULT_STANDARD* BTM_INQ_RESULT_WITH_RSSI* BTM_INQ_RESULT_EXTENDED** Returns voidp:指向 HCI Event Payload 的指针hci_evt_len:HCI Event 的有效长度(用于校验)inq_res_mode:结果模式(标准/带RSSI/扩展)*******************************************************************************/
void btm_process_inq_results(const uint8_t* p, uint8_t hci_evt_len,uint8_t inq_res_mode) {/*num_resp: 控制器返回的设备数量bda: 蓝牙设备地址p_i: 对应设备在 Inquiry DB 中的条目p_cur: 当前设备结果指针i_rssi: RSSI 信号强度(signed)p_inq: 指向全局的 Inquiry 状态结构体p_inq_results_cb: 应用层注册的回调函数指针*/uint8_t num_resp, xx;RawAddress bda;tINQ_DB_ENT* p_i;tBTM_INQ_RESULTS* p_cur = NULL;bool is_new = true;bool update = false;int8_t i_rssi;tBTM_INQUIRY_VAR_ST* p_inq = &btm_cb.btm_inq_vars;tBTM_INQ_RESULTS_CB* p_inq_results_cb = p_inq->p_inq_results_cb;uint8_t page_scan_rep_mode = 0;uint8_t page_scan_per_mode = 0;uint8_t page_scan_mode = 0;uint8_t rssi = 0;DEV_CLASS dc;uint16_t clock_offset;const uint8_t* p_eir_data = NULL;#if (BTM_INQ_DEBUG == TRUE)BTM_TRACE_DEBUG("btm_process_inq_results inq_active:0x%x state:%d",btm_cb.btm_inq_vars.inq_active, btm_cb.btm_inq_vars.state);
#endif/* 只有在 Inquiry 还活跃时才处理结果,否则直接丢弃。 */if (!(p_inq->inq_active & BTM_BR_INQ_ACTIVE_MASK)) return;/*从事件数据流中读取设备响应数量。*/STREAM_TO_UINT8(num_resp, p);// 然后根据 inq_res_mode 执行参数校验:if (inq_res_mode == BTM_INQ_RESULT_EXTENDED) {if (num_resp > 1) { // // 扩展结果只能有一个设备BTM_TRACE_ERROR("btm_process_inq_results() extended results (%d) > 1",num_resp);return;}constexpr uint16_t extended_inquiry_result_size = 254;if (hci_evt_len - 1 != extended_inquiry_result_size) { // 长度检查BTM_TRACE_ERROR("%s: can't fit %d results in %d bytes", __func__,num_resp, hci_evt_len);return;}} else if (inq_res_mode == BTM_INQ_RESULT_STANDARD ||inq_res_mode == BTM_INQ_RESULT_WITH_RSSI) {constexpr uint16_t inquiry_result_size = 14;if (hci_evt_len < num_resp * inquiry_result_size) { // 标准/带RSSI的情况BTM_TRACE_ERROR("%s: can't fit %d results in %d bytes", __func__,num_resp, hci_evt_len);return;}}// 主循环处理每个设备for (xx = 0; xx < num_resp; xx++) {update = false;/* 解析基础字段(从字节流中逐项提取) */STREAM_TO_BDADDR(bda, p); // 蓝牙设备地址STREAM_TO_UINT8(page_scan_rep_mode, p); // 页面扫描重复模式(决定发现频率)STREAM_TO_UINT8(page_scan_per_mode, p); // 页面扫描周期模式if (inq_res_mode == BTM_INQ_RESULT_STANDARD) {STREAM_TO_UINT8(page_scan_mode, p); // 页面扫描模式(可选)}STREAM_TO_DEVCLASS(dc, p); // 设备类别STREAM_TO_UINT16(clock_offset, p); // 蓝牙连接同步时用的参考时钟if (inq_res_mode != BTM_INQ_RESULT_STANDARD) {STREAM_TO_UINT8(rssi, p); // 信号强度(仅扩展/带RSSI模式有)}// 处理缓存数据库中的设备条目p_i = btm_inq_db_find(bda); // 在 Inquiry DB 中查找已有条目。 待会重点分析的点/* Check if this address has already been processed for this inquiry */if (btm_inq_find_bdaddr(bda)) { // 如果设备已存在,根据情况更新 rssi/* BTM_TRACE_DEBUG("BDA seen before %s", bda.ToString().c_str()); *//* By default suppose no update needed */i_rssi = (int8_t)rssi;/* If this new RSSI is higher than the last one */if ((rssi != 0) && p_i &&(i_rssi > p_i->inq_info.results.rssi ||p_i->inq_info.results.rssi == 0/* BR/EDR inquiry information update */||(p_i->inq_info.results.device_type & BT_DEVICE_TYPE_BREDR) != 0)) {p_cur = &p_i->inq_info.results;BTM_TRACE_DEBUG("update RSSI new:%d, old:%d", i_rssi, p_cur->rssi);p_cur->rssi = i_rssi;update = true;}/* If we received a second Extended Inq Event for an already *//* discovered device, this is because for the first one EIR was notreceived */else if ((inq_res_mode == BTM_INQ_RESULT_EXTENDED) && (p_i)) {p_cur = &p_i->inq_info.results;update = true;}/* If no update needed continue with next response (if any) */else {}}/* If existing entry, use that, else get a new one (possibly reusing the* oldest) */// 条目不存在 ➜ 新建:if (p_i == NULL) {p_i = btm_inq_db_new(bda); // 待会重点分析的点is_new = true;}/* If an entry for the device already exists, overwrite it ONLY if it isfroma previous inquiry. (Ignore it if it is a duplicate response from thesameinquiry.*/// 条目存在 ➜ 检查是否来自本次搜索:else if (p_i->inq_count == p_inq->inq_counter &&(p_i->inq_info.results.device_type == BT_DEVICE_TYPE_BREDR))is_new = false;// 更新结果信息(例如 RSSI)/* keep updating RSSI to have latest value */if (inq_res_mode != BTM_INQ_RESULT_STANDARD)p_i->inq_info.results.rssi = (int8_t)rssi;elsep_i->inq_info.results.rssi = BTM_INQ_RES_IGNORE_RSSI;// 如果是新设备,写入详细字段if (is_new) {/* 填写字段 */p_cur = &p_i->inq_info.results;p_cur->page_scan_rep_mode = page_scan_rep_mode;p_cur->page_scan_per_mode = page_scan_per_mode;p_cur->page_scan_mode = page_scan_mode;p_cur->dev_class[0] = dc[0];p_cur->dev_class[1] = dc[1];p_cur->dev_class[2] = dc[2];p_cur->clock_offset = clock_offset | BTM_CLOCK_OFFSET_VALID;p_i->time_of_resp = bluetooth::common::time_get_os_boottime_ms(); // 时间戳if (p_i->inq_count != p_inq->inq_counter)p_inq->inq_cmpl_info.num_resp++; /* A new response was found */p_cur->inq_result_type |= BTM_INQ_RESULT_BR;if (p_i->inq_count != p_inq->inq_counter) {p_cur->device_type = BT_DEVICE_TYPE_BREDR;p_i->scan_rsp = false;} elsep_cur->device_type |= BT_DEVICE_TYPE_BREDR;p_i->inq_count = p_inq->inq_counter; /* Mark entry for current inquiry *//* Initialize flag to false. This flag is set/used by application */p_i->inq_info.appl_knows_rem_name = false;}if (is_new || update) {// 扩展模式下提取 EIR UUIDsif (inq_res_mode == BTM_INQ_RESULT_EXTENDED) {memset(p_cur->eir_uuid, 0,BTM_EIR_SERVICE_ARRAY_SIZE * (BTM_EIR_ARRAY_BITS / 8)); // 清空 UUID 表/* set bit map of UUID list from received EIR */btm_set_eir_uuid(p, p_cur); // 从 EIR 数据中解析 UUIDp_eir_data = p;} elsep_eir_data = NULL;/* If a callback is registered, call it with the results */if (p_inq_results_cb) {// 回调通知上层应用, 待会重点分析的点(p_inq_results_cb)((tBTM_INQ_RESULTS*)p_cur, p_eir_data,HCI_EXT_INQ_RESPONSE_LEN);} else {}}}}
字段名 | 含义 |
---|---|
bda | 蓝牙设备地址 |
page_scan_rep_mode | 页面扫描重复模式(决定发现频率) |
page_scan_per_mode | 页面扫描周期模式 |
page_scan_mode | 页面扫描模式(可选) |
dc (dev_class) | 设备类别 |
clock_offset | 蓝牙连接同步时用的参考时钟 |
rssi | 信号强度(仅扩展/带RSSI模式有) |
btm_process_inq_results 主要做了下面的工作:
阶段 | 内容 |
---|---|
输入处理 | 读取设备数、校验 HCI Event 长度 |
数据提取 | 逐个提取设备地址、类别、扫描参数、RSSI、EIR 等 |
数据库管理 | 新增或更新本地 Inquiry DB 条目 |
条件更新 | 仅当 RSSI 更高、是扩展事件或新设备时才更新 |
应用回调 | 如果注册了结果回调,就通知上层应用(例如 UI 刷新设备列表) |
在上面 我们 重点分析 :
btm_inq_db_find
:btm_inq_db_new
:p_inq_results_cb
:
1. btm_inq_db_new
在 Inquiry DB 中查找一个空闲条目(如果没有空闲,就替换最旧的)。
/********************************************************************************* Function btm_inq_db_new** Description This function looks through the inquiry database for an* unused entry. If no entry is free, it allocates the oldest* entry.** Returns pointer to entry*******************************************************************************/
tINQ_DB_ENT* btm_inq_db_new(const RawAddress& p_bda) {uint16_t xx;tINQ_DB_ENT* p_ent = btm_cb.btm_inq_vars.inq_db; // 用于遍历 Inquiry DB; tINQ_DB_ENT inq_db[BTM_INQ_DB_SIZE];tINQ_DB_ENT* p_old = btm_cb.btm_inq_vars.inq_db; // 记录目前找到的最旧条目(用于替换)uint64_t ot = UINT64_MAX; // 当前发现的最早响应时间(用来找“最老”的条目)// 遍历 Inquiry DB(大小由 BTM_INQ_DB_SIZE 决定,通常是一个固定长度数组)。for (xx = 0; xx < BTM_INQ_DB_SIZE/*40*/; xx++, p_ent++) {if (!p_ent->in_use) { // 如果找到未使用条目memset(p_ent, 0, sizeof(tINQ_DB_ENT)); // 清空条目p_ent->inq_info.results.remote_bd_addr = p_bda; // 设置设备地址p_ent->in_use = true; // 标记为已使用return (p_ent); // 返回这个条目}if (p_ent->time_of_resp < ot) { // 如果是已使用的条目,检查其响应时间p_old = p_ent; // 记录最早响应时间(即“最旧”的条目)ot = p_ent->time_of_resp; // 如果当前条目的 time_of_resp 比之前更老 ➜ 更新 p_old}}/* If here, no free entry found. Return the oldest. */// 如果没有找到空闲条目 ➜ 替换最老的memset(p_old, 0, sizeof(tINQ_DB_ENT));p_old->inq_info.results.remote_bd_addr = p_bda;p_old->in_use = true;return (p_old);
}
步骤 | 内容 |
---|---|
① | 遍历数据库查找 in_use == false 的条目 |
② | 如果找到空闲,清空并分配地址,返回 |
③ | 如果没有空闲,找出最旧的条目 time_of_resp 最小 |
④ | 清空最旧条目,写入新地址,返回 |
为什么找“最老”的条目替换?
- 因为 Inquiry Database 是一个有限大小的缓存(例如 40 条),新设备不断出现时,必须淘汰旧设备,为新设备腾出空间。
用时间最久远的那个条目替换是一个典型的 LRU 策略(Least Recently Used)。
1. 数据关系
结构体 | 用途说明 | 包含关系 |
---|---|---|
tINQ_DB_ENT | Inquiry 数据库中的一条记录 | → 包含 tBTM_INQ_INFO |
tBTM_INQ_INFO | Inquiry 中关于某个设备的整体信息 | → 包含 tBTM_INQ_RESULTS |
tBTM_INQ_RESULTS | 实际的设备广播/扫描返回的核心数据 | 最底层结构 |
Inquiry DB 条目结构 tINQ_DB_ENT
typedef struct {uint64_t time_of_resp;uint32_tinq_count; /* "timestamps" the entry with a particular inquiry count *//* Used for determining if a response has already been *//* received for the current inquiry operation. (We do not *//* want to flood the caller with multiple responses from *//* the same device. */tBTM_INQ_INFO inq_info;bool in_use;bool scan_rsp;
} tINQ_DB_ENT;typedef struct {tBTM_INQ_RESULTS results;bool appl_knows_rem_name; /* set by application if it knows the remote name ofthe peer device.This is later used by application to determine ifremote name request isrequired to be done. Having the flag here avoidduplicate store of inquiry results */uint16_t remote_name_len;tBTM_BD_NAME remote_name;uint8_t remote_name_state;uint8_t remote_name_type;} tBTM_INQ_INFO;typedef struct {uint16_t clock_offset;RawAddress remote_bd_addr;DEV_CLASS dev_class;uint8_t page_scan_rep_mode;uint8_t page_scan_per_mode;uint8_t page_scan_mode;int8_t rssi; /* Set to BTM_INQ_RES_IGNORE_RSSI if not valid */uint32_t eir_uuid[BTM_EIR_SERVICE_ARRAY_SIZE];bool eir_complete_list;tBT_DEVICE_TYPE device_type;uint8_t inq_result_type;tBLE_ADDR_TYPE ble_addr_type;uint16_t ble_evt_type;uint8_t ble_primary_phy;uint8_t ble_secondary_phy;uint8_t ble_advertising_sid;int8_t ble_tx_power;uint16_t ble_periodic_adv_int;RawAddress ble_ad_rsi; /* Resolvable Set Identifier from advertising */bool ble_ad_is_le_audio_capable;uint8_t flag;bool include_rsi;RawAddress original_bda;
} tBTM_INQ_RESULTS;
1. 三个结构体的嵌套关系图:
tINQ_DB_ENT
│
├── time_of_resp ← 响应接收时间戳
├── inq_count ← 当前 inquiry 的次数标记
├── inq_info ← inquiry 详细信息 (tBTM_INQ_INFO)
│ ├── results ← inquiry 结果主体 (tBTM_INQ_RESULTS)
│ └── ... ← 远程名称相关字段
├── in_use ← 是否在使用中
└── scan_rsp ← 是否收到了 scan response
2. tINQ_DB_ENT
字段名 | 类型 / 宏定义 | 所属结构体 | 含义说明 |
---|---|---|---|
time_of_resp | uint64_t | tINQ_DB_ENT | 最近一次收到设备响应的时间(用于判断最旧条目) |
inq_count | uint32_t | tINQ_DB_ENT | 标记这个 entry 属于哪一次 inquiry,用于去重 |
inq_info | tBTM_INQ_INFO | tINQ_DB_ENT | inquiry 结果详情,包括设备名、results |
in_use | bool | tINQ_DB_ENT | 当前条目是否有效(是否在使用) |
scan_rsp | bool | tINQ_DB_ENT | 是否已经接收到 Scan Response |
3.tBTM_INQ_INFO
字段名 | 类型 | 所属结构体 | 含义说明 |
---|---|---|---|
results | tBTM_INQ_RESULTS | tBTM_INQ_INFO | 设备基础信息(地址、RSSI、设备类型、EIR数据等) |
appl_knows_rem_name | bool | tBTM_INQ_INFO | 应用层是否已经知道该设备的名称,避免重复发起名称请求 |
remote_name_len | uint16_t | tBTM_INQ_INFO | 已知远程设备名称的长度(如有) |
remote_name | tBTM_BD_NAME | tBTM_INQ_INFO | 远程设备名称(完整名称) |
remote_name_state | uint8_t | tBTM_INQ_INFO | 当前远程名称的状态(请求中、已知等) |
remote_name_type | uint8_t | tBTM_INQ_INFO | 名称类型(例如是否从 EIR 解析,是否是 scan response 获取的) |
4. tBTM_INQ_RESULTS
字段名 | 类型 | 所属结构体 | 含义说明 |
---|---|---|---|
clock_offset | uint16_t | tBTM_INQ_RESULTS | 设备的时钟偏移(用于同步) |
remote_bd_addr | RawAddress | tBTM_INQ_RESULTS | 设备的 Bluetooth 地址 |
dev_class | DEV_CLASS | tBTM_INQ_RESULTS | 设备的类别(如耳机、手机等) |
page_scan_rep_mode | uint8_t | tBTM_INQ_RESULTS | 页面扫描重复模式(影响连接建立策略) |
page_scan_per_mode | uint8_t | tBTM_INQ_RESULTS | 页面扫描周期模式 |
page_scan_mode | uint8_t | tBTM_INQ_RESULTS | 页面扫描模式 |
rssi | int8_t | tBTM_INQ_RESULTS | 接收信号强度指示(如果无效则设为 BTM_INQ_RES_IGNORE_RSSI) |
eir_uuid[] | uint32_t[] | tBTM_INQ_RESULTS | EIR 中携带的服务 UUID 列表(按位表示) |
eir_complete_list | bool | tBTM_INQ_RESULTS | 是否 EIR 包含完整服务列表 |
device_type | tBT_DEVICE_TYPE | tBTM_INQ_RESULTS | 设备类型(如 BR/EDR, BLE, DUAL) |
inq_result_type | uint8_t | tBTM_INQ_RESULTS | 结果类型(标志位,如是 BR、BLE、extended 等) |
ble_addr_type | tBLE_ADDR_TYPE | tBTM_INQ_RESULTS | BLE 地址类型(public 或 random) |
ble_evt_type | uint16_t | tBTM_INQ_RESULTS | BLE 事件类型(如 ADV_IND, SCAN_RSP 等) |
ble_primary_phy | uint8_t | tBTM_INQ_RESULTS | BLE 主 PHY(如 1M, Coded) |
ble_secondary_phy | uint8_t | tBTM_INQ_RESULTS | BLE 次 PHY |
ble_advertising_sid | uint8_t | tBTM_INQ_RESULTS | BLE Advertising SID(标识广播组) |
ble_tx_power | int8_t | tBTM_INQ_RESULTS | 广播的发射功率 |
ble_periodic_adv_int | uint16_t | tBTM_INQ_RESULTS | BLE 周期广播间隔 |
ble_ad_rsi | RawAddress | tBTM_INQ_RESULTS | BLE 广播中的 RSI(Resolvable Set Identifier) |
ble_ad_is_le_audio_capable | bool | tBTM_INQ_RESULTS | 是否支持 LE Audio |
flag | uint8_t | tBTM_INQ_RESULTS | 标志字段,具体用途上下文决定 |
include_rsi | bool | tBTM_INQ_RESULTS | 是否包含 RSI 信息 |
original_bda | RawAddress | tBTM_INQ_RESULTS | 若通过中继设备转发广播,此字段记录原始设备地址 |
2. btm_inq_db_find
// system/stack/btm/btm_inq.cc/********************************************************************************* Function btm_inq_db_find** Description This function looks through the inquiry database for a match* based on Bluetooth Device Address** Returns pointer to entry, or NULL if not found*******************************************************************************/
tINQ_DB_ENT* btm_inq_db_find(const RawAddress& p_bda) {uint16_t xx;tINQ_DB_ENT* p_ent = btm_cb.btm_inq_vars.inq_db;for (xx = 0; xx < BTM_INQ_DB_SIZE; xx++, p_ent++) {if (p_ent->in_use && p_ent->inq_info.results.remote_bd_addr == p_bda)return (p_ent);}/* If here, not found */return (NULL);
}
使用设备地址 在 Inquiry DB 中找到已经存在的条目。
3. p_inq_results_cb
最后我们来看一下, 如何通过 p_inq_results_cb
上报到更上层。
这里的 p_inq_results_cb == bta_dm_inq_results_cb
不清楚的可以回顾一下 【android bluetooth 协议分析 03】【蓝牙扫描详解 2】【app触发蓝牙扫描后,协议栈都做了那些事情】
三、bta 层的处理
3.1 bta_dm_inq_results_cb
bta_dm_inq_results_cb
:
- 这个函数是 BTU 向 BTA(Bluetooth Application)通报 Inquiry 扫描结果的回调函数,通常在设备扫描时调用。
- -函数用途:将设备扫描信息封装成
tBTA_DM_SEARCH
结构体,传递给上层回调bta_dm_search_cb.p_search_cback
。
// system/bta/dm/bta_dm_act.cc/********************************************************************************* Function bta_dm_inq_results_cb** Description Inquiry results callback from BTM** Returns void*******************************************************************************//*p_inq: 指向 inquiry 扫描返回的设备信息(底层结构)。p_eir: Extended Inquiry Response 数据指针。eir_len: EIR 数据长度。
*/static void bta_dm_inq_results_cb(tBTM_INQ_RESULTS* p_inq, const uint8_t* p_eir,uint16_t eir_len) {/*result:用于封装传给上层的搜索结果结构体。p_inq_info:从 inquiry 数据库中获取更完整设备信息(如设备名)。service_class:用来保存从设备 class 中解析出的服务类字段。*/tBTA_DM_SEARCH result;tBTM_INQ_INFO* p_inq_info;uint16_t service_class;// 设置搜索结果中的 Bluetooth 地址。result.inq_res.bd_addr = p_inq->remote_bd_addr;// Pass the original address to GattService#onScanResult// 这是 BLE 场景中可能用到的“原始地址”(被中继转发时),传给 GATT 用于识别设备。result.inq_res.original_bda = p_inq->original_bda;// 拷贝设备的 CoD(Class of Device),用于判定设备类型(耳机、键盘等)。memcpy(result.inq_res.dev_class, p_inq->dev_class, DEV_CLASS_LEN);BTM_COD_SERVICE_CLASS(service_class, p_inq->dev_class); // 从 dev_class 中提取 Service Class 部分。result.inq_res.is_limited =(service_class & BTM_COD_SERVICE_LMTD_DISCOVER) ? true : false; // 如果该设备标记为 Limited Discoverable Mode,设置 is_limited = true。// 拷贝设备的 BLE 相关信息:地址类型、设备类型、是否包含 RSI、RSSI 信号强度等。result.inq_res.rssi = p_inq->rssi;result.inq_res.ble_addr_type = p_inq->ble_addr_type;result.inq_res.inq_result_type = p_inq->inq_result_type;result.inq_res.device_type = p_inq->device_type;result.inq_res.flag = p_inq->flag;result.inq_res.include_rsi = p_inq->include_rsi;/* application will parse EIR to find out remote device name */// 将扫描返回的 EIR 数据转为非 const 后传给上层解析(可能包含名称、UUID、厂商信息等)。result.inq_res.p_eir = const_cast<uint8_t*>(p_eir);result.inq_res.eir_len = eir_len;// BLE 事件类型,比如 ADV_IND、SCAN_RSP,上层可据此判断是否进一步连接。result.inq_res.ble_evt_type = p_inq->ble_evt_type;// 尝试从 inquiry 数据库中获取对应设备的 entry。p_inq_info = BTM_InqDbRead(p_inq->remote_bd_addr); // 重点1 if (p_inq_info != NULL) {/* initialize remt_name_not_required to false so that we get the name by* default */// 初始化 remt_name_not_required 为 false(默认会尝试获取远程名称)。result.inq_res.remt_name_not_required = false;}// 如果搜索模块的回调函数不为空,就调用它,通知上层有设备发现事件(BTA_DM_INQ_RES_EVT)。if (bta_dm_search_cb.p_search_cback)bta_dm_search_cb.p_search_cback(BTA_DM_INQ_RES_EVT, &result); // 重点2if (p_inq_info) {/* application indicates if it knows the remote name, inside the callbackcopy that to the inquiry data base*/// 如果应用在上层设置了 remt_name_not_required = true,就标记数据库中该设备已经“知道名称”了,以避免再次请求。if (result.inq_res.remt_name_not_required)p_inq_info->appl_knows_rem_name = true;}
}
+-------------------------+| bta_dm_inq_results_cb |+-----------+-------------+|+-----------v------------+| 拷贝基础信息(地址/CoD)|+-----------+------------+|+-----------v------------+| 处理 BLE & EIR 数据 |+-----------+------------+|+-----------v------------+| 查询 Inquiry 数据库 |+-----------+------------+|+-----------v------------+| 调用上层回调,报告设备 |+-----------+------------+|+-----------v------------+| 更新设备名称已知状态 |+------------------------+
重点关注一下:
BTM_InqDbRead
p_search_cback
1. BTM_InqDbRead
// system/stack/btm/btm_inq.cc
tBTM_INQ_INFO* BTM_InqDbRead(const RawAddress& p_bda) {tINQ_DB_ENT* p_ent = btm_inq_db_find(p_bda); // 这里还是从 btm_inq_db_find 查询,见之前的分析return (p_ent == nullptr) ? nullptr : &p_ent->inq_info;
}
2. p_search_cback
这里的 p_search_cback == btif_dm_search_devices_evt
不清楚的可以回顾一下 【android bluetooth 协议分析 03】【蓝牙扫描详解 2】【app触发蓝牙扫描后,协议栈都做了那些事情】
四、btif 层处理
1. btif_dm_search_devices_evt
之前的文章中专门分析过 btif_dm_search_devices_evt
函数:
【android bluetooth 协议分析 03】【蓝牙扫描详解 1】【扫描关键函数 btif_dm_search_devices_evt 分析】
static void btif_dm_search_devices_evt(tBTA_DM_SEARCH_EVT event,tBTA_DM_SEARCH* p_search_data) {BTIF_TRACE_EVENT("%s event=%s", __func__, dump_dm_search_event(event));switch (event) {...case BTA_DM_INQ_RES_EVT:
{// 准备存放设备名称和 UUID(16-bit 和 128-bit)的缓存区。/* inquiry result */bt_bdname_t bdname;uint8_t remote_name_len;uint8_t num_uuids = 0, num_uuids128 = 0, max_num_uuid = 32;uint8_t uuid_list[32 * Uuid::kNumBytes16];uint8_t uuid_list128[32 * Uuid::kNumBytes128];// 根据 EIR 数据判断是否需要发起 Remote Name Request(即设备是否已广播出名称)。p_search_data->inq_res.remt_name_not_required =check_eir_remote_name(p_search_data, NULL, NULL);// 获取发现设备的地址。RawAddress& bdaddr = p_search_data->inq_res.bd_addr;BTIF_TRACE_DEBUG("%s() %s device_type = 0x%x\n", __func__,bdaddr.ToString().c_str(),p_search_data->inq_res.device_type);bdname.name[0] = 0;// 尝试从 EIR 中读取名称,失败则从缓存中获取。if (!check_eir_remote_name(p_search_data, bdname.name, &remote_name_len))check_cached_remote_name(p_search_data, bdname.name, &remote_name_len);/* Check EIR for services */// 获取 UUID(服务)信息:if (p_search_data->inq_res.p_eir) {BTM_GetEirUuidList(p_search_data->inq_res.p_eir,p_search_data->inq_res.eir_len, Uuid::kNumBytes16,&num_uuids, uuid_list, max_num_uuid);BTM_GetEirUuidList(p_search_data->inq_res.p_eir,p_search_data->inq_res.eir_len, Uuid::kNumBytes128,&num_uuids128, uuid_list128, max_num_uuid);}{/*构建属性列表:将设备属性打包成数组,逐项填充:- 蓝牙地址- 名称- 类别(COD)- 类型(BR/EDR、BLE 或 DUAL)- RSSI- 是否支持 CSIP 协调组- UUID(服务)UUID 部分还会缓存入 eir_uuids_cache,并使用 btif_update_uuid 做去重更新。*/bt_property_t properties[7];bt_device_type_t dev_type;uint32_t num_properties = 0;bt_status_t status;tBLE_ADDR_TYPE addr_type = BLE_ADDR_PUBLIC;memset(properties, 0, sizeof(properties));/* RawAddress */BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_BDADDR, sizeof(bdaddr), &bdaddr);num_properties++;/* BD_NAME *//* Don't send BDNAME if it is empty */if (bdname.name[0]) {BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_BDNAME,strlen((char*)bdname.name), &bdname);num_properties++;}/* DEV_CLASS */uint32_t cod = devclass2uint(p_search_data->inq_res.dev_class);BTIF_TRACE_DEBUG("%s cod is 0x%06x", __func__, cod);if (cod != 0) {BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_CLASS_OF_DEVICE, sizeof(cod),&cod);num_properties++;}/* DEV_TYPE *//* FixMe: Assumption is that bluetooth.h and BTE enums match *//* Verify if the device is dual mode in NVRAM */int stored_device_type = 0;if (btif_get_device_type(bdaddr, &stored_device_type) &&((stored_device_type != BT_DEVICE_TYPE_BREDR &&p_search_data->inq_res.device_type == BT_DEVICE_TYPE_BREDR) ||(stored_device_type != BT_DEVICE_TYPE_BLE &&p_search_data->inq_res.device_type == BT_DEVICE_TYPE_BLE))) {dev_type = (bt_device_type_t)BT_DEVICE_TYPE_DUMO;} else {dev_type = (bt_device_type_t)p_search_data->inq_res.device_type;}if (p_search_data->inq_res.device_type == BT_DEVICE_TYPE_BLE)addr_type = p_search_data->inq_res.ble_addr_type;BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_TYPE_OF_DEVICE, sizeof(dev_type),&dev_type);num_properties++;/* RSSI */BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_REMOTE_RSSI, sizeof(int8_t),&(p_search_data->inq_res.rssi));num_properties++;/* CSIP supported device */BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_REMOTE_IS_COORDINATED_SET_MEMBER,sizeof(bool),&(p_search_data->inq_res.include_rsi));num_properties++;/* Cache EIR queried services */if ((num_uuids + num_uuids128) > 0) {uint16_t* p_uuid16 = (uint16_t*)uuid_list;auto uuid_iter = eir_uuids_cache.find(bdaddr);Uuid new_remote_uuid[BT_MAX_NUM_UUIDS];size_t dst_max_num = sizeof(new_remote_uuid)/sizeof(Uuid);size_t new_num_uuid = 0;Uuid remote_uuid[BT_MAX_NUM_UUIDS];if (uuid_iter == eir_uuids_cache.end()) {auto triple = eir_uuids_cache.try_emplace(bdaddr, std::set<Uuid>{});uuid_iter = std::get<0>(triple);}//LOG_INFO("EIR UUIDs for %s:", bdaddr.ToString().c_str());for (int i = 0; i < num_uuids; ++i) {Uuid uuid = Uuid::From16Bit(p_uuid16[i]);//LOG_INFO(" %s", uuid.ToString().c_str());uuid_iter->second.insert(uuid);if (i < BT_MAX_NUM_UUIDS) {remote_uuid[i] = uuid;} else {LOG_INFO("%d >= %d", i, BT_MAX_NUM_UUIDS);}}for (int i = 0; i < num_uuids128; ++i) {Uuid uuid = Uuid::From128BitBE((uint8_t *)&uuid_list128[i * Uuid::kNumBytes128]);//LOG_INFO(" %s", uuid.ToString().c_str());uuid_iter->second.insert(uuid);if (i < BT_MAX_NUM_UUIDS) {remote_uuid[num_uuids + i] = uuid;} else {LOG_INFO("%d >= %d", i, BT_MAX_NUM_UUIDS);}}//LOG_INFO("%s %d : update EIR UUIDs.", __func__, __LINE__);new_num_uuid = btif_update_uuid(bdaddr, remote_uuid,(num_uuids + num_uuids128), new_remote_uuid,sizeof(new_remote_uuid),dst_max_num);BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_UUIDS,new_num_uuid * Uuid::kNumBytes128, new_remote_uuid);//LOG_INFO("%s %d : fill BT_PROPERTY_UUIDS property.", __func__, __LINE__);num_properties ++;}// 持久化 & 回调:status =btif_storage_add_remote_device(&bdaddr, num_properties, properties); // 添加进本地数据库ASSERTC(status == BT_STATUS_SUCCESS,"failed to save remote device (inquiry)", status);status = btif_storage_set_remote_addr_type(&bdaddr, addr_type); // 保存 BLE 地址类型ASSERTC(status == BT_STATUS_SUCCESS,"failed to save remote addr type (inquiry)", status);bool restrict_report = osi_property_get_bool("bluetooth.restrict_discovered_device.enabled", false);// 限制上报策略, 可选地根据系统属性是否限制上报某些设备。if (restrict_report &&p_search_data->inq_res.device_type == BT_DEVICE_TYPE_BLE &&!(p_search_data->inq_res.ble_evt_type & BTM_BLE_CONNECTABLE_MASK)) {LOG_INFO("%s: Ble device is not connectable",bdaddr.ToString().c_str());break;}// 上报到上层(Java 层): 触发 BluetoothAdapter.onBluetoothDeviceFound() 等 Java 层通知。/* Callback to notify upper layer of device */invoke_device_found_cb(num_properties, properties); // 重点}}...
...}
}
2. invoke_device_found_cb
- system/btif/src/bluetooth.cc
void invoke_device_found_cb(int num_properties, bt_property_t* properties) {do_in_jni_thread(FROM_HERE,base::BindOnce([](int num_properties, bt_property_t* properties) {HAL_CBACK(bt_hal_cbacks, device_found_cb,num_properties, properties);if (properties) {osi_free(properties);}},num_properties,property_deep_copy_array(num_properties, properties)));
}
- 回调到 jni 层
01-05 21:10:13.071116 2349 2833 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: operator(): HAL bt_hal_cbacks->device_found_cb
01-05 21:10:13.071253 2349 2833 D BluetoothRemoteDevices: Added new device property
01-05 21:10:13.071377 2349 2833 I BluetoothRemoteDevices: Property type: 2
01-05 21:10:13.071421 2349 2833 I BluetoothRemoteDevices: Remote Address is:74:8F:3C:01:E1:07
01-05 21:10:13.071433 2349 2833 I BluetoothRemoteDevices: Property type: 1
01-05 21:10:13.072565 2349 2833 I BluetoothRemoteDevices: Remote Device name is: Beats Flex
01-05 21:10:13.072585 2349 2833 I BluetoothRemoteDevices: Property type: 4
如果不太清楚 bt_hal_cbacks->device_found_cb
调用的是谁?
可以阅读 【android bluetooth 框架分析 01】【关键线程 3】【bt_jni_thread 线程介绍】
我这里直接 公布答案:
- android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static bt_callbacks_t sBluetoothCallbacks = {sizeof(sBluetoothCallbacks),adapter_state_change_callback,adapter_properties_callback,remote_device_properties_callback,device_found_callback, // 这里discovery_state_changed_callback,pin_request_callback,ssp_request_callback,bond_state_changed_callback,address_consolidate_callback,le_address_associate_callback,acl_state_changed_callback,callback_thread_event,dut_mode_recv_callback,le_test_mode_recv_callback,energy_info_recv_callback,link_quality_report_callback,generate_local_oob_data_callback,switch_buffer_size_callback,switch_codec_callback};
五、jni 层
5.1 device_found_callback
在之前 【android bluetooth 框架分析 04】【bt-framework 层详解 8】【DeviceProperties介绍】 中有分析过,感兴趣可以阅读
-
这是 蓝牙 JNI 层设备发现回调函数,由 native 层(C/C++)向 Java 层上报“发现了一个新设备”。
-
参数:
-
num_properties
: 当前设备携带的属性数量(比如地址、名称、UUID 等)。 -
properties
: 指向属性数组,每个属性是一个bt_property_t
。
-
// android/app/jni/com_android_bluetooth_btservice_AdapterService.cppstatic void device_found_callback(int num_properties,bt_property_t* properties) {CallbackEnv sCallbackEnv(__func__);if (!sCallbackEnv.valid()) return;/*创建一个 ScopedLocalRef<jbyteArray> 类型的 JNI 引用,用于存放 Java 层的地址数组。addr_index 用于记录 BT 地址所在属性数组的位置,方便后面复用原始地址。
*/ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), NULL);int addr_index;/*遍历设备属性,查找类型为 BT_PROPERTY_BDADDR(即地址)的属性。找到后分配一个 jbyteArray(Java 字节数组)用来传地址。调用 SetByteArrayRegion 把原生地址写入 Java 层数组。记录地址属性在原数组中的位置 addr_index,后面还要用。*/for (int i = 0; i < num_properties; i++) {if (properties[i].type == BT_PROPERTY_BDADDR) {addr.reset(sCallbackEnv->NewByteArray(properties[i].len));if (!addr.get()) {ALOGE("Address is NULL (unable to allocate) in %s", __func__);return;}sCallbackEnv->SetByteArrayRegion(addr.get(), 0, properties[i].len,(jbyte*)properties[i].val);addr_index = i;}}if (!addr.get()) { // 如果 addr 仍为空,说明在属性列表中找不到地址,不能继续执行,直接返回ALOGE("Address is NULL in %s", __func__);return;}ALOGV("%s: Properties: %d, Address: %s", __func__, num_properties,(const char*)properties[addr_index].val);/*这是 native 层广播设备属性变化的接口,向下发出 "发现设备" 的事件。RawAddress* 是地址指针,转自属性中的值。这可以通知 GAP、GATT、L2CAP 等蓝牙模块发现了设备。
*/remote_device_properties_callback(BT_STATUS_SUCCESS,(RawAddress*)properties[addr_index].val,num_properties, properties); // 1. 重点分析, 最终调用了 java devicePropertyChangedCallback/*调用 Java 层对应的回调函数,通知应用层有设备被发现。sJniCallbacksObj: JNI 全局对象,代表 Java 回调接口类的实例。method_deviceFoundCallback: JNI 方法 ID,表示 void deviceFoundCallback(byte[])。addr.get(): Java 地址字节数组,作为参数传入。
*/sCallbackEnv->CallVoidMethod(sJniCallbacksObj, method_deviceFoundCallback,addr.get()); // 2. 重点分析 最终调用了 java deviceFoundCallback
}method_deviceFoundCallback =env->GetMethodID(jniCallbackClass, "deviceFoundCallback", "([B)V");
这里分别回调了 java 层的:
devicePropertyChangedCallback
deviceFoundCallback
1.remote_device_properties_callback
该函数是 蓝牙 JNI 层用于上报设备属性变化的回调。当 native 层读取到某个设备(如名称、UUID、RSSI 等)属性后,调用本函数通知 Java 层。
-
status
:状态码,是否成功读取属性(如设备名称等)。 -
bd_addr
:设备的蓝牙地址。 -
num_properties
:属性数量。 -
properties
:属性数组(类型、长度、值)。
// android/app/jni/com_android_bluetooth_btservice_AdapterService.cppstatic void remote_device_properties_callback(bt_status_t status,RawAddress* bd_addr,int num_properties,bt_property_t* properties) {CallbackEnv sCallbackEnv(__func__);if (!sCallbackEnv.valid()) return;ALOGV("%s: Status is: %d, Properties: %d", __func__, status, num_properties);if (status != BT_STATUS_SUCCESS) {ALOGE("%s: Status %d is incorrect", __func__, status);return;}// 创建一个 Java byte[] 数组,长度为属性数量,但这个变量 val 实际没被用到,是冗余的或旧代码残留。ScopedLocalRef<jbyteArray> val(sCallbackEnv.get(),(jbyteArray)sCallbackEnv->NewByteArray(num_properties));if (!val.get()) {ALOGE("%s: Error allocating byteArray", __func__);return;}// 获取 val 的 Java 类引用(byte[] 的类),用于后面构建对象数组(NewObjectArray)。ScopedLocalRef<jclass> mclass(sCallbackEnv.get(),sCallbackEnv->GetObjectClass(val.get()));/* Initialize the jobjectArray and jintArray here itself and send theinitialized array pointers alone to get_properties *//*
创建一个 Java 对象数组 props,用于保存属性的值(如名称、UUID 等)。长度为属性数量,元素类型是 byte[]。
*/ScopedLocalRef<jobjectArray> props(sCallbackEnv.get(),sCallbackEnv->NewObjectArray(num_properties, mclass.get(), NULL));if (!props.get()) {ALOGE("%s: Error allocating object Array for properties", __func__);return;}/*创建一个 Java int[] 数组 types,用于保存属性的 type 值,例如:BT_PROPERTY_BDNAMEBT_PROPERTY_CLASS_OF_DEVICEBT_PROPERTY_UUIDS
*/ScopedLocalRef<jintArray> types(sCallbackEnv.get(), (jintArray)sCallbackEnv->NewIntArray(num_properties));if (!types.get()) {ALOGE("%s: Error allocating int Array for values", __func__);return;}/*分配一个 byte[6],用于存放设备地址(RawAddress)。将地址作为参数传递给 Java。
*/ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));if (!addr.get()) {ALOGE("Error while allocation byte array in %s", __func__);return;}/*将 native 的蓝牙地址拷贝进 Java 层的 byte[] 中。例如:将 [0x11, 0x22, 0x33, 0x44, 0x55, 0x66] 填入 addr。
*/sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),(jbyte*)bd_addr);/*get_properties 是 native 函数,用于把属性解析成 Java 对象数组和类型数组。将 C 层的 bt_property_t 转换为:int[] typesObject[] props(实际是 byte[],名字误导性强)
*/jintArray typesPtr = types.get();jobjectArray propsPtr = props.get();if (get_properties(num_properties, properties, &typesPtr, &propsPtr) < 0) {return;}// 调用 Java 层的方法:devicePropertyChangedCallback(address, types, props)sCallbackEnv->CallVoidMethod(sJniCallbacksObj,method_devicePropertyChangedCallback, addr.get(),types.get(), props.get());
}method_devicePropertyChangedCallback = env->GetMethodID(jniCallbackClass, "devicePropertyChangedCallback", "([B[I[[B)V");
六、java 层
6.1 devicePropertyChangedCallback
- android/app/src/com/android/bluetooth/btservice/JniCallbacks.java
void devicePropertyChangedCallback(byte[] address, int[] types, byte[][] val) {mRemoteDevices.devicePropertyChangedCallback(address, types, val);}
在之前 【android bluetooth 框架分析 04】【bt-framework 层详解 8】【DeviceProperties介绍】 中有分析过,感兴趣可以阅读
devicePropertyChangedCallback
: 目的就是更新 java 层中对应的设备 属性。
6.2 deviceFoundCallback
当 JNI 层通知有新设备被发现时,此函数会:
-
读取设备属性
-
应用过滤规则
-
构造
ACTION_FOUND
广播 -
发送广播通知应用层
- android/app/src/com/android/bluetooth/btservice/JniCallbacks.java
void deviceFoundCallback(byte[] address) {mRemoteDevices.deviceFoundCallback(address);}
- android/app/src/com/android/bluetooth/btservice/RemoteDevices.java
void deviceFoundCallback(byte[] address) {// The device properties are already registered - we can send the intent// now/*从蓝牙地址获取 BluetoothDevice 对象。该对象代表一个远程蓝牙设备。*/BluetoothDevice device = getDevice(address);infoLog("deviceFoundCallback: Remote Address is:" + device);/*获取该设备的缓存属性(DeviceProperties 是一个内部 Java 对象,缓存设备名称、RSSI、CoD、UUID 等信息)。这些信息在 native 层处理完后存储于此。*/DeviceProperties deviceProp = getDeviceProperties(device);if (deviceProp == null) { // 设备属性为空,可能是 native 层未正确设置,退出。errorLog("Device Properties is null for Device:" + device);return;}/*读取系统属性(getprop),判断是否开启了“限制设备发现”功能。如果该功能开启,则不广播无名称的设备。*/boolean restrict_device_found =SystemProperties.getBoolean("bluetooth.restrict_discovered_device.enabled", false);if (restrict_device_found && (deviceProp.mName == null || deviceProp.mName.isEmpty())) {debugLog("Device name is null or empty: " + device); // 如果启用了限制,且设备没有名称,就不广播该设备。return;}if (filterDevice(device)) { // 进一步过滤设备(可能是 MAC 白名单、类型过滤等),如果不符合条件,跳过广播。warnLog("Not broadcast Device: " + device);return;}infoLog("device:" + device + " adapterIndex=" + device.getAdapterIndex());// 构造标准蓝牙广播:发现新设备。Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); // 加入设备对象。intent.putExtra(BluetoothDevice.EXTRA_CLASS,new BluetoothClass(deviceProp.getBluetoothClass())); // 加入设备的 CoD(Class of Device),包括服务类型、主类、子类等信息。intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.getRssi()); // 加入 RSSI(信号强度),用于设备距离估计。intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.getName()); // 加入设备名称。intent.putExtra(BluetoothDevice.EXTRA_IS_COORDINATED_SET_MEMBER,deviceProp.isCoordinatedSetMember()); // 表示该设备是否属于 Coordinated Set(蓝牙 LE Audio 多设备组)。/*广播发送给所有监听 ACTION_FOUND 的应用。但只有持有 BLUETOOTH_SCAN 权限的 app 才能接收到。getTempBroadcastOptions() 通常用于设置广播临时传递策略(如前台优先)。*/sAdapterService.sendBroadcastMultiplePermissions(intent,new String[] { BLUETOOTH_SCAN },Utils.getTempBroadcastOptions());}
七、app 侧
7.1 广播注册
private void registerBroadcast() {Log.i(TAG, "registerBroadcast");IntentFilter filter = new IntentFilter();filter.addAction(BluetoothDevice.ACTION_FOUND); // 设备发现广播filter.addAction(BluetoothDevice.ACTION_UUID);filter.addAction(BluetoothDevice.ACTION_NAME_CHANGED);filter.addAction(BluetoothDevice.ACTION_CLASS_CHANGED);filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);getContext().registerReceiver(mReceiver, filter); // 这次广播}
7.2 接收设备发现广播
// 广播接收 ui 处理显示等private final BroadcastReceiver mReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();// When discovery finds a deviceif (BluetoothDevice.ACTION_FOUND.equals(action)) {handleActionFoundBroadcast(context, intent);} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {handleActionDiscoveryFinishedBroadcast(context, intent);} else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {handleActionBondStateChangedBroadcast(context, intent);}}};