【android bluetooth 协议分析 03】【蓝牙扫描详解 1】【扫描关键函数 btif_dm_search_devices_evt 分析】
1. 背景
本篇我们来对 btif_dm_search_devices_evt 函数进行分析.
这是系统性分析 Bluetooth 协议栈中的设备扫描流程时必须厘清的一环。
1. 为什么要单独分析 btif_dm_search_devices_evt 函数:
btif_dm_search_devices_evt 是 BTIF 层中处理设备扫描(discovery)阶段事件的核心函数。虽然它不负责服务发现(SDP),但它对扫描流程的结果质量、事件响应、设备发现体验具有决定性影响。
它在整个蓝牙设备发现(scan/inquiry)阶段中,是唯一负责接收和转发底层 BTA_DM 层设备发现结果的入口,因此:
它是“设备被发现的那一刻”开始的所有行为的主控函数。
2. 它在扫描过程中的核心作用(职责)
在用户发起扫描(如调用 BluetoothAdapter.startDiscovery())之后,底层蓝牙协议栈通过如下层级传播事件:
Controller → HCI → BTM → BTA_DM → BTIF (btif_dm_search_devices_evt) → Java Framework
在这个过程中,btif_dm_search_devices_evt 承担了:
职责 | 说明 |
---|---|
① 接收扫描结果事件(BTA_DM_INQ_RES_EVT ) | 一个远程设备被发现 |
② 接收扫描完成事件(BTA_DM_INQ_CMPL_EVT ) | 设备发现完成,结束通知 |
③ 接收设备基础信息事件(BTA_DM_DISCOVERY_RESULT_EVT ) | 包括名称、RSSI、Class、Bond 状态等 |
④ 对这些事件进行解析并封装 | 转换为 HAL 到 Java 层可识别格式(如 bt_property) |
⑤ 将解析后的信息通过 HAL 层回调 | 通知 Java Framework,更新系统设备列表(如 Settings 页面) |
3. 如果扫描行为出现问题,为什么应该从 btif_dm_search_devices_evt 切入排查?
1. 典型问题场景
问题场景 | 原因可能与 btif_dm_search_devices_evt 有关 |
---|---|
搜不到任何设备 | 没有收到 BTA_DM_INQ_RES_EVT 或未正确处理 |
扫描结束没有反馈 | 没有处理 BTA_DM_INQ_CMPL_EVT 或未通知上层 |
显示设备但没有名称 | 没有正确处理 BTA_DM_DISCOVERY_RESULT_EVT ,或未设置名称属性 |
发现的设备没有正确标注配对状态 | 未解析 bonded 标志位,或未设置 bt_property |
Settings 界面显示设备信息不完整 | bt_property 转换逻辑有误或缺字段 |
发现设备信息重复或闪烁 | 没有去重、更新逻辑错误 |
2. 排查路径从该函数切入的优势
信息齐全:它汇聚了 scan 阶段的所有设备信息(包括名称、RSSI、配对状态)
流程中枢:所有底层扫描事件都通过它统一传入 BTIF 层
上层反馈口:最终会通过 HAL 接口向 Java 层发送设备属性信息
因此:一旦设备列表更新、展示、扫描反馈等出了问题,这里一定是最关键的分析锚点。
2. btif_dm_search_devices_evt
上面已经介绍了 btif_dm_search_devices_evt 重要性。那我们正式开始 对 该函数的详细分析。
/******************************************************************************** Function btif_dm_search_devices_evt** Description Executes search devices callback events in btif context** Returns void******************************************************************************/
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_DISC_RES_EVT: {} break; // 该事件是 SDP 相关的事件。该事件只在 btif_dm_search_services_evt 中被调用。这里不会调用。可以忽略case BTA_DM_INQ_RES_EVT: {} break;case BTA_DM_INQ_CMPL_EVT: {} break;case BTA_DM_DISC_CMPL_EVT: {} break; // 该事件是 SDP 相关的事件。该事件只在 btif_dm_search_services_evt 中被调用。这里不会调用。可以忽略case BTA_DM_SEARCH_CANCEL_CMPL_EVT: {} break;}
}
参数说明:
event
: 搜索事件类型(例如设备发现、发现完成等),枚举值tBTA_DM_SEARCH_EVT
p_search_data
: 携带事件相关数据的结构体指针,类型为tBTA_DM_SEARCH*
该函数分别处理如下几个事件:
事件类型 | 触发背景 | 核心处理内容 |
---|---|---|
BTA_DM_INQ_RES_EVT | 扫描到新设备或设备状态更新 | 分析 EIR、获取名称和服务 UUID,持久化并通知上层 |
BTA_DM_INQ_CMPL_EVT | Inquiry 扫描阶段结束 | 不处理(由其它模块标记状态) |
BTA_DM_SEARCH_CANCEL_CMPL_EVT | 用户取消搜索操作完成 | 判断是否可结束搜索流程并通知上层 |
接下来针对每个事件做 介绍
1.BTA_DM_INQ_RES_EVT - 设备扫描结果(inquiry)
case BTA_DM_INQ_RES_EVT
: 设备扫描结果(inquiry)
背景:
这是“扫描”过程中最常见的事件,每当发现一个新的设备或者更新了已有设备信息,都会触发这个事件。
触发条件:
设备处于扫描模式中(Inquiry),某个远程设备发出 Inquiry Response 或者扫描响应时触发。
{// 准备存放设备名称和 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.BTA_DM_INQ_CMPL_EVT - 扫描完成
该事件标志 Inquiry 扫描结束,但此处未做处理(可能状态已在其它地方标记)。
{/* do nothing */
}
3. BTA_DM_SEARCH_CANCEL_CMPL_EVT - 搜索取消完成
case BTA_DM_SEARCH_CANCEL_CMPL_EVT
: 搜索取消完成
{/* if inquiry is not in progress and we get a cancel event, then* it means we are done with inquiry, but remote_name fetches are in* progress** if inquiry is in progress, then we don't want to act on this* cancel_cmpl_evt* but instead wait for the cancel_cmpl_evt via the Busy Level**/// 如果当前扫描已结束但仍在等待 remote name,则标记整个搜索流程正式结束。if (!btif_dm_inquiry_in_progress) {invoke_discovery_state_changed_cb(BT_DISCOVERY_STOPPED);}}