【android bluetooth 协议分析 03】【蓝牙扫描详解 2】【app触发蓝牙扫描后,协议栈都做了那些事情】
一、引言
最近项目上报了一个蓝牙耳机扫描慢的问题, 借这个问题。 笨叔来分享一下 在 aosp 蓝牙协议栈里面扫描的流程。
大家可以思考一下 :
- 应用侧触发扫描 到 最终 协议栈 发送扫描命令,中间都有那些流程?
- 每一层都做了那些处理?
- BR/EDR 扫描, 会触发 ble 扫描吗?
二、应用侧
BluetoothAdapter mBtAdapter = BluetoothAdapter.getDefaultAdapter();mBtAdapter.startDiscovery();
应用侧,调用 startDiscovery 将触发蓝牙的扫描。
三、蓝牙服务侧
3.1 framework
- android/app/src/com/android/bluetooth/btservice/AdapterService.java
@Overridepublic void startDiscovery(AttributionSource source, SynchronousResultReceiver receiver) {final String packageName = source.getPackageName();Log.d(TAG, "BluetoothAdapter Binder startDiscovery pkg:"+packageName);try {receiver.send(startDiscovery(source)); // 1. } catch (RuntimeException e) {receiver.propagateException(e);}}private boolean startDiscovery(AttributionSource attributionSource) {AdapterService service = getService();if (service == null|| !callerIsSystemOrActiveOrManagedUser(service, TAG, "startDiscovery")) {return false;}if (!Utils.checkScanPermissionForDataDelivery(service, attributionSource, "Starting discovery.")) {return false;}return service.startDiscovery(attributionSource); // 2. }
应用侧会触发 AdapterService.startDiscovery 的调用
boolean startDiscovery(AttributionSource attributionSource) {...debugLog("startDiscovery");...return startDiscoveryNative(); // 直接调用 jni 层}private native boolean startDiscoveryNative();
3.2 jni
- android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static jboolean startDiscoveryNative(JNIEnv* env, jobject obj) {ALOGV("%s", __func__);if (!sBluetoothInterface) return JNI_FALSE;int ret = sBluetoothInterface->start_discovery(); // 1.return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
// system/btif/src/bluetooth.cc
static int start_discovery(void) {if (!interface_ready()) return BT_STATUS_NOT_READY;do_in_main_thread(FROM_HERE, base::BindOnce(btif_dm_start_discovery));return BT_STATUS_SUCCESS;
}
将 蓝牙 扫描任务提交给 main_thread 线程的 btif_dm_start_discovery 函数去处理。
3.3 btif 层
// system/btif/src/btif_dm.ccvoid btif_dm_start_discovery(void) {BTIF_TRACE_EVENT("%s", __func__);// 如果当前正在 搜索 ,就会直接返回if (bta_dm_is_search_request_queued()) {LOG_INFO("%s skipping start discovery because a request is queued",__func__);return;}/* Will be enabled to true once inquiry busy level has been received */btif_dm_inquiry_in_progress = false;/* find nearby devices */BTA_DmSearch(btif_dm_search_devices_evt); // btif_dm_search_devices_evt ,处理扫描结果的回调
}bool bta_dm_is_search_request_queued() {return bta_dm_search_cb.p_pending_search != NULL; // 通过 p_pending_search 是否为null 来判断此刻是否有正在扫描的任务。
}
两个重点:
- btif 层 扫描结果 回调处理函数
btif_dm_search_devices_evt
- 他的处理过程可参照 【android bluetooth 协议分析 03】【蓝牙扫描详解 1】【扫描关键函数 btif_dm_search_devices_evt 分析】
- 是否有正在扫描的任务, 通过
bta_dm_search_cb.p_pending_search
是否为 null 来判断。
我们继续分析是如何触发 扫描的。
3.4 bta 层
// system/bta/dm/bta_dm_api.cc
void BTA_DmSearch(tBTA_DM_SEARCH_CBACK* p_cback) {tBTA_DM_API_SEARCH* p_msg =(tBTA_DM_API_SEARCH*)osi_calloc(sizeof(tBTA_DM_API_SEARCH));p_msg->hdr.event = BTA_DM_API_SEARCH_EVT;p_msg->p_cback = p_cback; // 将 btif 层,扫描结果处理的回调函数放置到 p_cback 中bta_sys_sendmsg(p_msg); // 触发 bta 层 BTA_DM_API_SEARCH_EVT 事件
}
接着看一下 bta 层 是如何处理 BTA_DM_API_SEARCH_EVT
事件的:
// system/bta/dm/bta_dm_main.ccbool bta_dm_search_sm_execute(BT_HDR_RIGID* p_msg) {LOG_INFO("bta_dm_search_sm_execute state:%d, event:0x%x",bta_dm_search_get_state(), p_msg->event);tBTA_DM_MSG* message = (tBTA_DM_MSG*)p_msg;switch (bta_dm_search_cb.state) {case BTA_DM_SEARCH_IDLE:switch (p_msg->event) {case BTA_DM_API_SEARCH_EVT:bta_dm_search_set_state(BTA_DM_SEARCH_ACTIVE); // 设置 bta 层 BTA_DM_SEARCH_ACTIVE 状态bta_dm_search_start(message); // 触发扫描break;...}break;...}return true;
}
bta_dm_search_sm_execute
是 bta层 事件处理函数:- 我们目前先按照最简单的逻辑分析:
- 当前 Bta 层 状态是
BTA_DM_SEARCH_IDLE
无扫描事件的状态 - 此时触发了
BTA_DM_API_SEARCH_EVT
事件
- 当前 Bta 层 状态是
// system/bta/dm/bta_dm_act.cc/********************************************************************************* Function bta_dm_search_start** Description Starts an inquiry*** Returns void*******************************************************************************/
void bta_dm_search_start(tBTA_DM_MSG* p_data) {
/*tBTM_INQUIRY_CMPL 是一个结构体,用于记录 inquiry(搜索)的完成结果。初始化为全 0,用于后续 fallback 调用。
*/tBTM_INQUIRY_CMPL result = {};/*注册 GATT client 到系统中(适用于 BLE 场景)。即使当前是 classic 搜索,也会初始化 BLE GATT 客户端,用于 hybrid 场景(同时使用 BR/EDR 和 Ble 的场景,也叫 双模场景)。
*/bta_dm_gattc_register();// p_bta_dm_cfg->avoid_scatter 表示是否避免设备搜索过程中的信道跳变干扰APPL_TRACE_DEBUG("%s avoid_scatter=%d", __func__,p_bta_dm_cfg->avoid_scatter);/*清除 inquiry 数据库,移除之前搜索过的设备信息。参数为 nullptr 表示清除全部。
*/BTM_ClearInqDb(nullptr);/*bta_dm_search_cb 是全局搜索控制块,用于记录当前搜索状态。p_search_cback 是搜索结果的回调函数,用于将结果通知上层。services 表示搜索的服务掩码,后续用于 Service Discovery。
*/bta_dm_search_cb.p_search_cback = p_data->search.p_cback; // 我们将 btif 层的回调 btif_dm_search_devices_evt 放置在 bta_dm_search_cb.p_search_cback 中bta_dm_search_cb.services = p_data->search.services;/*启动 Bluetooth classic Inquiry 流程。第一个回调 bta_dm_inq_results_cb:单个设备发现回调。第二个回调 bta_dm_inq_cmpl_cb:搜索完成回调。返回状态存储到 result.status 中。
*/result.status = BTM_StartInquiry(bta_dm_inq_results_cb, bta_dm_inq_cmpl_cb); // 触发扫描/*打印 BTM_StartInquiry 启动结果,BTM_CMD_STARTED 表示成功。如果启动失败(如 controller busy 或初始化失败):记录错误日志;构造一个空的 inquiry complete 事件;直接调用 bta_dm_inq_cmpl_cb() 模拟搜索完成,以防搜索流程卡死。
*/APPL_TRACE_EVENT("%s status=%d", __func__, result.status);if (result.status != BTM_CMD_STARTED) {LOG(ERROR) << __func__ << ": BTM_StartInquiry returned "<< std::to_string(result.status);result.num_resp = 0;bta_dm_inq_cmpl_cb((void*)&result);}
}
- 这里我们继续看 btm 层中
BTM_StartInquiry
的处理。 - 同时请关注 bta 层的回调函数, 将 通过 BTM_StartInquiry 注册到 btm 层
bta_dm_inq_results_cb
bta_dm_inq_cmpl_cb
3.5 btm 层
项目 | 内容 |
---|---|
函数名 | BTM_StartInquiry |
模块 | BTM(Bluetooth Manager) |
功能 | 启动 Classic Bluetooth inquiry 设备搜索 |
参数 | 设备发现结果回调 + 完成回调 |
涉及控制块 | btm_cb.btm_inq_vars (全局 Inquiry 状态块) |
- system/stack/btm/btm_inq.cc
/*启动 Inquiry 的主函数。参数:p_results_cb:发现单个设备时调用。p_cmpl_cb:所有搜索完成时调用。*/tBTM_STATUS BTM_StartInquiry(tBTM_INQ_RESULTS_CB* p_results_cb,tBTM_CMPL_CB* p_cmpl_cb) {// 这里暂时没有打开 gd, 该分支暂时不分析if (bluetooth::shim::is_gd_shim_enabled()) {return bluetooth::shim::BTM_StartInquiry(p_results_cb, p_cmpl_cb);}/*BTM 限制同时只能进行一个 Inquiry。inq_active != 0 表示当前有设备搜索在进行。
*/if (btm_cb.btm_inq_vars.inq_active) {LOG_WARN("Active device discovery already in progress inq_active:0x%02x"" state:%hhu counter:%u",btm_cb.btm_inq_vars.inq_active, btm_cb.btm_inq_vars.state,btm_cb.btm_inq_vars.inq_counter);// 已在搜索中则拒绝再次启动return BTM_BUSY;}/* 检查蓝牙是否打开 */if (!BTM_IsDeviceUp()) {LOG(ERROR) << __func__ << ": adapter is not up";return BTM_WRONG_MODE;}BTM_LogHistory(kBtmLogTag, RawAddress::kEmpty, "Classic inquiry started");/* 所有的 Inquiry 状态、回调、结果缓存都存放在 btm_cb.btm_inq_vars 结构体中。*/tBTM_INQUIRY_VAR_ST* p_inq = &btm_cb.btm_inq_vars;/*mode 同时启用 Classic + BLE 的通用搜索(Hybrid 场景)。duration:单位 1.28 秒,如设置为 8 表示 10.24 秒。*/p_inq->inqparms = {};p_inq->inqparms.mode = BTM_GENERAL_INQUIRY | BTM_BLE_GENERAL_INQUIRY;p_inq->inqparms.duration = BTIF_DM_DEFAULT_INQ_MAX_DURATION;/* 设置当前搜索为 active。清除上次搜索的响应数量计数。记录回调函数,用于结果上报。*/p_inq->state = BTM_INQ_ACTIVE_STATE;p_inq->p_inq_cmpl_cb = p_cmpl_cb; // 将 bta 层扫描完成的回调,保存在这里 p_inq->p_inq_results_cb = p_results_cb; // 将 bta 层 扫描结果的回调,保存在这里p_inq->inq_cmpl_info.num_resp = 0; /* Clear the results counter */p_inq->inq_active = p_inq->inqparms.mode;// 打印当前 active mode 状态(用于判断是否 BLE + Classic 混合)LOG_DEBUG("Starting device discovery inq_active:0x%02x",btm_cb.btm_inq_vars.inq_active);/*判断 Controller 是否支持 BLE:如果支持,则启动 BLE 扫描(Hybrid 场景的一部分)。否则从 mode 中去掉 BLE 标志位(避免后续逻辑误判)。*/if (controller_get_interface()->supports_ble()) {LOG_INFO("debug_inquiry %s %d handle ble.", __func__, __LINE__);btm_ble_start_inquiry(p_inq->inqparms.duration); // 开启 ble 扫描} else {LOG_WARN("Trying to do LE scan on a non-LE adapter");p_inq->inqparms.mode &= ~BTM_BLE_INQUIRY_MASK;}/*通知 ACL 层 Inquiry 已启动可能用于 ACL 空闲时调度逻辑优化(例如省电)。
*/btm_acl_update_inquiry_status(BTM_INQUIRY_STARTED);/*特殊处理:SSP(安全简单配对)状态下立即完成直接触发 inquiry complete。用于保障协议状态一致性。
*/if (p_inq->inq_active & BTM_SSP_INQUIRY_ACTIVE) {btm_process_inq_complete(HCI_ERR_MAX_NUM_OF_CONNECTIONS,BTM_GENERAL_INQUIRY);return BTM_CMD_STARTED;}/*清除历史的 Inquiry 过滤条件避免影响本次搜索结果,比如之前设置了过滤条件只允许找某些 Class/地址
*/btm_clr_inq_result_flt();/*分配缓存空间保存响应设备的地址存放本轮发现的设备地址集合。*/p_inq->p_bd_db = (tINQ_BDADDR*)osi_calloc(BT_DEFAULT_BUFFER_SIZE);p_inq->max_bd_entries =(uint16_t)(BT_DEFAULT_BUFFER_SIZE / sizeof(tINQ_BDADDR));/*最终发起 Classic Inquiry 请求(通过 legacy HCI 接口)发起 HCI Inquiry 命令,底层通过 UART/USB 等接口发到蓝牙芯片。使用通用 LAP:0x9E8B33(用于一般设备搜索)
*/bluetooth::legacy::hci::GetInterface().StartInquiry(general_inq_lap, p_inq->inqparms.duration, 0);// 正常启动返回成功return BTM_CMD_STARTED;
}
这里我们分别看一下, ble 和 BR/EDR 是如何发起扫描的:
btm_ble_start_inquiry(p_inq->inqparms.duration):
开启 ble 扫描bluetooth::legacy::hci::GetInterface().StartInquiry(general_inq_lap, p_inq->inqparms.duration, 0);
开启 BR/EDR 扫描
3.5.1 ble 扫描
项目 | 内容 |
---|---|
名称 | btm_ble_start_inquiry |
模块 | BTM(Bluetooth Manager)BLE 子系统 |
作用 | 启动 BLE 设备扫描(inquiry),属于 Hybrid 场景中的 BLE 分支 |
参数 | duration :扫描时间,单位为秒,0 表示取消扫描 |
返回值 | 启动成功或失败状态码(如 BTM_CMD_STARTED 、BTM_BUSY ) |
/*定义函数,输入 duration 表示 BLE 扫描时间(秒)。若 duration = 0,表示取消 inquiry
*/
tBTM_STATUS btm_ble_start_inquiry(uint8_t duration) {/*初始化变量p_ble_cb:全局 BLE 控制块。p_inq:全局 inquiry 控制块(Classic + BLE 共用)。
*/tBTM_STATUS status = BTM_CMD_STARTED;tBTM_BLE_CB* p_ble_cb = &btm_cb.ble_ctr_cb;tBTM_INQUIRY_VAR_ST* p_inq = &btm_cb.btm_inq_vars;// 打印当前是否已有 Active 的 inquiry 状态(bit mask),便于调试。BTM_TRACE_DEBUG("btm_ble_start_inquiry: inq_active = 0x%02x",btm_cb.btm_inq_vars.inq_active);/*检查是否已有 BLE Inquiry 正在运行只允许一个 BLE 扫描任务并发。若有活动,则拒绝本次请求,防止控制器资源冲突。
*/if (p_ble_cb->is_ble_inquiry_active()) {BTM_TRACE_ERROR("LE Inquiry is active, can not start inquiry");return (BTM_BUSY);}// 条件编译块:配置扫描过滤器(用于高级扫描匹配)
#ifdef BLE_SCAN_FILTER_ENABLED
/*如果启用了 BLE_SCAN_FILTER_ENABLED(通常用于 Android 车机、IoT 场景),在启动 BLE inquiry 前设置广播过滤参数,避免接收到无关设备数据,节省能耗与处理能力。
*//* 清除索引 0 的过滤器设置。*/BTM_BleAdvFilterParamSetup(BTM_BLE_SCAN_COND_DELETE,static_cast<tBTM_BLE_PF_FILT_INDEX>(0), nullptr,base::Bind(btm_ble_scan_filt_param_cfg_evt));// 配置一个“允许全部广播”的默认过滤器,实际开发中也可配置白名单/服务 UUID 匹配。auto adv_filt_param = std::make_unique<btgatt_filt_param_setup_t>();/* Add an allow-all filter on index 0*/adv_filt_param->dely_mode = IMMEDIATE_DELY_MODE;adv_filt_param->feat_seln = ALLOW_ALL_FILTER;adv_filt_param->filt_logic_type = BTA_DM_BLE_PF_FILT_LOGIC_OR;adv_filt_param->list_logic_type = BTA_DM_BLE_PF_LIST_LOGIC_OR;adv_filt_param->rssi_low_thres = LOWEST_RSSI_VALUE;adv_filt_param->rssi_high_thres = LOWEST_RSSI_VALUE;BTM_BleAdvFilterParamSetup(BTM_BLE_SCAN_COND_ADD, static_cast<tBTM_BLE_PF_FILT_INDEX>(0),std::move(adv_filt_param), base::Bind(btm_ble_scan_filt_param_cfg_evt));
#endif/*扫描控制与 HCI 配置*/if (!p_ble_cb->is_ble_scan_active()) { // 如果当前没有 BLE 扫描:cache.ClearAll(); // 清除扫描缓存(可能是广播信息的临时存储)。/*设置 BLE 扫描参数:Active 模式(主机会发送 SCAN_REQ 请求响应 SCAN_RSP);使用低延迟参数,适合短时间内快速发现设备;地址类型与扫描策略设置为“接收所有广播”。*/btm_send_hci_set_scan_params(BTM_BLE_SCAN_MODE_ACTI, BTM_BLE_LOW_LATENCY_SCAN_INT,BTM_BLE_LOW_LATENCY_SCAN_WIN,btm_cb.ble_ctr_cb.addr_mgnt_cb.own_addr_type, SP_ADV_ALL);p_ble_cb->inq_var.scan_type = BTM_BLE_SCAN_MODE_ACTI;// 启动扫描。btm_ble_start_scan();} else if ((p_ble_cb->inq_var.scan_interval !=BTM_BLE_LOW_LATENCY_SCAN_INT) ||(p_ble_cb->inq_var.scan_window != BTM_BLE_LOW_LATENCY_SCAN_WIN)) { // 否则,如果当前扫描参数不是“低延迟模式”,则重设BTM_TRACE_DEBUG("%s, restart LE scan with low latency scan params",__func__);// 先关闭当前扫描。btm_send_hci_scan_enable(BTM_BLE_SCAN_DISABLE, BTM_BLE_DUPLICATE_ENABLE);// 设置为低延迟参数。btm_send_hci_set_scan_params(BTM_BLE_SCAN_MODE_ACTI, BTM_BLE_LOW_LATENCY_SCAN_INT,BTM_BLE_LOW_LATENCY_SCAN_WIN,btm_cb.ble_ctr_cb.addr_mgnt_cb.own_addr_type, SP_ADV_ALL);// 重新启用扫描。btm_send_hci_scan_enable(BTM_BLE_SCAN_ENABLE, BTM_BLE_DUPLICATE_DISABLE);}// BLE inquiry 启动成功后的后续操作if (status == BTM_CMD_STARTED) {// 设置内部状态标志p_inq->inq_active |= BTM_BLE_GENERAL_INQUIRY;p_ble_cb->set_ble_inquiry_active(); // 设置 BLE Inquiry 启动标志。BTM_TRACE_DEBUG("btm_ble_start_inquiry inq_active = 0x%02x",p_inq->inq_active);// 设置 Inquiry 超时定时器if (duration != 0) {/* 如果指定了 duration,则在 mainloop 上设置定时器,到期后会调用 btm_ble_inquiry_timer_timeout() 来自动停止扫描。*/uint64_t duration_ms = duration * 1000;alarm_set_on_mloop(p_ble_cb->inq_var.inquiry_timer, duration_ms,btm_ble_inquiry_timer_timeout, NULL);}}// 返回本次 BLE Inquiry 启动状态。return status;
}
btm_ble_start_inquiry()│├─► 检查是否已有 BLE Inquiry 活动中 → 是 → 返回 BTM_BUSY│├─► 清除旧过滤器 + 添加新过滤器(可选)│├─► 若无扫描任务 → 设置参数 + 启动扫描│ ││ └─► 低延迟扫描参数(主动扫描)│├─► 若已有扫描任务但参数不符 → 重设参数 + 重启扫描│├─► 设置 BLE Inquiry 状态位├─► 启动定时器(用于超时后自动结束)│└─► 返回 BTM_CMD_STARTED
3.5.1.1 btm_send_hci_set_scan_params
项目 | 内容 |
---|---|
名称 | btm_send_hci_set_scan_params |
模块 | BTM(Bluetooth Manager) |
作用 | 设置 BLE 扫描参数,发出 HCI 命令 |
涉及类型 | 扫描类型、间隔、窗口、设备地址类型、扫描策略 |
特殊支持 | 支持 BLE 扩展广播(Extended Advertising) |
// system/stack/btm/btm_ble_gap.cc/*
设置 BLE 的 HCI 扫描参数,包括:scan_type: 主动扫描(Active)或被动扫描(Passive);scan_int: 扫描间隔(单位为 0.625ms);scan_win: 扫描窗口,表示每次扫描持续多久;addr_type_own: 本地设备地址类型(public / random);scan_filter_policy: 控制是否接收未配对设备广播等。*/
void btm_send_hci_set_scan_params(uint8_t scan_type, uint16_t scan_int,uint16_t scan_win,tBLE_ADDR_TYPE addr_type_own,uint8_t scan_filter_policy) {/*判断是否支持 扩展广播(Extended Advertising)如果底层控制器支持 BLE 5.0 的扩展广播特性,则走新版本的 HCI 命令路径(使用 PHY 配置)。*/if (controller_get_interface()->supports_ble_extended_advertising()) {/*填充 PHY 配置结构体扫描参数赋值到结构体 scanning_phy_cfg,用于扩展命令接口。支持未来多 PHY(如 1M、Coded PHY)的扩展能力。*/scanning_phy_cfg phy_cfg;phy_cfg.scan_type = scan_type;phy_cfg.scan_int = scan_int;phy_cfg.scan_win = scan_win;/*发送 HCI 扩展命令btsnd_hcic_ble_set_extended_scan_params:用于 BLE 5.0 的 HCI 命令;参数 1 表示 1 个 PHY(通常是 LE 1M);phy_cfg 包含具体配置;会发出如下 HCI 命令:HCI_LE_Set_Extended_Scan_Parameters (0x2041)
*/btsnd_hcic_ble_set_extended_scan_params(addr_type_own, scan_filter_policy,1, &phy_cfg);} else {
/*否则,控制器不支持扩展广播调用 btsnd_hcic_ble_set_scan_params,底层会发送标准 BLE 4.x 命令: HCI_LE_Set_Scan_Parameters (0x200B)*/btsnd_hcic_ble_set_scan_params(scan_type, scan_int, scan_win, addr_type_own,scan_filter_policy);}
}
btm_send_hci_set_scan_params()│├─► 判断是否支持 BLE 扩展广播?│ ││ ├─► 是 → 构造 PHY 配置结构 → 发送 HCI 扩展命令(0x2041)│ ││ └─► 否 → 发送经典 HCI 扫描参数命令(0x200B)│└─► 配置完成,等待扫描启动
参数 | 含义 | 说明 |
---|---|---|
scan_type | 扫描类型 | 0x00 被动、0x01 主动 |
scan_int | 扫描间隔 | 单位 0.625ms(如 0x50 = 50ms) |
scan_win | 扫描窗口 | 持续扫描时间,不能大于间隔 |
addr_type_own | 本地地址类型 | public(0)或 random(1) |
scan_filter_policy | 扫描过滤策略 | 0 = 接收所有广播;1 = 只接收白名单设备 |
关键点 | 内容 |
---|---|
功能 | 向 Controller 设置 BLE 扫描参数 |
适配性 | 兼容传统 BLE 与 BLE 5.0 扩展广播 |
应用场景 | BLE 设备发现(车钥匙、手环、广播 App) |
底层输出 | 生成 HCI 命令(0x200B 或 0x2041 ) |
注意事项 | scan_window ≤ scan_interval ;主动扫描更耗电但能获取更多信息 |
1. BLE 扩展广播 vs 传统广播
对比项 | 传统广播(Legacy Advertising) | 扩展广播(Extended Advertising) |
---|---|---|
引入版本 | BLE 4.0 ~ BLE 4.2 | BLE 5.0 及以上 |
广播信道 | 3 个主信道(37, 38, 39) | 初始使用主信道,后续切换到数据信道 |
广播数据长度 | 最多 31 字节 | 最多支持 1650 字节(分包) |
广播包结构 | 单个 PDU 包 | 可分片,多个 AUX_ADV_IND 包 |
广播类型数量 | 最多 1 个可用广播集 | 支持多个广播集(最高支持 4~5 个并发) |
定向广播支持 | 支持,受限 | 支持更多灵活形式(周期定向广播) |
周期广播支持 | ❌ 不支持 | ✅ 支持(Periodic Advertising) |
接收端支持 | 所有 BLE 设备都支持 | 需 BLE 5.0+ 控制器和 Host 配合 |
功耗 | 较低,简单广播 | 略高,适合需要大数据量/多广播需求 |
典型使用场景 | 普通 BLE 外设、手环、Beacon | BLE Mesh、车钥匙广播、音箱配网、AR设备 |
广播能力 | 只能广播简要信息 | 可以广播图标、位置信息、音频元数据等 |
是否支持多 PHY | 否 | 是(1M PHY / 2M PHY / Coded PHY) |
被扫描响应(ScanRsp) | 支持 31 字节 | 可扩展 Scan Response,多包响应 |
定向广播范围 | 一般为连接请求 | 可做无连接广播+定向数据投送 |
控制器要求 | 支持 BLE 即可 | 需支持 HCI_LE_Set_Extended_Advertising |
使用场景举例:
场景 | 推荐广播方式 | 原因说明 |
---|---|---|
手环/心率带 | 传统广播 | 数据量小,兼容性优先 |
车钥匙广播位置信息 | 扩展广播 | 支持高数据量 + 周期广播 |
配网设备(如 BLE 音箱) | 扩展广播 | 可发多个信息段(二维码、Wi-Fi 参数) |
低成本 Beacon | 传统广播 | 节省资源、电量小 |
广播大型业务标识符(URL、UUID) | 扩展广播 | 超过 31 字节,需要 AUX_ADV |
BLE Mesh 节点发现 | 扩展广播 | Mesh 使用定向/周期广播发邻居信息 |
技术设计动因总结:
设计差异 | 背后目的 |
---|---|
数据包长度扩展 | 支持发送更复杂的信息,如 NDEF、地图数据、图像摘要等 |
分片 + 多通道 | 减少主信道拥堵,提升广播可靠性 |
多广播集 | 支持多个 profile 并存,例如音频 + 位置信息 |
周期广播支持 | 实现 BLE 无连接定向广播,如音频同步、广播定位 |
常见问题:
问题 | 解答 |
---|---|
所有设备都能接收扩展广播吗? | ❌ 否,必须 BLE 5.0+ 的芯片 + Host Stack |
使用扩展广播是否一定更好? | ❌ 否,若数据量小、兼容性优先,应使用传统广播 |
周期广播是不是连接了设备? | ❌ 否,是“无连接”的数据同步机制 |
小结
类型 | 优点 | 局限 | 使用建议 |
---|---|---|---|
传统广播 | 简单、兼容性好、功耗低 | 数据少、不支持多路广播 | 适用于小设备、通用 BLE 方案 |
扩展广播 | 数据大、灵活、支持周期广播 | 控制器要求高、功耗稍高 | 适用于 BLE 5.0+ 项目,如车载、Mesh、AR |
2. btsnd_hcic_ble_set_extended_scan_params
// system/stack/hcic/hciblecmds.cc
void btsnd_hcic_ble_set_extended_scan_params(uint8_t own_address_type,uint8_t scanning_filter_policy,uint8_t scanning_phys,scanning_phy_cfg* phy_cfg) {BT_HDR* p = (BT_HDR*)osi_malloc(HCI_CMD_BUF_SIZE);uint8_t* pp = (uint8_t*)(p + 1);int phy_cnt =std::bitset<std::numeric_limits<uint8_t>::digits>(scanning_phys).count();uint16_t param_len = 3 + (5 * phy_cnt);p->len = HCIC_PREAMBLE_SIZE + param_len;p->offset = 0;UINT16_TO_STREAM(pp, HCI_LE_SET_EXTENDED_SCAN_PARAMETERS); // 0x2041UINT8_TO_STREAM(pp, param_len);UINT8_TO_STREAM(pp, own_address_type);UINT8_TO_STREAM(pp, scanning_filter_policy);UINT8_TO_STREAM(pp, scanning_phys);for (int i = 0; i < phy_cnt; i++) {UINT8_TO_STREAM(pp, phy_cfg[i].scan_type);UINT16_TO_STREAM(pp, phy_cfg[i].scan_int);UINT16_TO_STREAM(pp, phy_cfg[i].scan_win);}btu_hcif_send_cmd(LOCAL_BR_EDR_CONTROLLER_ID, p); // 通过 hci 发送给 controller
}
3. btsnd_hcic_ble_set_scan_params
// system/stack/hcic/hciblecmds.ccvoid btsnd_hcic_ble_set_scan_params(uint8_t scan_type, uint16_t scan_int,uint16_t scan_win, uint8_t addr_type_own,uint8_t scan_filter_policy) {BT_HDR* p = (BT_HDR*)osi_malloc(HCI_CMD_BUF_SIZE);uint8_t* pp = (uint8_t*)(p + 1);p->len = HCIC_PREAMBLE_SIZE + HCIC_PARAM_SIZE_BLE_WRITE_SCAN_PARAM;p->offset = 0;UINT16_TO_STREAM(pp, HCI_BLE_WRITE_SCAN_PARAMS); // 0x200bUINT8_TO_STREAM(pp, HCIC_PARAM_SIZE_BLE_WRITE_SCAN_PARAM);UINT8_TO_STREAM(pp, scan_type);UINT16_TO_STREAM(pp, scan_int);UINT16_TO_STREAM(pp, scan_win);UINT8_TO_STREAM(pp, addr_type_own);UINT8_TO_STREAM(pp, scan_filter_policy);btu_hcif_send_cmd(LOCAL_BR_EDR_CONTROLLER_ID, p);
}
99 2025-01-01 18:46:44.390599 host controller HCI_CMD 11 Sent LE Set Scan ParametersFrame 99: 11 bytes on wire (88 bits), 11 bytes captured (88 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI Command - LE Set Scan ParametersCommand Opcode: LE Set Scan Parameters (0x200b)Parameter Total Length: 7Scan Type: Active (0x01)Scan Interval: 8000 (5000 msec)Scan Window: 1600 (1000 msec)Own Address Type: Public Device Address (0x00)Scan Filter Policy: Accept all advertisements, except directed advertisements not addressed to this device (0x00)[Response in frame: 100][Command-Response Delta: 2.792ms]
- 而我们实际在车上发送的 也是 普通广播
3.5.1.2 btm_send_hci_scan_enable
// system/stack/btm/btm_ble_gap.ccstatic void btm_send_hci_scan_enable(uint8_t enable,uint8_t filter_duplicates) {if (controller_get_interface()->supports_ble_extended_advertising()) {// 如果支持扩展广播btsnd_hcic_ble_set_extended_scan_enable(enable, filter_duplicates, 0x0000,0x0000);} else {// 走传统btsnd_hcic_ble_set_scan_enable(enable, filter_duplicates);}
}
1. btsnd_hcic_ble_set_extended_scan_enable
// system/stack/hcic/hciblecmds.cc
void btsnd_hcic_ble_set_extended_scan_enable(uint8_t enable,uint8_t filter_duplicates,uint16_t duration,uint16_t period) {BT_HDR* p = (BT_HDR*)osi_malloc(HCI_CMD_BUF_SIZE);uint8_t* pp = (uint8_t*)(p + 1);const int param_len = 6;p->len = HCIC_PREAMBLE_SIZE + param_len;p->offset = 0;UINT16_TO_STREAM(pp, HCI_LE_SET_EXTENDED_SCAN_ENABLE); // 0x2042UINT8_TO_STREAM(pp, param_len);UINT8_TO_STREAM(pp, enable);UINT8_TO_STREAM(pp, filter_duplicates);UINT16_TO_STREAM(pp, duration);UINT16_TO_STREAM(pp, period);btu_hcif_send_cmd(LOCAL_BR_EDR_CONTROLLER_ID, p);
}
2. btsnd_hcic_ble_set_scan_enable
// system/stack/hcic/hciblecmds.cc
void btsnd_hcic_ble_set_scan_enable(uint8_t scan_enable, uint8_t duplicate) {BT_HDR* p = (BT_HDR*)osi_malloc(HCI_CMD_BUF_SIZE);uint8_t* pp = (uint8_t*)(p + 1);p->len = HCIC_PREAMBLE_SIZE + HCIC_PARAM_SIZE_BLE_WRITE_SCAN_ENABLE;p->offset = 0;UINT16_TO_STREAM(pp, HCI_BLE_WRITE_SCAN_ENABLE); // 0x200cUINT8_TO_STREAM(pp, HCIC_PARAM_SIZE_BLE_WRITE_SCAN_ENABLE);UINT8_TO_STREAM(pp, scan_enable);UINT8_TO_STREAM(pp, duplicate);btu_hcif_send_cmd(LOCAL_BR_EDR_CONTROLLER_ID, p);
}
101 2025-01-01 18:46:44.393771 host controller HCI_CMD 6 Sent LE Set Scan EnableFrame 101: 6 bytes on wire (48 bits), 6 bytes captured (48 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI Command - LE Set Scan EnableCommand Opcode: LE Set Scan Enable (0x200c)Parameter Total Length: 2Scan Enable: true (0x01)Filter Duplicates: false (0x00)[Response in frame: 102][Command-Response Delta: 3.875ms]
- 开启 ble 扫描
3.5.2 BR/EDR 扫描
bluetooth::legacy::hci::GetInterface().StartInquiry(general_inq_lap, p_inq->inqparms.duration, 0);
开启 BR/EDR 扫描
- 这里实际上调用 btsnd_hcic_inquiry
// system/stack/hcic/hcicmds.ccbluetooth::legacy::hci::Interface interface_ = {// LINK_CONTROL.StartInquiry = btsnd_hcic_inquiry, // OCF 0x0401.InquiryCancel = btsnd_hcic_inq_cancel, // OCF 0x0402.Disconnect = btsnd_hcic_disconnect, // OCF 0x0406.ChangeConnectionPacketType = btsnd_hcic_change_conn_type, // OCF 0x040F,.StartRoleSwitch = btsnd_hcic_switch_role, // OCF 0x080B,.ReadLocalOobExtendData = btsnd_hcic_read_local_oob_ext_data,// OCF 0x0C7D,
};const bluetooth::legacy::hci::Interface&
bluetooth::legacy::hci::GetInterface() {return interface_;
}
// system/stack/hcic/hcicmds.ccstatic void btsnd_hcic_inquiry(const LAP inq_lap, uint8_t duration,uint8_t response_cnt) {BT_HDR* p = (BT_HDR*)osi_malloc(HCI_CMD_BUF_SIZE);uint8_t* pp = (uint8_t*)(p + 1);p->len = HCIC_PREAMBLE_SIZE + HCIC_PARAM_SIZE_INQUIRY;p->offset = 0;UINT16_TO_STREAM(pp, HCI_INQUIRY); // 0x0401UINT8_TO_STREAM(pp, HCIC_PARAM_SIZE_INQUIRY);LAP_TO_STREAM(pp, inq_lap);UINT8_TO_STREAM(pp, duration);UINT8_TO_STREAM(pp, response_cnt);btu_hcif_send_cmd(LOCAL_BR_EDR_CONTROLLER_ID, p);
}
103 2025-01-01 18:46:44.398332 host controller HCI_CMD 9 Sent InquiryFrame 103: 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: 104][Command-Pending Delta: 2.167ms][Response in frame: 1133][Command-Response Delta: 12801.524ms]
3.6 btu 层
无论 BR/EDR 还是 Ble 最终都会触发 btu 层的 btu_hcif_send_cmd
函数将 命令 + 参数 发送给蓝牙控制器。
btu_hcif_send_cmd
该函数位于 BTU 模块(Bluetooth Upper Layer) 中,用于向控制器发送 HCI 命令,是 Host-Controller 通信的核心路径之一
参数 | 说明 |
---|---|
controller_id | 预留参数,支持多控制器(大多数情况下未使用) |
p_buf | 指向 HCI 命令缓冲区(BT_HDR),其中封装了 HCI 命令 |
// system/stack/btu/btu_hcif.cc
void btu_hcif_send_cmd(UNUSED_ATTR uint8_t controller_id, const BT_HDR* p_buf) {if (!p_buf) return;/*opcode: HCI 命令的操作码;stream: 指向实际 HCI 命令数据部分的指针(跳过 BT_HDR 的 offset);vsc_callback: 预留的回调函数指针,用于 Vendor Specific Command(VSC)等需要异步通知的命令。
*/uint16_t opcode;const uint8_t* stream = p_buf->data + p_buf->offset;void* vsc_callback = NULL;/* 从流中读取 2 字节的 HCI 命令 Opcode(小端)opcode 编码包含:OGF(Opcode Group Field) = 高 6 位OCF(Opcode Command Field)= 低 10 位
*/STREAM_TO_UINT16(opcode, stream);// Eww...horrible hackery here/* If command was a VSC, then extract command_complete callback */
/*提取回调函数(仅用于 VSC/BLE特殊命令)条件判断:若命令属于 Vendor Specific Command(VSC);或者是特殊 BLE 命令(如 HCI_BLE_RAND、HCI_BLE_ENCRYPT);则将 BT_HDR 结构后面附加的数据作为回调函数指针取出。说明:BT_HDR 后可附带用户定义的 void* 区域(通常用于回调);*((void**)(p_buf + 1)) 表示跳过 BT_HDR 后取回调指针。“horrible hackery” 是 AOSP 原作者对这种 “结构内强制附加回调” 的吐槽,因为它违背了类型安全。
*/if ((opcode & HCI_GRP_VENDOR_SPECIFIC) == HCI_GRP_VENDOR_SPECIFIC ||(opcode == HCI_BLE_RAND) || (opcode == HCI_BLE_ENCRYPT)) {vsc_callback = *((void**)(p_buf + 1));}// Skip parameter length before logging// 跳过 HCI 命令中的参数长度字段(1 字节),为日志记录做准备:只记录实际参数内容,不包括长度。stream++;// 记录发送的命令信息,用于性能、行为分析或统计系统;btu_hcif_log_command_metrics(opcode, stream,android::bluetooth::hci::STATUS_UNKNOWN, false);// transmit_command(...) 是 HCI 层统一的命令发送接口;bluetooth::shim::hci_layer_get_interface()->transmit_command(p_buf/*命令缓冲区*/, btu_hcif_command_complete_evt/*用于处理 Command Complete Event 回调*/, btu_hcif_command_status_evt/*用于处理 Command Status Event 回调*/,vsc_callback/*若有 vendor 回调,则设置,否则为 nullptr*/);
}
HCI 命令格式如下:
字段 | 长度 |
---|---|
Opcode | 2 字节 |
参数长度 | 1 字节 |
参数 | N 字节 |
btu_hcif_send_cmd(p_buf)├── 检查 p_buf 是否为空├── 提取 opcode├── 若是 VSC 或 BLE 随机命令等 → 提取回调函数├── 跳过长度字段├── 上报 metrics 日志└── 通过 shim hci 接口发送命令(附带回调)
3.7 hci 层
我们先来看一下 bluetooth::shim::hci_layer_get_interface()
// system/main/shim/hci_layer.ccstatic hci_t interface = {.set_data_cb = set_data_cb,.transmit_command = transmit_command, // 调用这里.transmit_command_futured = transmit_command_futured,.transmit_downward = transmit_downward};const hci_t* bluetooth::shim::hci_layer_get_interface() {packet_fragmenter = packet_fragmenter_get_interface();packet_fragmenter->init(&packet_fragmenter_callbacks);return &interface;
}
static void transmit_command(const BT_HDR* command,command_complete_cb complete_callback,command_status_cb status_callback, void* context) {if (bluetooth::common::init_flags::gd_rust_is_enabled()) {rust::transmit_command(command, complete_callback, status_callback,context);} else {// 我们这里 先只看 c++ 的实现cpp::transmit_command(command, complete_callback, status_callback, context);}
}
参数名 | 说明 |
---|---|
command | 指向封装好的 HCI 命令缓冲区(类型为 BT_HDR* ) |
complete_callback | 命令执行完成时(Command Complete Event)回调 |
status_callback | 命令开始处理时(Command Status Event)回调 |
context | 自定义上下文指针,用于在回调中传递 |
- 该函数封装并发送 HCI 命令,底层调用 GD HCI 层的
EnqueueCommand()
发送,并通过 handler 注册回调。
static void transmit_command(const BT_HDR* command,command_complete_cb complete_callback,command_status_cb status_callback, void* context) {CHECK(command != nullptr);// 获取指向实际 HCI 命令数据的指针(考虑 BT_HDR.offset 偏移)const uint8_t* data = command->data + command->offset;// 获取命令数据的总长度size_t len = command->len;
/*校验命令格式合法性:每个 HCI 命令至少包含:Opcode(2字节)参数长度(1字节)
*/CHECK(len >= (kCommandOpcodeSize + kCommandLengthSize));/*
提取 Opcode(操作码),使用小端格式组合两个字节。data[0]: 低字节(OCF)data[1]: 高字节(OGF)
*/uint16_t command_op_code = (data[1] << 8 | data[0]);/*注释说明:GD(Gabeldorsche)HCI 层 API 要求:显式提供 Opcode;自动计算 payload 长度;不需要 Host 额外填写 Opcode/Len 字段。
*/// Gd stack API requires opcode specification and calculates length, so// no need to provide opcode or length here.// 跳过前 3 字节(Opcode + Length),只保留参数部分,构建 payload。data += (kCommandOpcodeSize + kCommandLengthSize);len -= (kCommandOpcodeSize + kCommandLengthSize);// 将原始 uint16_t Opcode 强制转换为 GD 栈使用的类型 OpCode。auto op_code = static_cast<const bluetooth::hci::OpCode>(command_op_code);// 创建 payload 数据包(智能指针),只包含参数部分。
// MakeUniquePacket() 是 GD 栈中用于构建封装好的 L2CAP/HCI 数据结构的函数。auto payload = MakeUniquePacket(data, len);/*构建完整的 HCI Command Packet(封装了 Opcode + 参数)。CommandBuilder 是 GD 栈中用于构建命令的统一接口;生成后的 packet 会用于底层发送。
*/auto packet =bluetooth::hci::CommandBuilder::Create(op_code, std::move(payload));// OpCodeText() 将 Opcode 转换为可读字符串,如 "HCI_RESET"。LOG_DEBUG("Sending command %s", bluetooth::hci::OpCodeText(op_code).c_str());// 判断当前 Opcode 是否属于只返回 Command Status Event(而非 Command Complete)的类型if (bluetooth::hci::Checker::IsCommandStatusOpcode(op_code)) {// 将 command 封装为智能指针,延迟释放。适用于 Command Status 模式,回调后可能还要用原始 command 数据。auto command_unique = std::make_unique<OsiObject>(command);/*发送命令:通过 shim 层 HCI 接口 EnqueueCommand() 实际将命令排入发送队列;注册 OnTransmitPacketStatus 作为异步回调;回调触发时会携带:status_callbackcontext原始命令(封装在 command_unique 中)*/bluetooth::shim::GetHciLayer()->EnqueueCommand(std::move(packet), bluetooth::shim::GetGdShimHandler()->BindOnce(OnTransmitPacketStatus, status_callback, context,std::move(command_unique)));} else { // 如果不是 Command Status 类型,则默认是 Command Complete 类型。// 同样发送命令,但回调函数绑定的是 OnTransmitPacketCommandComplete。bluetooth::shim::GetHciLayer()->EnqueueCommand(std::move(packet),bluetooth::shim::GetGdShimHandler()->BindOnce(OnTransmitPacketCommandComplete, complete_callback, context));/*命令数据不再需要,释放 command 对应内存;只在 Command Complete 情况下立即释放;如果是 Command Status,延迟释放(封装在 OsiObject 中)。*/osi_free(const_cast<void*>(static_cast<const void*>(command)));}
}
transmit_command(command)
│
├─ 校验参数合法性
├─ 提取 Opcode / 参数
├─ 构建 Command Packet(GD 栈格式)
├─ 判断是否为 Command Status 类型
│ ├─ 是 → 使用 OnTransmitPacketStatus 作为回调
│ └─ 否 → 使用 OnTransmitPacketCommandComplete 回调并释放命令内存
│
└─ 调用 shim::HciLayer::EnqueueCommand() 发送命令
// system/main/shim/entry.cchci::HciLayer* GetHciLayer() {return Stack::GetInstance()->GetStackManager()->GetInstance<hci::HciLayer>();
}// system/gd/stack_manager.h
StackManager* Stack::GetStackManager() {std::lock_guard<std::recursive_mutex> lock(mutex_);ASSERT(is_running_);return &stack_manager_;
}template <class T>T* GetInstance() const {return static_cast<T*>(registry_.Get(&T::Factory));}
最终 会调用到 hci::HciLayer
模块: 可以参看下面的文章。
【android bluetooth 框架分析 02】【Module详解 5】【HciLayer 模块介绍】
四、小结
本节 从 app 侧到 蓝牙协议栈, 从上到下, 梳理了 扫描的触发流程。但并没有分析 扫描上报的流程 。
介于篇幅原因: 将在下一篇, 分享扫描到设备后 如何一层层上报到 app 侧。尽情期待。
🧠 写这篇文章的过程中,调了 log,翻了源码,扒了堆栈,连咖啡都续了两杯——
要是你看到这里还不点赞、不评论、不分享,那我可真是……白耗电了!⚡️💬 评论区不是摆设!你一句“写得不错”胜过我刷 100 行 logcat。
👍 点赞一下,就像打了个补丁,瞬间性能飙升300%。
🔁 转发分享,更是帮我拉满活跃用户数(KPI你懂的)。别做沉默的系统进程,来点互动,不然我都不好意思发下一篇了😂!
——Android 是开源的,但我写博客的动力,得靠你手指点一点!