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

发布于:2025-07-17 ⋅ 阅读:(17) ⋅ 点赞:(0)

一、引言

最近项目上报了一个蓝牙耳机扫描慢的问题, 借这个问题。 笨叔来分享一下 在 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
        @Override
        public 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.cc

void 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.cc

bool 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.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); // 0x2041
  UINT8_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.cc

void 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); // 0x200b
  UINT8_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 Parameters

Frame 99: 11 bytes on wire (88 bits), 11 bytes captured (88 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI Command - LE Set Scan Parameters
    Command Opcode: LE Set Scan Parameters (0x200b)
    Parameter Total Length: 7
    Scan 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.cc

static 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); // 0x2042
  UINT8_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); // 0x200c
  UINT8_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 Enable


Frame 101: 6 bytes on wire (48 bits), 6 bytes captured (48 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI Command - LE Set Scan Enable
    Command Opcode: LE Set Scan Enable (0x200c)
    Parameter Total Length: 2
    Scan 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.cc

bluetooth::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.cc

static 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); // 0x0401
  UINT8_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 Inquiry

Frame 103: 9 bytes on wire (72 bits), 9 bytes captured (72 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI Command - Inquiry
    Command Opcode: Inquiry (0x0401)
    Parameter Total Length: 5
    LAP: 0x9e8b33
    Inquiry 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.cc

static 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_callback
				
				context
				
				原始命令(封装在 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.cc

hci::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 是开源的,但我写博客的动力,得靠你手指点一点!



网站公告

今日签到

点亮在社区的每一天
去签到