当前位置: 首页 > news >正文

【android bluetooth 协议分析 03】【蓝牙扫描详解 2】【app触发蓝牙扫描后,协议栈都做了那些事情】

一、引言

最近项目上报了一个蓝牙耳机扫描慢的问题, 借这个问题。 笨叔来分享一下 在 aosp 蓝牙协议栈里面扫描的流程。

大家可以思考一下 :

  1. 应用侧触发扫描 到 最终 协议栈 发送扫描命令,中间都有那些流程?
  2. 每一层都做了那些处理?
  3. 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 来判断此刻是否有正在扫描的任务。
}

两个重点:

  1. btif 层 扫描结果 回调处理函数 btif_dm_search_devices_evt
    1. 他的处理过程可参照 【android bluetooth 协议分析 03】【蓝牙扫描详解 1】【扫描关键函数 btif_dm_search_devices_evt 分析】
  2. 是否有正在扫描的任务, 通过 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 事件
// 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 是如何发起扫描的:

  1. btm_ble_start_inquiry(p_inq->inqparms.duration): 开启 ble 扫描
  2. 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_STARTEDBTM_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 命令(0x200B0x2041
注意事项scan_window ≤ scan_interval;主动扫描更耗电但能获取更多信息
1. BLE 扩展广播 vs 传统广播
对比项传统广播(Legacy Advertising)扩展广播(Extended Advertising)
引入版本BLE 4.0 ~ BLE 4.2BLE 5.0 及以上
广播信道3 个主信道(37, 38, 39)初始使用主信道,后续切换到数据信道
广播数据长度最多 31 字节最多支持 1650 字节(分包)
广播包结构单个 PDU 包可分片,多个 AUX_ADV_IND 包
广播类型数量最多 1 个可用广播集支持多个广播集(最高支持 4~5 个并发)
定向广播支持支持,受限支持更多灵活形式(周期定向广播)
周期广播支持❌ 不支持✅ 支持(Periodic Advertising)
接收端支持所有 BLE 设备都支持需 BLE 5.0+ 控制器和 Host 配合
功耗较低,简单广播略高,适合需要大数据量/多广播需求
典型使用场景普通 BLE 外设、手环、BeaconBLE 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 命令格式如下:

字段长度
Opcode2 字节
参数长度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 是开源的,但我写博客的动力,得靠你手指点一点!


http://www.dtcms.com/a/282393.html

相关文章:

  • 跨平台 App 如何无痛迁移到鸿蒙系统?全流程实战+Demo 教程
  • 八股文——包装类
  • Android 升级targetSdk无法启动服务
  • 动态规划题解——分割等和子集【LeetCode】
  • 面向向量检索的教育QA建模:九段日本文化研究所日本语学院的Prompt策略分析(6 / 500)
  • 知识点3:python-sdk 核心概念(prompt、image、context)
  • 有哪些好用的原型设计软件?墨刀、Axure等测评对比
  • MAC 苹果版Adobe Photoshop 2019下载及保姆级安装教程!!
  • Prompt Engineering 快速入门+实战案例
  • C#.NET BackgroundService 详解
  • 增程式汽车底盘设计cad【9张】三维图+设计说明书
  • 机器学习sklearn入门:归一化和标准化
  • 深入解析 AWS RDS Proxy
  • VirtualBox 中 CentOS 7 双网卡配置静态 IP
  • 用 Ray 跨节点调用 GPU 部署 DeepSeek 大模型,实现分布式高效推理
  • 「计算机网络」笔记(一)
  • qt 中英文翻译 如何配置和使用
  • 面试150 二叉树的锯齿层次遍历
  • YOLO13正式发布!考虑将yolov13的创新点融合到半监督中,构建YOLOv13_ssod
  • Qt 将触摸事件转换为鼠标事件(Qt4和Qt5及以上版本)
  • Qt 的信号槽机制中,使用 `connect` 函数时,第五个参数是 **连接类型(Connection Type)**,
  • Ubuntu中man手册不全解决以及man手册中英文切换方法
  • 若依框架下前后端分离项目交互流程详解
  • 20、鸿蒙Harmony Next开发:组件导航(Navigation)和页面路由(@ohos.router)
  • 现代人工智能综合分类:大模型时代的架构、模态与生态系统
  • Node.js ORM框架Sequelize 一对一(One-to-One)、一对多(One-to-Many)和多对多(Many-to-Many)
  • NDVI、噪声和细微差别:使用卫星时间序列进行土地覆盖分类
  • K近邻算法的分类与回归应用场景
  • LVS集群调度器
  • 2022年CIE SCI2区TOP,NSGA-II+直升机-无人机搜救任务分配,深度解析+性能实测