前面我们提到了 蓝牙协议栈中的 Properties , 这篇文章是 他的补充。
- 【android bluetooth 框架分析 04】【bt-framework 层详解 6】【Properties介绍】
1. 设计初衷与核心问题
1. 为什么要设计 DeviceProperties
?
在 Android 蓝牙实际使用中,系统需反复处理设备的发现、服务解析、配对、连接等场景。在这些过程中,远程设备的信息管理混乱、数据缺失、不一致是普遍存在的问题。
2. DeviceProperties
解决的问题:
场景 | 待解决问题 | 设计目标 |
---|---|---|
搜索 | 同一设备多次出现在列表中,名称等信息丢失 | 唯一标识设备、统一管理搜索信息 |
SDP | 每次连接都重新做服务发现,耗时、重复 | 缓存 UUID,提高连接效率 |
配对 | 配对状态混乱、无法判断安全能力 | 缓存密钥与能力,便于复用 |
连接 | 无法快速判断设备是否支持某 profile | 统一缓存 profile 能力与状态 |
因此,AOSP 中通过 DeviceProperties
实现了一个 以 MAC 地址为主键的远程设备状态缓存中心,并与 StorageModule
联动实现持久化。
2. DeviceProperties 模块设计概述
1. 核心职责
功能 | 说明 |
---|---|
缓存设备属性 | 设备名称、类型、Class of Device、UUID、RSSI、Bond 状态、安全能力、Link Key 等 |
提供统一读写接口 | 供 btif 层、profile 层、JNI 层查询与设置设备状态 |
与 StorageModule 协作持久化 | 关键属性写入 bt_config.conf 配置文件,保证系统重启后信息不丢失 |
2. 数据存储结构
每个远程设备(用 address 唯一标识)对应一个 DeviceProperties
实例,核心字段如:
android/app/src/com/android/bluetooth/btservice/RemoteDevices.javaclass DeviceProperties {private String mName;private byte[] mAddress;private String mIdentityAddress;private boolean mIsConsolidated = false;private int mBluetoothClass = BluetoothClass.Device.Major.UNCATEGORIZED;private int mBredrConnectionHandle = BluetoothDevice.ERROR;private int mLeConnectionHandle = BluetoothDevice.ERROR;private short mRssi;private String mAlias;private BluetoothDevice mDevice;private boolean mIsBondingInitiatedLocally;private int mBatteryLevelFromHfp = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;private int mBatteryLevelFromBatteryService = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;private boolean mIsCoordinatedSetMember;private int mAshaCapability;private int mAshaTruncatedHiSyncId;private String mModelName;@VisibleForTesting int mBondState;@VisibleForTesting int mDeviceType;@VisibleForTesting ParcelUuid[] mUuids;private BluetoothSinkAudioPolicy mAudioPolicy;...
}
1. 创建 DeviceProperties 对象
创建 DeviceProperties 的地方:
android/app/src/com/android/bluetooth/btservice/RemoteDevices.javaDeviceProperties addDeviceProperties(byte[] address) {synchronized (mDevices) {DeviceProperties prop = new DeviceProperties(); // 1. 创建 DeviceProperties 对象prop.setDevice(sAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)));prop.setAddress(address);String key = Utils.getAddressStringFromByte(address); // 2.key 是 mac 地址DeviceProperties pv = mDevices.put(key, prop); // 保存在 RemoteDevices.mDevices 中...return prop;}}
java 层在如下场景中,会调用 addDeviceProperties 创建一个 DeviceProperties 对象:
- AdapterProperties.adapterPropertyChangedCallback:BT_PROPERTY_ADAPTER_BONDED_DEVICES
- 在打开蓝牙时, AdapterProperties 会收到 BT_PROPERTY_ADAPTER_BONDED_DEVICES 事件;此时会将之前 已经配对的设备 封装为一个个 DeviceProperties 对象。
- BondStateMachine.sspRequestCallback
- 设备配对时支持 SSP 模式,进行确认、比较、输入密钥等操作时触发
- BondStateMachine.pinRequestCallback
- 当连接传统蓝牙设备(BR/EDR)时需要输入 PIN 码进行配对时触发
- RemoteDevices.devicePropertyChangedCallback
- 当 设备 属性发生变化时, 从 native -> java 上报设备信息时,如果找不到对应设备的 Property 将新建一个。
2. 管理那些属性:
在 【android bluetooth 框架分析 04】【bt-framework 层详解 6】【Properties介绍】 中有详细介绍。
枚举常量 | 说明 | 使用范围 | 数据类型 | 访问权限 |
---|---|---|---|---|
🔁 适用于 Adapter 和 Remote Device | ||||
BT_PROPERTY_BDNAME | 设备名称 | Adapter: 读/写Remote Device: 只读 | bt_bdname_t | GET / SET(Adapter)GET(Remote) |
BT_PROPERTY_BDADDR | 设备地址 | Adapter & Remote Device | RawAddress | GET |
BT_PROPERTY_UUIDS | 支持的服务 UUID 列表 | Remote Device | bluetooth::Uuid[] | GET |
BT_PROPERTY_CLASS_OF_DEVICE | 类别码 | Remote Device | uint32_t | GET |
BT_PROPERTY_TYPE_OF_DEVICE | 设备类型(BR/EDR/LE) | Remote Device | bt_device_type_t | GET |
BT_PROPERTY_SERVICE_RECORD | 服务记录 | Remote Device | bt_service_record_t | GET |
枚举常量 | 说明 | 使用范围 | 数据类型 | 访问权限 |
---|---|---|---|---|
📡 仅适用于 Remote Device(远程设备) | ||||
BT_PROPERTY_REMOTE_FRIENDLY_NAME | 远程设备名称(用户设定) | Remote Device | bt_bdname_t | GET / SET |
BT_PROPERTY_REMOTE_RSSI | 远程设备 RSSI | Remote Device | int8_t | GET |
BT_PROPERTY_REMOTE_VERSION_INFO | 远程设备协议版本信息 | Remote Device | bt_remote_version_t | GET / SET |
BT_PROPERTY_REMOTE_IS_COORDINATED_SET_MEMBER | 是否是协同设备成员 | Remote Device | bool | GET |
BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP | 属性刷新时间戳 | Remote Device | int64_t (或自定义) | GET |
3. native -> java callback
在 搜索、 配对 、 sdp 的过程中,native 在不同阶段都会触发 回调 到 java 层,来更新 DeviceProperties .
协议栈会通过 下面两个函数来, 层层 上报 属性到 java 层:
- invoke_device_found_cb
- invoke_remote_device_properties_cb
接下来我们梳理一下 他们的调用逻辑。
1. invoke_device_found_cb & invoke_remote_device_properties_cb
// system/btif/src/bluetooth.ccvoid invoke_device_found_cb(int num_properties, bt_property_t* properties) {do_in_jni_thread(FROM_HERE,base::BindOnce([](int num_properties, bt_property_t* properties) {HAL_CBACK(bt_hal_cbacks, device_found_cb,num_properties, properties); // 调用 jni 函数if (properties) {osi_free(properties);}},num_properties,property_deep_copy_array(num_properties, properties)));
}void invoke_remote_device_properties_cb(bt_status_t status, RawAddress bd_addr,int num_properties,bt_property_t* properties) {do_in_jni_thread(FROM_HERE, base::BindOnce([](bt_status_t status, RawAddress bd_addr,int num_properties, bt_property_t* properties) {HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb,status, &bd_addr, num_properties, properties); // 调用 jni 函数if (properties) {osi_free(properties);}},status, bd_addr, num_properties,property_deep_copy_array(num_properties, properties)));
}
// android/app/jni/com_android_bluetooth_btservice_AdapterService.cpptypedef struct {...remote_device_properties_callback remote_device_properties_cb;device_found_callback device_found_cb;...
} bt_callbacks_t;static bt_callbacks_t sBluetoothCallbacks = {sizeof(sBluetoothCallbacks),adapter_state_change_callback,adapter_properties_callback,remote_device_properties_callback,device_found_callback,...};
- HAL_CBACK(bt_hal_cbacks, device_found_cb, num_properties, properties);
- 调用的就是 om_android_bluetooth_btservice_AdapterService.cpp::device_found_callback
- HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, status, &bd_addr, num_properties, properties);
- 调用的就是 om_android_bluetooth_btservice_AdapterService.cpp::remote_device_properties_callback
// android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static void device_found_callback(int num_properties,bt_property_t* properties) {CallbackEnv sCallbackEnv(__func__);if (!sCallbackEnv.valid()) return;ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), NULL);int addr_index;for (int i = 0; i < num_properties; i++) {if (properties[i].type == BT_PROPERTY_BDADDR) {addr.reset(sCallbackEnv->NewByteArray(properties[i].len));if (!addr.get()) {ALOGE("Address is NULL (unable to allocate) in %s", __func__);return;}sCallbackEnv->SetByteArrayRegion(addr.get(), 0, properties[i].len,(jbyte*)properties[i].val);addr_index = i;}}if (!addr.get()) {ALOGE("Address is NULL in %s", __func__);return;}ALOGV("%s: Properties: %d, Address: %s", __func__, num_properties,(const char*)properties[addr_index].val);remote_device_properties_callback(BT_STATUS_SUCCESS,(RawAddress*)properties[addr_index].val,num_properties, properties); // 1. sCallbackEnv->CallVoidMethod(sJniCallbacksObj, method_deviceFoundCallback,addr.get()); // 回调到 java 层
}
// android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static void remote_device_properties_callback(bt_status_t status,RawAddress* bd_addr,int num_properties,bt_property_t* properties) {CallbackEnv sCallbackEnv(__func__);if (!sCallbackEnv.valid()) return;ALOGV("%s: Status is: %d, Properties: %d", __func__, status, num_properties);if (status != BT_STATUS_SUCCESS) {ALOGE("%s: Status %d is incorrect", __func__, status);return;}ScopedLocalRef<jbyteArray> val(sCallbackEnv.get(),(jbyteArray)sCallbackEnv->NewByteArray(num_properties));if (!val.get()) {ALOGE("%s: Error allocating byteArray", __func__);return;}ScopedLocalRef<jclass> mclass(sCallbackEnv.get(),sCallbackEnv->GetObjectClass(val.get()));/* Initialize the jobjectArray and jintArray here itself and send theinitialized array pointers alone to get_properties */ScopedLocalRef<jobjectArray> props(sCallbackEnv.get(),sCallbackEnv->NewObjectArray(num_properties, mclass.get(), NULL));if (!props.get()) {ALOGE("%s: Error allocating object Array for properties", __func__);return;}ScopedLocalRef<jintArray> types(sCallbackEnv.get(), (jintArray)sCallbackEnv->NewIntArray(num_properties));if (!types.get()) {ALOGE("%s: Error allocating int Array for values", __func__);return;}ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));if (!addr.get()) {ALOGE("Error while allocation byte array in %s", __func__);return;}sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),(jbyte*)bd_addr);jintArray typesPtr = types.get();jobjectArray propsPtr = props.get();if (get_properties(num_properties, properties, &typesPtr, &propsPtr) < 0) {return;}sCallbackEnv->CallVoidMethod(sJniCallbacksObj,method_devicePropertyChangedCallback, addr.get(),types.get(), props.get()); // 回调到 java 层
}
// android/app/jni/com_android_bluetooth_btservice_AdapterService.cppmethod_devicePropertyChangedCallback = env->GetMethodID(jniCallbackClass, "devicePropertyChangedCallback", "([B[I[[B)V");method_deviceFoundCallback =env->GetMethodID(jniCallbackClass, "deviceFoundCallback", "([B)V");
// android/app/src/com/android/bluetooth/btservice/JniCallbacks.javavoid devicePropertyChangedCallback(byte[] address, int[] types, byte[][] val) {mRemoteDevices.devicePropertyChangedCallback(address, types, val);}void deviceFoundCallback(byte[] address) {mRemoteDevices.deviceFoundCallback(address);}
2. RemoteDevices.devicePropertyChangedCallback
是 Java 层对 native 层 method_devicePropertyChangedCallback
的响应回调,用于更新本地记录的远程蓝牙设备属性。
// android/app/src/com/android/bluetooth/btservice/RemoteDevices.java/*address:远程设备的 MAC 地址(byte[] 格式)types:属性类型数组(int 值,参照 AbstractionLayer.BT_PROPERTY_* 常量定义)values:每个属性对应的值(数组形式,一一对应)*/void devicePropertyChangedCallback(byte[] address, int[] types, byte[][] values) {Intent intent;byte[] val;int type;BluetoothDevice bdDevice = getDevice(address);DeviceProperties deviceProperties;/*如果是第一次收到该地址设备的属性变更,说明是新设备,需添加。DeviceProperties 是系统对一个远程设备的本地属性封装类。*/if (bdDevice == null) {debugLog("Added new device property");deviceProperties = addDeviceProperties(address); // 创建新的 DevicePropertiesbdDevice = getDevice(address);} else {deviceProperties = getDeviceProperties(bdDevice); // 再次获取 BluetoothDevice 实例}// 无属性则退出。if (types.length <= 0) {errorLog("No properties to update");return;}// 遍历所有变更的属性for (int j = 0; j < types.length; j++) {type = types[j];val = values[j];if (val.length > 0) {synchronized (mObject) { // 同步锁:避免并发问题infoLog("Property type: " + type);// 根据属性类型更新具体字段switch (type) {case AbstractionLayer.BT_PROPERTY_BDNAME: // 设备名称final String newName = new String(val);if (newName.equals(deviceProperties.getName())) {infoLog("Skip name update for " + bdDevice);break;}deviceProperties.setName(newName);// 广播设备名称改变事件intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED);intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice);intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProperties.getName());intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT,Utils.getTempAllowlistBroadcastOptions());infoLog("Remote Device name is: " + deviceProperties.getName());break;case AbstractionLayer.BT_PROPERTY_REMOTE_FRIENDLY_NAME: // 用户自定义名称(Alias)deviceProperties.setAlias(bdDevice, new String(val));infoLog("Remote device alias is: " + deviceProperties.getAlias());break;case AbstractionLayer.BT_PROPERTY_BDADDR: // 设备地址deviceProperties.setAddress(val);infoLog("Remote Address is:" + Utils.getAddressStringFromByte(val));break;// 设备类型标识(例如:手机、耳机)case AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE: final int newClass = Utils.byteArrayToInt(val);if (newClass == deviceProperties.getBluetoothClass()) {infoLog("Skip class update for " + bdDevice);break;}deviceProperties.setBluetoothClass(newClass);// 广播 class 改变事件intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED);intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice);intent.putExtra(BluetoothDevice.EXTRA_CLASS,new BluetoothClass(deviceProperties.getBluetoothClass()));intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT,Utils.getTempAllowlistBroadcastOptions());infoLog("Remote class is:" + newClass);break;case AbstractionLayer.BT_PROPERTY_UUIDS: // 支持的 Profile UUID , SDP 发现结束后会通过此字段更新支持的 Profile,如 A2DP、HFP。int numUuids = val.length / AbstractionLayer.BT_UUID_SIZE;final ParcelUuid[] newUuids = Utils.byteArrayToUuid(val);//ParcelUuid[] uuids = updateUuids(deviceProperties.mUuids, newUuids);if (areUuidsEqual(newUuids, deviceProperties.mUuids)) {infoLog( "Skip uuids update for " + bdDevice.getAddress());break;}deviceProperties.mUuids = newUuids;if (sAdapterService.getState() == BluetoothAdapter.STATE_ON) {sAdapterService.deviceUuidUpdated(bdDevice);sendUuidIntent(bdDevice, deviceProperties);} else if (sAdapterService.getState()== BluetoothAdapter.STATE_BLE_ON) {sAdapterService.deviceUuidUpdated(bdDevice);}break;case AbstractionLayer.BT_PROPERTY_TYPE_OF_DEVICE:// 设备连接类型(BR/EDR, LE, Dual)if (deviceProperties.isConsolidated()) {break;}// The device type from hal layer, defined in bluetooth.h,// matches the type defined in BluetoothDevice.javadeviceProperties.setDeviceType(Utils.byteArrayToInt(val));break;case AbstractionLayer.BT_PROPERTY_REMOTE_RSSI: // 信号强度// RSSI from hal is in one bytedeviceProperties.setRssi(val[0]);break;case AbstractionLayer.BT_PROPERTY_REMOTE_IS_COORDINATED_SET_MEMBER:// 是否属于 Coordinated SetdeviceProperties.setIsCoordinatedSetMember(val[0] != 0);break;}}}}}
功能点 | 说明 |
---|---|
📥 入口 | native 层通知 Java 层远程设备属性有更新(如名称、class、UUID、RSSI) |
🗃️ 存储 | 更新本地 Java 层缓存(DeviceProperties 对象) |
📢 广播 | 对关键属性(名称、class、UUID)变化发送系统广播 |
💾 保存 | 最终可能通过 StorageModule 写入 bt_config.conf (如 UUID、Alias、Class) |
🧩 用途 | 支持 UI 展示、连接判断、profile 支持判断等 |
3. RemoteDevices.deviceFoundCallback
此函数是在设备被扫描到时由 native 层调用 Java 层,属于蓝牙设备发现流程的重要组成部分。
当蓝牙发现流程(Inquiry 或 LE Scan)中发现了一个新设备或再次发现旧设备时,会触发此回调。它的职责是:
-
获取设备信息
-
根据系统配置和策略决定是否广播设备发现
-
通过
ACTION_FOUND
广播通知系统和应用
// android/app/src/com/android/bluetooth/btservice/RemoteDevices.java// Native 层通过 JNI 调用 Java 层,传入远程设备的地址(6 字节 MAC 地址)。void deviceFoundCallback(byte[] address) {// The device properties are already registered - we can send the intent// now 根据 MAC 地址获取或创建 BluetoothDevice 对象。BluetoothDevice device = getDevice(address);infoLog("deviceFoundCallback: Remote Address is:" + device);// 获取设备的本地属性封装对象(DeviceProperties),包含设备名称、class、RSSI 等。DeviceProperties deviceProp = getDeviceProperties(device);if (deviceProp == null) {// 如果属性为空(很罕见,可能是同步未完成),直接返回。errorLog("Device Properties is null for Device:" + device);return;}// 检查是否开启“限制无名称设备广播”策略boolean restrict_device_found =SystemProperties.getBoolean("bluetooth.restrict_discovered_device.enabled", false);if (restrict_device_found && (deviceProp.mName == null || deviceProp.mName.isEmpty())) {// 读取系统属性,如果为 true,表示系统不希望广播没有名字的设备(可用于节能或隐私控制)// 如果设备没有名字,并且限制策略开启,则不广播此设备。debugLog("Device name is null or empty: " + device);return;}/*应用层级过滤(如阻止某些设备类型)filterDevice() 是系统或厂商定制的设备过滤逻辑(如过滤黑名单、特殊厂商设备等)。若返回 true,跳过此设备广播。*/if (filterDevice(device)) {warnLog("Not broadcast Device: " + device);return;}infoLog("device:" + device + " adapterIndex=" + device.getAdapterIndex());// 创建用于通知发现设备的广播事件。Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); // 附加设备对象本身。intent.putExtra(BluetoothDevice.EXTRA_CLASS,new BluetoothClass(deviceProp.getBluetoothClass())); // 附加设备类型(如手机、耳机、电脑等)intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.getRssi()); // 附加设备的信号强度。intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.getName()); // 附加设备名称。intent.putExtra(BluetoothDevice.EXTRA_IS_COORDINATED_SET_MEMBER,deviceProp.isCoordinatedSetMember()); // 附加是否属于 Coordinated Set(蓝牙 5.2+ 中用于群组播放,如 TWS 左右耳同时控制)。/*广播发出发出带权限限制的广播,只有持有 BLUETOOTH_SCAN 权限的应用可以接收该广播。使用 sendBroadcastMultiplePermissions() 是对 sendBroadcast() 的扩展,支持多权限、支持临时广播策略(如广播延迟/前台优先级等)。*/sAdapterService.sendBroadcastMultiplePermissions(intent,new String[] { BLUETOOTH_SCAN },Utils.getTempBroadcastOptions());}
阶段 | 描述 |
---|---|
1️⃣ 获取 | 根据 address 获取 BluetoothDevice 和 DeviceProperties |
2️⃣ 检查 | 是否开启了过滤策略(无名称设备、特定设备过滤) |
3️⃣ 构建 | 创建广播 intent 并附加设备属性 |
4️⃣ 广播 | 向系统发送 ACTION_FOUND 广播,仅供授权应用接收 |
应用场景:
- 当用户在设置界面打开蓝牙并点击“扫描设备”时,后台会多次触发 deviceFoundCallback()。
- App 中注册了 ACTION_FOUND 广播接收器后,可以接收到附近设备并展示在列表上。
4. 小结
协议栈 native 侧,会触发上面的两路 回调, 但是他们 所代表 的含义却是不同的:
- invoke_device_found_cb[native] -> RemoteDevices.deviceFoundCallback[java]
- invoke_remote_device_properties_cb[native] -> RemoteDevices.devicePropertyChangedCallback[java]
devicePropertyChangedCallback
和 deviceFoundCallback
是 AOSP 蓝牙框架中两个核心的回调函数,虽然它们都与设备属性和发现有关,但它们在触发时机、作用、广播内容、应用场景等方面都有明显差异。
下面从多个维度对 相同点与不同点 进行详细对比:
1.相同点
维度 | 描述 |
---|---|
🔧 来源 | 都是由 native 层(通过 JNI)调用 Java 层的回调函数 |
📡 与设备相关 | 都涉及对某个 BluetoothDevice 设备的处理 |
🧠 依赖 DeviceProperties | 都通过 getDeviceProperties(device) 获取设备缓存属性 |
🔒 权限控制 | 广播时都依赖蓝牙相关权限(如 BLUETOOTH_SCAN ) |
📲 可导致广播 | 都有可能向上层发送 Android 广播(如 ACTION_FOUND , ACTION_NAME_CHANGED , ACTION_UUID , 等) |
🧪 开发调试中常出现 | 都会在使用蓝牙调试(如配对、扫描)过程中频繁触发 |
🧩 与应用层交互 | 都可能引发第三方 app 的回调(通过广播接收器) |
2.不同点
比较维度 | deviceFoundCallback | devicePropertyChangedCallback |
---|---|---|
💥 触发时机 | 当蓝牙扫描发现设备时调用(第一次或再次发现) | 当远程设备的属性发生变化时调用(如名称、RSSI、UUID 等) |
🔁 调用频率 | 在一次扫描过程中可能多次触发(每个设备发现一次) | 属性每变化一次触发一次,可能频繁(如 RSSI 不断变化) |
📩 广播行为 | 广播 BluetoothDevice.ACTION_FOUND (设备被发现) | 根据属性类型广播不同事件,如:
|
🔍 目的 | 表示“新设备”被发现,通知系统和应用显示 | 表示“已知设备的属性”发生变化,更新状态或 UI |
🧬 广播携带信息 | BluetoothDevice 、设备类型、名称、RSSI、是否为群组成员 | 取决于变化的属性(可能是 UUID、名称、RSSI) |
🧰 过滤策略参与 | 参与“是否广播”策略(如名称为空不广播) | 不参与过滤,始终处理属性变化 |
📲 典型场景 | 蓝牙设置页中设备扫描列表展示 | 已配对设备列表中设备名称、信号变化,或配对时获取 UUID |
💡 是否依赖扫描流程 | 是,仅在蓝牙扫描流程中调用 | 否,也可能在连接、配对、服务发现后调用 |
3.实际生活中的类比
情况 | deviceFoundCallback | devicePropertyChangedCallback |
---|---|---|
你在商场里发现一个新品牌店铺 | 店铺出现在你面前的那一刻 —— “发现设备” | 店铺换了名字、装修风格变了、换老板了、上了新的商品 —— “属性改变” |
手机蓝牙设置中扫描时发现设备列表刷新 | 每个设备出现一次触发一次 | 某设备名称更新或信号强度变化,刷新其展示项 |
4.应用开发建议
目标 | 使用哪个回调 |
---|---|
想监听设备是否被发现(用于展示设备列表) | BluetoothDevice.ACTION_FOUND 广播(源自 deviceFoundCallback ) |
想监听某设备名称是否变更(如设备重命名) | BluetoothDevice.ACTION_NAME_CHANGED (源自 devicePropertyChangedCallback ) |
想获取设备的 UUID(服务)更新 | BluetoothDevice.ACTION_UUID (源自 devicePropertyChangedCallback ) |
想实时显示设备信号强度(如附近蓝牙设备距离) | BluetoothDevice.EXTRA_RSSI (由 devicePropertyChangedCallback 中 RSSI 更新引发) |
3. 系统接口
关于 DevicePropertyNative 有如下几个接口:
- android/app/src/com/android/bluetooth/btservice/AdapterService.java
/*package*/native boolean setDevicePropertyNative(byte[] address, int type, byte[] val);/*package*/native boolean getDevicePropertyNative(byte[] address, int type);
1. Get 流程
getDevicePropertyNative
android/app/src/com/android/bluetooth/btservice/AdapterService.java/*package*/native boolean getDevicePropertyNative(byte[] address, int type);
// android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static jboolean getDevicePropertyNative(JNIEnv* env, jobject obj,jbyteArray address, jint type) {ALOGV("%s", __func__);if (!sBluetoothInterface) return JNI_FALSE;jbyte* addr = env->GetByteArrayElements(address, NULL);if (addr == NULL) {jniThrowIOException(env, EINVAL);return JNI_FALSE;}int ret = sBluetoothInterface->get_remote_device_property((RawAddress*)addr, (bt_property_type_t)type);env->ReleaseByteArrayElements(address, addr, 0);return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
system/btif/src/bluetooth.ccint get_remote_device_property(RawAddress* remote_addr,bt_property_type_t type) {if (!btif_is_enabled()) return BT_STATUS_NOT_READY;do_in_main_thread(FROM_HERE, base::BindOnce(btif_get_remote_device_property,*remote_addr, type));return BT_STATUS_SUCCESS;
}
system/btif/src/btif_core.cc/********************************************************************************* Function btif_get_remote_device_property** Description Fetches the remote device property from the NVRAM*******************************************************************************/
void btif_get_remote_device_property(RawAddress remote_addr,bt_property_type_t type) {char buf[1024];bt_property_t prop;prop.type = type;prop.val = (void*)buf;prop.len = sizeof(buf);bt_status_t status =btif_storage_get_remote_device_property(&remote_addr, &prop);invoke_remote_device_properties_cb(status, remote_addr, 1, &prop); // 1.
}
invoke_remote_device_properties_cb
// system/btif/src/btif_core.cc
void btif_remote_properties_evt(bt_status_t status, RawAddress* remote_addr,uint32_t num_props, bt_property_t* p_props) {invoke_remote_device_properties_cb(status, *remote_addr, num_props, p_props);
}
2. 写配置
btif_storage_add_remote_device
system/btif/src/btif_storage.ccbt_status_t btif_storage_add_remote_device(const RawAddress* remote_bd_addr,uint32_t num_properties,bt_property_t* properties) {uint32_t i = 0;/* TODO: If writing a property, fails do we go back undo the earlier* written properties? */for (i = 0; i < num_properties; i++) {/* Ignore the RSSI as this is not stored in DB */if (properties[i].type == BT_PROPERTY_REMOTE_RSSI) continue;/* address for remote device needs special handling as we also store* timestamp */if (properties[i].type == BT_PROPERTY_BDADDR) {bt_property_t addr_prop;memcpy(&addr_prop, &properties[i], sizeof(bt_property_t));addr_prop.type = (bt_property_type_t)BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP;btif_storage_set_remote_device_property(remote_bd_addr, &addr_prop); // 1. } else {btif_storage_set_remote_device_property(remote_bd_addr, &properties[i]); // 2. }}return BT_STATUS_SUCCESS;
}
btif_storage_set_remote_device_property
// system/btif/src/btif_storage.cc
bt_status_t btif_storage_set_remote_device_property(const RawAddress* remote_bd_addr, bt_property_t* property) {return prop2cfg(remote_bd_addr, property) ? BT_STATUS_SUCCESS: BT_STATUS_FAIL;
}
prop2cfg
static int prop2cfg(const RawAddress* remote_bd_addr, bt_property_t* prop) {std::string bdstr;if (remote_bd_addr) {bdstr = remote_bd_addr->ToString();}char value[1024];if (prop->len <= 0 || prop->len > (int)sizeof(value) - 1) {LOG_WARN("Unable to save property to configuration file type:%d, "" len:%d is invalid",prop->type, prop->len);return false;}switch (prop->type) {case BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP:btif_config_set_int(bdstr, BTIF_STORAGE_PATH_REMOTE_DEVTIME,(int)time(NULL));break;case BT_PROPERTY_BDNAME: {int name_length = prop->len > BTM_MAX_LOC_BD_NAME_LEN? BTM_MAX_LOC_BD_NAME_LEN: prop->len;strncpy(value, (char*)prop->val, name_length);value[name_length] = '\0';if (remote_bd_addr) {btif_config_set_str(bdstr, BTIF_STORAGE_PATH_REMOTE_NAME, value);} else {btif_config_set_str("Adapter", BTIF_STORAGE_KEY_ADAPTER_NAME, value);btif_config_flush();}break;}case BT_PROPERTY_REMOTE_FRIENDLY_NAME:strncpy(value, (char*)prop->val, prop->len);value[prop->len] = '\0';btif_config_set_str(bdstr, BTIF_STORAGE_PATH_REMOTE_ALIASE, value);break;case BT_PROPERTY_ADAPTER_SCAN_MODE:btif_config_set_int("Adapter", BTIF_STORAGE_KEY_ADAPTER_SCANMODE,*(int*)prop->val);break;case BT_PROPERTY_LOCAL_IO_CAPS:btif_config_set_int("Adapter", BTIF_STORAGE_KEY_LOCAL_IO_CAPS,*(int*)prop->val);break;case BT_PROPERTY_LOCAL_IO_CAPS_BLE:btif_config_set_int("Adapter", BTIF_STORAGE_KEY_LOCAL_IO_CAPS_BLE,*(int*)prop->val);break;case BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT:btif_config_set_int("Adapter", BTIF_STORAGE_KEY_ADAPTER_DISC_TIMEOUT,*(int*)prop->val);break;case BT_PROPERTY_CLASS_OF_DEVICE:btif_config_set_int(bdstr, BTIF_STORAGE_PATH_REMOTE_DEVCLASS,*(int*)prop->val);break;case BT_PROPERTY_TYPE_OF_DEVICE:btif_config_set_int(bdstr, BTIF_STORAGE_PATH_REMOTE_DEVTYPE,*(int*)prop->val);break;case BT_PROPERTY_UUIDS: {std::string val;size_t cnt = (prop->len) / sizeof(Uuid);for (size_t i = 0; i < cnt; i++) {val += (reinterpret_cast<Uuid*>(prop->val) + i)->ToString() + " ";}btif_config_set_str(bdstr, BTIF_STORAGE_PATH_REMOTE_SERVICE, val);break;}case BT_PROPERTY_REMOTE_VERSION_INFO: {bt_remote_version_t* info = (bt_remote_version_t*)prop->val;if (!info) return false;btif_config_set_int(bdstr, BT_CONFIG_KEY_REMOTE_VER_MFCT,info->manufacturer);btif_config_set_int(bdstr, BT_CONFIG_KEY_REMOTE_VER_VER, info->version);btif_config_set_int(bdstr, BT_CONFIG_KEY_REMOTE_VER_SUBVER,info->sub_ver);} break;default:BTIF_TRACE_ERROR("Unknown prop type:%d", prop->type);return false;}/* No need to look for bonded device with address of NULL */if (remote_bd_addr &&btif_in_fetch_bonded_device(bdstr) == BT_STATUS_SUCCESS) {/* save changes if the device was bonded */btif_config_flush();}return true;
}
1. btif_config_set_str 和 btif_config_set_int
// system/btif/src/btif_config.cc
bool btif_config_set_int(const std::string& section, const std::string& key,int value) {CHECK(bluetooth::shim::is_gd_stack_started_up());return bluetooth::shim::BtifConfigInterface::SetInt(section, key, value);
}// system/main/shim/config.cc
bool BtifConfigInterface::SetInt(const std::string& section,const std::string& property, int value) {ConfigCacheHelper::FromConfigCache(*GetStorage()->GetConfigCache()).SetInt(section, property, value);return true;
}// system/gd/storage/config_cache_helper.cc
void ConfigCacheHelper::SetInt(const std::string& section, const std::string& property, int value) {config_cache_.SetProperty(section, property, std::to_string(value));
}
2. ConfigCache::SetProperty
功能:
- 设置一个配置项(如蓝牙设备属性或通用配置),并根据是否为设备属性决定是否进入持久化配置或临时配置缓存中。
// system/gd/storage/config_cache.ccvoid ConfigCache::SetProperty(std::string section, std::string property, std::string value) {/*使用递归互斥锁保护对 information_sections_、persistent_devices_、temporary_devices_ 等共享数据结构的并发访问。防止多线程同时读写 config。*/std::lock_guard<std::recursive_mutex> lock(mutex_);// 移除传入字符串中可能存在的 \n 或 \r,防止注入或破坏配置格式。TrimAfterNewLine(section);TrimAfterNewLine(property);TrimAfterNewLine(value);// section 和 property 名不能为空,否则断言失败(开发期调试用)ASSERT_LOG(!section.empty(), "Empty section name not allowed");ASSERT_LOG(!property.empty(), "Empty property name not allowed");/* 判断是否为“设备节一般如 [Adapter], [Metrics], [Global] 属于非设备配置节;而类似 [Device_XX:XX:XX:XX:XX:XX] 才是设备配置节。*/if (!IsDeviceSection(section)) {/*存入 information_sections_(非设备类配置)如果该节尚不存在,则创建;将 property -> value 插入;通知配置已更改。适用于如 [Adapter] 节下的 Name、ScanMode 等普通配置项。*/auto section_iter = information_sections_.find(section);if (section_iter == information_sections_.end()) {section_iter = information_sections_.try_emplace_back(section, common::ListMap<std::string, std::string>{}).first;}section_iter->second.insert_or_assign(property, std::move(value));PersistentConfigChangedCallback();return;}/*如果是设备配置节(Device_...),检查是否可持久化保存如果该设备还不在 persistent_devices_ 中,且当前 property 是可持久化的(如 LinkKey),尝试从临时设备中迁移。只有配对时,才有可能将 设备 从临时设备列表中, 移到 可持久化列表里。*/auto section_iter = persistent_devices_.find(section);if (section_iter == persistent_devices_.end() && IsPersistentProperty(property)) {// move paired devices or create new paired device when a link key is set/*从 temporary_devices_ 迁移或新建一项若该设备的属性存在于临时设备列表中,则将其“转正”迁入 persistent;否则新建。场景举例:连接配对时第一次保存 link key,从临时状态迁移为持久配对状态。*/auto section_properties = temporary_devices_.extract(section);if (section_properties) {section_iter = persistent_devices_.try_emplace_back(section, std::move(section_properties->second)).first;} else {section_iter = persistent_devices_.try_emplace_back(section, common::ListMap<std::string, std::string>{}).first;}}/*安全模式下加密敏感属性值如果开启了安全模式(如 CC Mode)并且属性为敏感字段(如 LinkKey、LE_KEY_PENC 等):通过 KeyStore 接口尝试加密存储;若成功,则设置为标记字符串 value = "$encrypted" 表示已加密。
*/if (section_iter != persistent_devices_.end()) {bool is_encrypted = value == kEncryptedStr;if ((!value.empty()) && os::ParameterProvider::GetBtKeystoreInterface() != nullptr &&os::ParameterProvider::IsCommonCriteriaMode() && InEncryptKeyNameList(property) && !is_encrypted) {if (os::ParameterProvider::GetBtKeystoreInterface()->set_encrypt_key_or_remove_key(section + "-" + property, value)) {value = kEncryptedStr;}}/*插入持久化设备属性 + 通知更改将处理后的值插入设备节中;通知持久化更改(通常触发异步写入 config 文件)。
*/section_iter->second.insert_or_assign(property, std::move(value));PersistentConfigChangedCallback();return;}/*如果该设备节仍不存在,写入 temporary_devices_表示当前属性不需要持久化;保存到 temporary_devices_ 中(通常用于会话属性、未配对设备等 );扫描到的设备都一般存储在这里*/section_iter = temporary_devices_.find(section);if (section_iter == temporary_devices_.end()) {auto triple = temporary_devices_.try_emplace(section, common::ListMap<std::string, std::string>{});section_iter = std::get<0>(triple);}section_iter->second.insert_or_assign(property, std::move(value));
}
在搜素时,没有配对的设备 都将保存在 临时 设备列表里面。
只有 在配对时,收到 设备的 LinkKey 时, 才会从临时设备 列表,挪到 可持久设备列表里面。
/*如果是设备配置节(Device_...),检查是否可持久化保存如果该设备还不在 persistent_devices_ 中,且当前 property 是可持久化的(如 LinkKey),尝试从临时设备中迁移。只有配对时,才有可能将 设备 从临时设备列表中, 移到 可持久化列表里。*/auto section_iter = persistent_devices_.find(section);if (section_iter == persistent_devices_.end() && IsPersistentProperty(property)) {// move paired devices or create new paired device when a link key is set...}
1. IsPersistentProperty(property)
// system/gd/storage/config_cache.ccbool ConfigCache::IsPersistentProperty(const std::string& property) const {return persistent_property_names_.find(property) != persistent_property_names_.end();
}ConfigCache::ConfigCache(size_t temp_device_capacity, std::unordered_set<std::string_view> persistent_property_names): persistent_property_names_(std::move(persistent_property_names)),information_sections_(),persistent_devices_(),temporary_devices_(temp_device_capacity) {}
- 在 ConfigCache 构造函数里面,会初始化
persistent_property_names_
// system/gd/storage/storage_module.cc
void StorageModule::Start() {...auto config = LegacyConfigFile::FromPath(config_file_path_).Read(temp_devices_capacity_);...
}
// system/gd/storage/legacy_config_file.ccstd::optional<ConfigCache> LegacyConfigFile::Read(size_t temp_devices_capacity) {...ConfigCache cache(temp_devices_capacity, Device::kLinkKeyProperties);...
}
在 StorageModule 模块初始化时, 将 从 /data/misc/bluedroid/bt_config.conf 读取内容, 此时会 创建 ConfigCache 对象,此时 persistent_property_names_ = Device::kLinkKeyProperties
4. 在各阶段的核心作用
1. 搜索阶段(Inquiry / BLE Scan)
1. 作用
-
当 Controller 发现新设备时(classic 或 BLE):
- 没有就创建并初始化
- 有就更新(如 RSSI、名称、设备类型)
-
作用于:
- 避免重复设备出现在 UI(去重)
- 通知 Java 层刷新设备列表
2. 解决的问题
问题 | 如何解决 |
---|---|
同一设备出现多次 | 用地址唯一标识缓存 |
设备名称/类型不一致 | 统一由 DeviceProperties 更新 |
3. BR/EDR 扫描流程鉴赏
1. btif_dm_search_devices_evt
这段代码是 设备发现流程的核心处理逻辑之一,对应于系统层调用 BluetoothAdapter.startDiscovery()
时产生的设备发现过程中的事件处理。
btif_dm_search_devices_evt()
是 BTIF 层处理 BTA 层 tBTA_DM_xxx_EVT
事件的统一入口:
- 当发现远程设备时(设备通过 Inquiry 过程或 BLE 扫描被发现),
event == BTA_DM_INQ_RES_EVT
,该事件就会触发,并进入 case 分支处理。
// system/btif/src/btif_dm.cc
static void btif_dm_search_devices_evt(tBTA_DM_SEARCH_EVT event,tBTA_DM_SEARCH* p_search_data) {BTIF_TRACE_EVENT("%s event=%s", __func__, dump_dm_search_event(event));switch (event) {...case BTA_DM_INQ_RES_EVT: {/* 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];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;// 打印发现设备的地址和类型(BR/EDR、BLE 或 DUMO),有助于调试日志追踪。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(Extended Inquiry Response)中解析远程设备名称,如果没有,就尝试从缓存中找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 */// 提取设备 EIR 中的 UUID(服务列表)if (p_search_data->inq_res.p_eir) {// 分别提取 16-bit 和 128-bit UUID,用于构建远程设备所支持的服务,比如 A2DP Sink、HID、PAN 等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);}{// 组织设备属性结构体 bt_property_tbt_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/*COD 设备类别*/, 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/*设备类型(BR/EDR、BLE、DUMO)*/, 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/*是否为 Coordinated Set(例如耳机双边同步)*/,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/*支持的服务 UUID 列表*/,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);ASSERTC(status == BT_STATUS_SUCCESS,"failed to save remote addr type (inquiry)", status);// 可选过滤(非 connectable 的 BLE 设备忽略), 防止显示无法连接的 BLE 设备,例如 Beacon,这对某些厂商有定制用途。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;}/* Callback to notify upper layer of device */invoke_device_found_cb(num_properties, properties); // 通知上层 Java 层 deviceFoundCallback()}} break;}
}
btif_dm_search_devices_evt()
是 AOSP 中实现设备搜索发现的核心逻辑,负责:
-
从 Inquiry/BLE 扫描结果中提取远程设备信息;
-
尝试从 EIR 中解析远程名称和服务;
-
构建标准的
bt_property_t
属性数组; -
存储发现设备并通知 Java 层应用。
而它最终触发的 deviceFoundCallback()
是应用层感知新设备发现的关键入口。
我们再次 devicePropertyChangedCallback vs. deviceFoundCallback 对比:
比较点 | devicePropertyChangedCallback | deviceFoundCallback |
---|---|---|
触发条件 | 已知设备属性变化(如名称变化、RSSI 变化) | 发现新设备(搜索阶段) |
回调数据 | 变化的 bt_property_t 属性 | 初次发现设备的 bt_property_t 全集 |
使用场景 | 设备连接后属性更新 | 搜索设备时通知发现 |
是否多次触发 | 是(属性有变化就会触发) | 是(发现多个设备时各触发一次) |
上层应用行为 | 更新已有设备界面信息 | 新增列表项,展示设备名称等 |
4. Ble scan 时
当 ble 扫描到设备时, 就会触发调用 BleScannerInterfaceImpl::OnScanResult
// system/main/shim/le_scanning_manager.ccvoid BleScannerInterfaceImpl::OnScanResult(uint16_t event_type, uint8_t address_type, bluetooth::hci::Address address,uint8_t primary_phy, uint8_t secondary_phy, uint8_t advertising_sid,int8_t tx_power, int8_t rssi, uint16_t periodic_advertising_interval,std::vector<uint8_t> advertising_data) {RawAddress raw_address = ToRawAddress(address);tBLE_ADDR_TYPE ble_addr_type = to_ble_addr_type(address_type);if (ble_addr_type != BLE_ADDR_ANONYMOUS) {btm_ble_process_adv_addr(raw_address, &ble_addr_type);}// 这里会 去触发 更新 ble 设备的 propertiesdo_in_jni_thread(FROM_HERE,base::BindOnce(&BleScannerInterfaceImpl::handle_remote_properties,base::Unretained(this), raw_address, ble_addr_type,advertising_data));do_in_jni_thread(FROM_HERE,base::BindOnce(&ScanningCallbacks::OnScanResult,base::Unretained(scanning_callbacks_), event_type,static_cast<uint8_t>(address_type), raw_address,primary_phy, secondary_phy, advertising_sid, tx_power,rssi, periodic_advertising_interval, advertising_data));...
}
// system/main/shim/le_scanning_manager.ccvoid BleScannerInterfaceImpl::handle_remote_properties(RawAddress bd_addr, tBLE_ADDR_TYPE addr_type,std::vector<uint8_t> advertising_data) {if (!bluetooth::shim::is_gd_stack_started_up()) {LOG_WARN("Gd stack is stopped, return");return;}// skip anonymous advertismentif (addr_type == BLE_ADDR_ANONYMOUS) {return;}auto device_type = bluetooth::hci::DeviceType::LE;uint8_t flag_len;const uint8_t* p_flag = AdvertiseDataParser::GetFieldByType(advertising_data, BTM_BLE_AD_TYPE_FLAG, &flag_len);if (p_flag != NULL && flag_len != 0) {if ((BTM_BLE_BREDR_NOT_SPT & *p_flag) == 0) {device_type = bluetooth::hci::DeviceType::DUAL;}}uint8_t remote_name_len;const uint8_t* p_eir_remote_name = AdvertiseDataParser::GetFieldByType(advertising_data, HCI_EIR_COMPLETE_LOCAL_NAME_TYPE, &remote_name_len);if (p_eir_remote_name == NULL) {p_eir_remote_name = AdvertiseDataParser::GetFieldByType(advertising_data, HCI_EIR_SHORTENED_LOCAL_NAME_TYPE, &remote_name_len);}// update device nameif ((addr_type != BLE_ADDR_RANDOM) || (p_eir_remote_name)) {if (!address_cache_.find(bd_addr)) {address_cache_.add(bd_addr);if (p_eir_remote_name) {bt_bdname_t bdname;memcpy(bdname.name, p_eir_remote_name, remote_name_len);if (remote_name_len < BD_NAME_LEN + 1)bdname.name[remote_name_len] = '\0';// 这里很关键, 也就是说 只有 扫描到的 ble 设备 有名字, 才会去更新 properties. 其他例如 ble 的 rssi 等,都不会更新。btif_dm_update_ble_remote_properties(bd_addr, bdname.name, device_type);}}}auto* storage_module = bluetooth::shim::GetStorage();bluetooth::hci::Address address = ToGdAddress(bd_addr);// update device typeauto mutation = storage_module->Modify();bluetooth::storage::Device device =storage_module->GetDeviceByLegacyKey(address);mutation.Add(device.SetDeviceType(device_type));mutation.Commit();// update address typeauto mutation2 = storage_module->Modify();bluetooth::storage::LeDevice le_device = device.Le();mutation2.Add(le_device.SetAddressType((bluetooth::hci::AddressType)addr_type));mutation2.Commit();
}
// system/btif/src/btif_dm.cc
void btif_dm_update_ble_remote_properties(const RawAddress& bd_addr,BD_NAME bd_name,tBT_DEVICE_TYPE dev_type) {btif_update_remote_properties(bd_addr, bd_name, NULL, dev_type);
}
// system/btif/src/btif_dm.ccstatic void btif_update_remote_properties(const RawAddress& bdaddr,BD_NAME bd_name, DEV_CLASS dev_class,tBT_DEVICE_TYPE device_type) {int num_properties = 0;bt_property_t properties[3];bt_status_t status = BT_STATUS_UNHANDLED;uint32_t cod;bt_device_type_t dev_type;memset(properties, 0, sizeof(properties));/* remote name */if (strlen((const char*)bd_name)) {BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties], BT_PROPERTY_BDNAME,strlen((char*)bd_name), bd_name);if (!bluetooth::shim::is_gd_security_enabled()) {status = btif_storage_set_remote_device_property(&bdaddr, &properties[num_properties]);ASSERTC(status == BT_STATUS_SUCCESS, "failed to save remote device name",status);}num_properties++;}...invoke_remote_device_properties_cb(status, bdaddr, num_properties,properties); // 1.
}
ble 在扫描阶段, 只有扫描到 设备的名字时, 才会去 调用 invoke_remote_device_properties_cb 更新 properties.
1.例子一
01-02 14:20:35.237345 2146 2634 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:637 btif_update_remote_properties: cod from storage is also unclassified01-02 14:20:35.237477 2146 2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_in_fetch_bonded_ble_device Found a LE device: ba:03:cc:1c:cd:2201-02 14:20:35.237906 2146 2634 D BtGatt.GattService: onScanResult() - eventType=0x1b, addressType=0, address=BA:03:CC:1C:CD:22, primaryPhy=1, secondaryPhy=0, advertisingSid=0xff, txPower=127, rssi=-73, periodicAdvInt=0x0, originalAddress=00:00:00:00:00:0001-02 14:20:35.238671 2146 2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: operator(): HAL bt_hal_cbacks->remote_device_properties_cb
01-02 14:20:35.238841 2146 2634 D BluetoothRemoteDevices: Added new device property
01-02 14:20:35.239122 2146 2634 I BluetoothRemoteDevices: Property type: 1
01-02 14:20:35.240002 2146 2634 I BluetoothRemoteDevices: Remote Device name is: MXD57_MI
01-02 14:20:35.240042 2146 2634 I BluetoothRemoteDevices: Property type: 4
01-02 14:20:35.240082 2146 2634 I BluetoothRemoteDevices: Skip class update for BA:03:CC:1C:CD:22
01-02 14:20:35.240104 2146 2634 I BluetoothRemoteDevices: Property type: 5
131 2025-01-02 14:20:35.236141 controller host HCI_EVT 60 Rcvd LE Meta (LE Extended Advertising Report)Frame 131: 60 bytes on wire (480 bits), 60 bytes captured (480 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI Event - LE MetaEvent Code: LE Meta (0x3e)Parameter Total Length: 57Sub Event: LE Extended Advertising Report (0x0d)Num Reports: 1Event Type: 0x0013, Connectable, Scannable, Legacy, Data Status: CompletePeer Address Type: Public Device Address (0x00)BD_ADDR: ba:03:cc:1c:cd:22 (ba:03:cc:1c:cd:22)Primary PHY: LE 1M (0x01)Secondary PHY: No packets on the secondary advertising channel (0x00)Advertising SID: 0xff (not available)TX Power: 127 dBm (not available)RSSI: -74 dBmPeriodic Advertising Interval: 0x0000 (no periodic advertising)Direct Address Type: Public Device Address (0x00)Direct BD_ADDR: 00:00:00_00:00:00 (00:00:00:00:00:00)Data Length: 31Advertising DataFlagsManufacturer SpecificDevice Name: MXD57_MI # 广播数据中有设备名字Manufacturer Specific
2.例子二
01-02 14:20:35.311975 2146 2634 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:637 btif_update_remote_properties: cod from storage is also unclassified01-02 14:20:35.312013 2146 2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_in_fetch_bonded_ble_device Found a LE device: c0:53:c1:67:0e:a801-02 14:20:35.312128 2146 2634 D BtGatt.GattService: onScanResult() - eventType=0x1b, addressType=1, address=C0:53:C1:67:0E:A8, primaryPhy=1, secondaryPhy=0, advertisingSid=0xff, txPower=127, rssi=-82, periodicAdvInt=0x0, originalAddress=00:00:00:00:00:0001-02 14:20:35.312390 2146 2634 D BluetoothRemoteDevices: Added new device property
01-02 14:20:35.312479 2146 2634 I BluetoothRemoteDevices: Property type: 1
01-02 14:20:35.313982 2146 2634 I BluetoothRemoteDevices: Remote Device name is: LE_WF-C500
01-02 14:20:35.314013 2146 2634 I BluetoothRemoteDevices: Property type: 4
01-02 14:20:35.314035 2146 2634 I BluetoothRemoteDevices: Skip class update for C0:53:C1:67:0E:A8
01-02 14:20:35.314046 2146 2634 I BluetoothRemoteDevices: Property type: 5
158 2025-01-02 14:20:35.308074 controller host HCI_EVT 41 Rcvd LE Meta (LE Extended Advertising Report)Frame 158: 41 bytes on wire (328 bits), 41 bytes captured (328 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI Event - LE MetaEvent Code: LE Meta (0x3e)Parameter Total Length: 38Sub Event: LE Extended Advertising Report (0x0d)Num Reports: 1Event Type: 0x0013, Connectable, Scannable, Legacy, Data Status: CompletePeer Address Type: Random Device Address (0x01)BD_ADDR: c0:53:c1:67:0e:a8 (c0:53:c1:67:0e:a8)Primary PHY: LE 1M (0x01)Secondary PHY: No packets on the secondary advertising channel (0x00)Advertising SID: 0xff (not available)TX Power: 127 dBm (not available)RSSI: -81 dBmPeriodic Advertising Interval: 0x0000 (no periodic advertising)Direct Address Type: Public Device Address (0x00)Direct BD_ADDR: 00:00:00_00:00:00 (00:00:00:00:00:00)Data Length: 12Advertising DataDevice Name: LE_WF-C500 // 设备名字
3. 例子三
01-02 14:20:41.776270 2146 2634 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:637 btif_update_remote_properties: cod from storage is also unclassified01-02 14:20:41.776344 2146 2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_in_fetch_bonded_ble_device Found a LE device: d4:f0:ea:91:af:c601-02 14:20:41.776511 2146 2634 D BtGatt.GattService: onScanResult() - eventType=0x1b, addressType=0, address=D4:F0:EA:91:AF:C6, primaryPhy=1, secondaryPhy=0, advertisingSid=0xff, txPower=127, rssi=-84, periodicAdvInt=0x0, originalAddress=00:00:00:00:00:0001-02 14:20:41.776644 2146 2634 D BluetoothRemoteDevices: Added new device property
01-02 14:20:41.776740 2146 2634 I BluetoothRemoteDevices: Property type: 1
01-02 14:20:41.777297 2146 2634 I BluetoothRemoteDevices: Remote Device name is: SMI-M14
01-02 14:20:41.777325 2146 2634 I BluetoothRemoteDevices: Property type: 4
01-02 14:20:41.777346 2146 2634 I BluetoothRemoteDevices: Skip class update for D4:F0:EA:91:AF:C6
01-02 14:20:41.777359 2146 2634 I BluetoothRemoteDevices: Property type: 5
735 2025-01-02 14:20:41.775853 controller host HCI_EVT 59 Rcvd LE Meta (LE Extended Advertising Report)Frame 735: 59 bytes on wire (472 bits), 59 bytes captured (472 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI Event - LE MetaEvent Code: LE Meta (0x3e)Parameter Total Length: 56Sub Event: LE Extended Advertising Report (0x0d)Num Reports: 1Event Type: 0x0013, Connectable, Scannable, Legacy, Data Status: CompletePeer Address Type: Public Device Address (0x00)BD_ADDR: BeijingX_91:af:c6 (d4:f0:ea:91:af:c6)Primary PHY: LE 1M (0x01)Secondary PHY: No packets on the secondary advertising channel (0x00)Advertising SID: 0xff (not available)TX Power: 127 dBm (not available)RSSI: -84 dBmPeriodic Advertising Interval: 0x0000 (no periodic advertising)Direct Address Type: Public Device Address (0x00)Direct BD_ADDR: 00:00:00_00:00:00 (00:00:00:00:00:00)Data Length: 30Advertising DataFlagsLength: 2Type: Flags (0x01)0. .... = Reserved: 0x0...0 .... = Simultaneous LE and BR/EDR to Same Device Capable (Host): false (0x0).... 0... = Simultaneous LE and BR/EDR to Same Device Capable (Controller): false (0x0).... .1.. = BR/EDR Not Supported: true (0x1).... ..1. = LE General Discoverable Mode: true (0x1).... ...0 = LE Limited Discoverable Mode: false (0x0)Service Data - 16 bit UUIDLength: 17Type: Service Data - 16 bit UUID (0x16)UUID 16: Xiaomi Inc. (0xfe95)Service Data: b054452d00c6af91eaf0d4080e00Device Name: SMI-M14 // 设备名字Length: 8Type: Device Name (0x09)Device Name: SMI-M14
2. SDP / 服务发现阶段
1.作用
-
在 classic 蓝牙中,通过 SDP 获取远程设备支持的 UUID
-
在 BLE 中,通过 GATT Discover Services 获取 UUID
-
均更新到
DeviceProperties::uuids
字段中
2.解决的问题
问题 | 如何解决 |
---|---|
重复做 SDP,浪费时间 | 已知 UUID 可直接进入连接逻辑 |
Profile 初始化失败 | 使用缓存 UUID 预判断是否支持 A2DP、HFP 等 |
连接效率低 | 提前缓存服务能力,减少阻塞等待时间 |
3.代码流程鉴赏
1. btif_dm_search_services_evt
1. BTA_DM_DISC_RES_EVT
这段代码是 Android AOSP 中蓝牙设备 服务发现结果事件(BTA_DM_DISC_RES_EVT)的处理逻辑,位于 btif_dm_search_services_evt 函数中。它主要完成 SDP 服务发现结果的处理、UUID 合并、A2DP 能力识别、属性上报,以及处理 SDP 失败的情况。
大体流程概览:
- 提取事件数据(设备地址、UUID 等)
- 判断是否是配对过程的设备
- 判断 SDP 是否失败并重试(带次数限制)
- 如果成功则合并 UUID、识别 A2DP 功能
- 特殊处理 LE Audio 设备
- 将 UUID 结果保存、上报到 Java 层
- 处理 SDP 失败时尝试使用 EIR 中的 UUID
- 最终调用回调通知 UUID 属性变化
/********************************************************************************* Function btif_dm_search_services_evt** Description Executes search services event in btif context** Returns void*******************************************************************************/
static void btif_dm_search_services_evt(tBTA_DM_SEARCH_EVT event,tBTA_DM_SEARCH* p_data) {switch (event) {case BTA_DM_DISC_RES_EVT: {// 初始化变量,用于保存服务发现结果、UUID 集合和属性值,a2dp_sink_capable 标记是否支持 A2DP Sink。bt_property_t prop;uint32_t i = 0;bt_status_t ret;std::vector<uint8_t> property_value;std::set<Uuid> uuids;bool a2dp_sink_capable = false;// 获取服务发现设备的地址引用。RawAddress& bd_addr = p_data->disc_res.bd_addr;// 判断当前 SDP 结果是否是为当前正在配对的设备服务。bool results_for_bonding_device =(bd_addr == pairing_cb.bd_addr || bd_addr == pairing_cb.static_bdaddr);// 日志输出 SDP 的返回值和服务字段。LOG_VERBOSE("result=0x%x, services 0x%x", p_data->disc_res.result,p_data->disc_res.services);/*SDP 失败后的重试逻辑(仅限配对设备):*/if (results_for_bonding_device && p_data->disc_res.result != BTA_SUCCESS &&pairing_cb.state == BT_BOND_STATE_BONDED &&pairing_cb.sdp_attempts < BTIF_DM_MAX_SDP_ATTEMPTS_AFTER_PAIRING) { // 如果是配对设备、SDP 失败、当前状态为已配对、重试次数不超过限制:// 第一次不重试,第二次以上继续尝试,最多三次。if (pairing_cb.sdp_attempts) {LOG_WARN("SDP failed after bonding re-attempting for %s",PRIVATE_ADDRESS(bd_addr));pairing_cb.sdp_attempts++;LOG_INFO("sdp_attempts = %d", pairing_cb.sdp_attempts);btif_dm_get_remote_services(bd_addr, BT_TRANSPORT_AUTO);} else {LOG_WARN("SDP triggered by someone failed when bonding");}return;}// 如果是配对设备,则标记经典 SDP 已完成。if (results_for_bonding_device) {LOG_INFO("SDP finished for %s:", PRIVATE_ADDRESS(bd_addr));pairing_cb.sdp_over_classic =btif_dm_pairing_cb_t::ServiceDiscoveryState::FINISHED;}// 设置 UUID 属性类型。prop.type = BT_PROPERTY_UUIDS;prop.len = 0;if ((p_data->disc_res.result == BTA_SUCCESS) &&(p_data->disc_res.num_uuids > 0)) { // 如果 SDP 成功且发现了 UUID:LOG_INFO("New UUIDs for %s:", bd_addr.ToString().c_str());for (i = 0; i < p_data->disc_res.num_uuids; i++) { // 遍历所有 UUID,过滤无效项,并插入到集合中。auto uuid = p_data->disc_res.p_uuid_list + i;if (btif_should_ignore_uuid(*uuid)) {continue;}LOG_INFO("index:%d uuid:%s", i, uuid->ToString().c_str());uuids.insert(*uuid);}// 将已有的 UUID 合并进去,避免覆盖。if (results_for_bonding_device) {btif_merge_existing_uuids(pairing_cb.static_bdaddr, &uuids);btif_merge_existing_uuids(pairing_cb.bd_addr, &uuids);} else {btif_merge_existing_uuids(bd_addr, &uuids);}// 将 UUID 转为 128bit 字节流,并识别 A2DP Sink 能力。for (auto& uuid : uuids) {auto uuid_128bit = uuid.To128BitBE();property_value.insert(property_value.end(), uuid_128bit.begin(),uuid_128bit.end());if (uuid == UUID_A2DP_SINK) {a2dp_sink_capable = true;}}prop.val = (void*)property_value.data();prop.len = Uuid::kNumBytes128 * uuids.size();}// 如果同时支持 LE Audio 且 GATT 服务发现未完成,则推迟向 Java 层报告 UUID,等待 LE GATT 结果bool skip_reporting_wait_for_le = false;/* If we are doing service discovery for device that just bonded, that is* capable of a2dp, and both sides can do LE Audio, and it haven't* finished GATT over LE yet, then wait for LE service discovery to finish* before before passing services to upper layers. */if (results_for_bonding_device &&a2dp_sink_capable && LeAudioClient::IsLeAudioClientRunning() &&pairing_cb.gatt_over_le !=btif_dm_pairing_cb_t::ServiceDiscoveryState::FINISHED &&(check_cod_le_audio(bd_addr) ||metadata_cb.le_audio_cache.contains(bd_addr) ||BTA_DmCheckLeAudioCapable(bd_addr))) {skip_reporting_wait_for_le = true;}/* onUuidChanged requires getBondedDevices to be populated.** bond_state_changed needs to be sent prior to remote_device_property*/auto num_eir_uuids = 0;Uuid uuid = {};// 只在 SDP + 配对都完成后进入。if (pairing_cb.state == BT_BOND_STATE_BONDED && pairing_cb.sdp_attempts &&results_for_bonding_device) {LOG_INFO("SDP search done for %s", bd_addr.ToString().c_str());pairing_cb.sdp_attempts = 0; // 重置重试计数。LOG_INFO("sdp_attempts = %d", pairing_cb.sdp_attempts);// Send UUIDs discovered through EIR to Java to unblock pairing intent// when SDP failed or no UUID is discoveredif (p_data->disc_res.result != BTA_SUCCESS ||p_data->disc_res.num_uuids == 0) { // 如果 SDP 失败或没有发现 UUID:auto uuids_iter = eir_uuids_cache.find(bd_addr);if (uuids_iter != eir_uuids_cache.end()) { // 尝试从缓存中读取 EIR UUID 并上报。num_eir_uuids = static_cast<int>(uuids_iter->second.size());LOG_INFO("SDP failed, send %d EIR UUIDs to unblock bonding %s",num_eir_uuids, bd_addr.ToString().c_str());for (auto eir_uuid : uuids_iter->second) {auto uuid_128bit = eir_uuid.To128BitBE();property_value.insert(property_value.end(), uuid_128bit.begin(),uuid_128bit.end());}eir_uuids_cache.erase(uuids_iter);}if (num_eir_uuids > 0) {prop.val = (void*)property_value.data();prop.len = num_eir_uuids * Uuid::kNumBytes128;} else { // 若 EIR 无内容,则构造空 UUID。LOG_WARN("SDP failed and we have no EIR UUIDs to report either");prop.val = &uuid;prop.len = Uuid::kNumBytes128;}}// Both SDP and bonding are done, clear pairing control block in case// it is not already clearedpairing_cb = {}; // 清除 pairing_cb 控制块。LOG_INFO("clearing btif pairing_cb");}// 将 UUID 存储到本地数据库。if (p_data->disc_res.num_uuids != 0 || num_eir_uuids != 0) {/* Also write this to the NVRAM */ret = btif_storage_set_remote_device_property(&bd_addr, &prop);ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote services failed",ret);if (skip_reporting_wait_for_le) { // 如果不是 LE Audio 的延迟上报情况,则立即通过回调通知 Java 层属性已更新。LOG_INFO("Bonding LE Audio sink - must wait for le services discovery ""to pass all services to java %s",PRIVATE_ADDRESS(bd_addr));/* For LE Audio capable devices, we care more about passing GATT LE* services than about just finishing pairing. Service discovery* should be scheduled when LE pairing finishes, by call to* btif_dm_get_remote_services(bd_addr, BT_TRANSPORT_LE) */return;}/* Send the event to the BTIF */invoke_remote_device_properties_cb(BT_STATUS_SUCCESS, bd_addr, 1,&prop);}} break;...}
功能点 | 实现逻辑 |
---|---|
是否是配对设备 | 比较地址匹配 pairing_cb |
SDP 重试 | 最多重试 3 次,间隔触发 |
UUID 处理 | 收集、过滤、合并、识别 A2DP 能力 |
LE Audio | 若需 GATT 完成则跳过上报 |
SDP 失败回退 | 通过 EIR UUID 解锁配对流程 |
属性更新 | 保存到数据库 + 上报 Java 层 |
01-02 14:21:00.149273 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1638 btif_dm_search_services_evt: SDP finished for xx:xx:xx:xx:b0:62:
01-02 14:21:00.149282 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1647 btif_dm_search_services_evt: New UUIDs for 70:8f:47:91:b0:62:
01-02 14:21:00.149295 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:0 uuid:0000110a-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149309 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:1 uuid:00001105-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149320 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:2 uuid:00001115-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149332 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:3 uuid:00001116-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149343 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:4 uuid:0000112d-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149355 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:5 uuid:0000110e-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149366 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:6 uuid:0000112f-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149378 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:7 uuid:00001112-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149397 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:8 uuid:0000111f-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149409 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:9 uuid:00001132-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149423 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:12 uuid:8fa9c715-bd1f-596c-a1b0-13162b15c892
01-02 14:21:00.149441 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:13 uuid:2c042b0a-7f57-4c0a-afcf-1762af70257c
01-02 14:21:00.149453 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:14 uuid:9fed64fd-e91a-499e-88dd-73dfe023feed
01-02 14:21:00.149859 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1696 btif_dm_search_services_evt: SDP search done for 70:8f:47:91:b0:62
01-02 14:21:00.149882 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1698 btif_dm_search_services_evt: sdp_attempts = 0
01-02 14:21:00.149891 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1728 btif_dm_search_services_evt: clearing btif pairing_cb
- 车机向 手机请求 sdp
976 2025-01-02 14:21:00.107670 f8:6b:14:d1:ec:32 (cheji) vivoMobi_91:b0:62 (cbx) SDP 31 Sent Service Search Attribute Request : L2CAP: Attribute Range (0x0000 - 0xffff) Frame 976: 31 bytes on wire (248 bits), 31 bytes captured (248 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP Protocol
Bluetooth SDP ProtocolPDU: Service Search Attribute Request (0x06)Transaction Id: 0x0001Parameter Length: 17Service Search Pattern: L2CAPMaximum Attribute Byte Count: 1008Attribute ID ListContinuation State: yes (03 F0)
- 手机向 车机返回结果:
980 2025-01-02 14:21:00.117463 vivoMobi_91:b0:62 (cbx) f8:6b:14:d1:ec:32 (Mycar28825) SDP 487 Rcvd Service Search Attribute Response Frame 980: 487 bytes on wire (3896 bits), 487 bytes captured (3896 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP Protocol
Bluetooth SDP ProtocolPDU: Service Search Attribute Response (0x07)Transaction Id: 0x0001Parameter Length: 473Attribute List Byte Count: 470Data FragmentContinuation State: no (00)[Reassembled Attribute List]Attribute Lists [count = 16]Data Element: Sequence uint16 1475 bytes0011 0... = Data Element Type: Sequence (6).... .110 = Data Element Size: uint16 (6)Data Element Var Size: 1475Data ValueAttribute List [count = 4] (Generic Attribute Profile)Attribute List [count = 4] (Generic Access Profile)Attribute List [count = 6] (Headset Audio Gateway)Attribute List [count = 8] (Handsfree Audio Gateway)Attribute List [count = 8] (A/V Remote Control Target)Attribute List [count = 7] (Audio Source)Attribute List [count = 7] (A/V Remote Control)Attribute List [count = 11] (PAN NAP)Attribute List [count = 9] (PAN PANU)Attribute List [count = 6] (SIM Access)Attribute List [count = 7] (Phonebook Access Server)Attribute List [count = 10] (Message Access Server)Attribute List [count = 8] (OBEX Object Push)Attribute List [count = 5] (CustomUUID: Unknown)Attribute List [count = 4] (CustomUUID: Unknown)Attribute List [count = 4] (CustomUUID: Unknown)
2. BTA_DM_GATT_OVER_LE_RES_EVT
这段代码,它处理的是 通过 GATT over LE 发现到的服务 UUID,这是在蓝牙设备进行 LE Audio 相关配对和服务发现时的关键步骤之一。
static void btif_dm_search_services_evt(tBTA_DM_SEARCH_EVT event,tBTA_DM_SEARCH* p_data) {switch (event) {// 表示收到 BLE GATT 服务发现完成事件,主要处理通过 GATT over LE 获取到的服务信息。case BTA_DM_GATT_OVER_LE_RES_EVT: {/*num_properties: 记录即将设置的属性数量(如 UUID、名称)。prop: 保存即将传递给上层的属性,最多两个(UUIDs + 名称)。property_value: 保存所有服务 UUID 的序列化值。uuids: 使用 set 记录 UUID,防止重复。bd_addr: 当前发现服务的设备地址。*/int num_properties = 0;bt_property_t prop[2];std::vector<uint8_t> property_value;std::set<Uuid> uuids;RawAddress& bd_addr = p_data->disc_ble_res.bd_addr;if (event == BTA_DM_GATT_OVER_LE_RES_EVT) {LOG_INFO("New GATT over LE UUIDs for %s:",PRIVATE_ADDRESS(bd_addr));if ((bd_addr == pairing_cb.bd_addr ||bd_addr == pairing_cb.static_bdaddr)) {if (pairing_cb.gatt_over_le !=btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED) {LOG_ERROR("gatt_over_le should be SCHEDULED, did someone clear the ""control block for %s ?",PRIVATE_ADDRESS(bd_addr));}/*如果当前设备是正在配对的目标设备,更新配对控制块 pairing_cb 的 GATT 状态为已完成。同时判断 gatt_over_le 状态是否应该是 SCHEDULED(即之前已经调度过该设备的服务发现)——否则可能说明状态机错乱。*/pairing_cb.gatt_over_le =btif_dm_pairing_cb_t::ServiceDiscoveryState::FINISHED;if (pairing_cb.sdp_over_classic !=btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED) {// 如果 classic SDP 服务发现也没有调度或已经完成,说明整个配对与服务发现流程都已完成,可以安全清除 pairing_cb。// Both SDP and bonding are either done, or not scheduled,// we are safe to clear the service discovery part of CB.LOG_INFO("clearing pairing_cb");pairing_cb = {};}}} else {// 这段用于处理 BTA_DM_GATT_OVER_SDP_RES_EVT(即通过 classic SDP 获取 GATT UUID),但这里实际处理的是 GATT_OVER_LE_RES_EVT。LOG_INFO("New GATT over SDP UUIDs for %s:", PRIVATE_ADDRESS(bd_addr));}for (Uuid uuid : *p_data->disc_ble_res.services) {/*遍历发现到的 GATT 服务 UUID:只保留 “有趣的” LE 服务(如 Hearing Aid、LE Audio)。忽略指定的 UUID。使用 set 自动去重。*/if (btif_is_interesting_le_service(uuid)) {if (btif_should_ignore_uuid(uuid)) {continue;}LOG_INFO("index:%d uuid:%s", static_cast<int>(uuids.size()),uuid.ToString().c_str());uuids.insert(uuid);}}if (uuids.empty()) {// 如果没有任何服务 UUID 被记录,直接退出。LOG_INFO("No well known GATT services discovered");return;}// 读取该设备之前已经缓存的 UUID 并合并,防止遗漏或丢失信息。Uuid existing_uuids[BT_MAX_NUM_UUIDS] = {};btif_get_existing_uuids(&bd_addr, existing_uuids);for (int i = 0; i < BT_MAX_NUM_UUIDS; i++) {Uuid uuid = existing_uuids[i];if (uuid.IsEmpty()) {continue;}uuids.insert(uuid);}// 将 UUID 以 128bit Big-Endian 格式写入 property_value,用于上层 Java 层接收。for (auto& uuid : uuids) {auto uuid_128bit = uuid.To128BitBE();property_value.insert(property_value.end(), uuid_128bit.begin(),uuid_128bit.end());}// 设置 UUID 属性并保存到 内存中prop[0].type = BT_PROPERTY_UUIDS;prop[0].val = (void*)property_value.data();prop[0].len = Uuid::kNumBytes128 * uuids.size();/* Also write this to the NVRAM */bt_status_t ret =btif_storage_set_remote_device_property(&bd_addr, &prop[0]);ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote services failed", ret);num_properties++;// 如果通过 BLE GATT 回包中包含设备名,就保存远程设备名称属性。/* Remote name update */if (strnlen((const char*)p_data->disc_ble_res.bd_name, BD_NAME_LEN)) {prop[1].type = BT_PROPERTY_BDNAME;prop[1].val = p_data->disc_ble_res.bd_name;prop[1].len = strnlen((char*)p_data->disc_ble_res.bd_name, BD_NAME_LEN);ret = btif_storage_set_remote_device_property(&bd_addr, &prop[1]);ASSERTC(ret == BT_STATUS_SUCCESS,"failed to save remote device property", ret);num_properties++;}// 如果当前是 GATT over SDP 的结果,不在这里直接上报,而是等待 DISC_RES_EVT 一并处理/* If services were returned as part of SDP discovery, we will immediately* send them with rest of SDP results in BTA_DM_DISC_RES_EVT */if (event == BTA_DM_GATT_OVER_SDP_RES_EVT) {return;}// 将 UUID 和名称(如果有)回调通知上层应用,例如用于 Java API BluetoothDevice.getUuids() 或用于触发服务绑定。/* Send the event to the BTIF */invoke_remote_device_properties_cb(BT_STATUS_SUCCESS, bd_addr,num_properties, prop);} break;default: { ASSERTC(0, "unhandled search services event", event); } break;}
此 case 的主要作用是处理通过 BLE GATT 通道 发现的服务 UUID,具体流程如下:
步骤 | 操作 |
---|---|
1 | 判断是否是当前配对目标 |
2 | 标记 GATT 服务发现已完成 |
3 | 从 GATT 发现包中提取 UUID,并过滤无关的 |
4 | 合并之前已缓存的 UUID(避免丢失) |
5 | 保存 UUID 属性并更新 NVRAM |
6 | 如有远程设备名,也一并保存 |
7 | 最后回调通知 Java 层,完成 BLE 服务发现过程 |
3. 配对阶段(Pairing / Bonding)
1. 作用
-
缓存配对结果:Bond 状态、IO 能力、安全连接支持与否、LinkKey 类型与值
-
Profile 模块可依赖
DeviceProperties
判断是否可信 / 是否可加密传输
2. 写入 bt_config.conf 内容
字段 | 示例 |
---|---|
Bonded | true |
LinkKey | base64 编码 |
LinkKeyType | 5(Unauthenticated Combination) |
IOCapability | 3(No Input No Output) |
SecureConnection | true |
3. 解决的问题
问题 | 如何解决 |
---|---|
重启系统后丢失配对信息 | LinkKey / Bond 信息持久化 |
多设备状态混乱 | 每个设备独立记录属性 |
不确定是否加密传输 | 使用缓存 IO 能力与 LinkKey 属性判断安全等级 |
4. 代码鉴赏
1. btif_dm_ssp_cfm_req_evt
btif_dm_ssp_cfm_req_evt
函数
- 它在 Bluetooth Secure Simple Pairing(SSP)确认请求事件 到达时执行。
这个函数在收到远端设备发起的 SSP 请求(特别是 Just Works 或 Passkey Confirmation 模式)时:
-
判断是否可继续配对;
-
设置配对状态;
-
依据配对模式判断是否自动接受;
-
最终将请求上报给 Java 层显示 UI 以确认(如弹窗显示 PIN)。
// system/btif/src/btif_dm.cc/********************************************************************************* Function btif_dm_ssp_cfm_req_evt** Description Executes SSP confirm request event in btif context** Returns void*******************************************************************************/
// 接收一个指向 SSP 确认请求的结构体指针 p_ssp_cfm_req,包含发起配对的设备地址、名称、class、配对模式(Just Works / Passkey)、数字比较数值等信息
static void btif_dm_ssp_cfm_req_evt(tBTA_DM_SP_CFM_REQ* p_ssp_cfm_req) {bt_bdname_t bd_name;bool is_incoming = !(pairing_cb.state == BT_BOND_STATE_BONDING); // 判断当前是否是被动配对(未处于 BONDING 状态)。uint32_t cod; // Class of Device(设备类型编码)。int dev_type; // 设备类型(BR/EDR、BLE 或 Dual Mode)。BTIF_TRACE_DEBUG("%s", __func__);/*优先通过 feature 判断远端是否为 dual-mode。若失败,则尝试查询设备类型。查询失败,默认视为传统 BR/EDR 设备。*//* Remote properties update */if (BTM_GetPeerDeviceTypeFromFeatures(p_ssp_cfm_req->bd_addr) ==BT_DEVICE_TYPE_DUMO) {dev_type = BT_DEVICE_TYPE_DUMO;} else if (!btif_get_device_type(p_ssp_cfm_req->bd_addr, &dev_type)) {// Failed to get device type, defaulting to BR/EDR.dev_type = BT_DEVICE_TYPE_BREDR;}// 更新设备的属性缓存:名称、Class of Device 和类型。btif_update_remote_properties(p_ssp_cfm_req->bd_addr, p_ssp_cfm_req->bd_name,p_ssp_cfm_req->dev_class,(tBT_DEVICE_TYPE)dev_type);// 缓存设备地址和名称,后面用于显示和判断。RawAddress bd_addr = p_ssp_cfm_req->bd_addr;memcpy(bd_name.name, p_ssp_cfm_req->bd_name, BD_NAME_LEN);// 如果当前正在和某个设备配对,而新来的请求来自另一个设备,说明存在配对冲突。if (pairing_cb.state == BT_BOND_STATE_BONDING &&bd_addr != pairing_cb.bd_addr) {BTIF_TRACE_WARNING("%s(): already in bonding state, reject request",__FUNCTION__);// 拒绝该请求,防止状态混乱或被攻击。btif_dm_ssp_reply(bd_addr, BT_SSP_VARIANT_PASSKEY_CONFIRMATION, 0);return;}/* Set the pairing_cb based on the local & remote authentication requirements*/// 将当前设备设置为 BONDING 状态,并上报配对事件(Java 层通过广播感知)。bond_state_changed(BT_STATUS_SUCCESS, bd_addr, BT_BOND_STATE_BONDING);// 记录当前配对是否为 Just Works,以及双方的认证需求。BTIF_TRACE_EVENT("%s: just_works:%d, loc_auth_req=%d, rmt_auth_req=%d",__func__, p_ssp_cfm_req->just_works,p_ssp_cfm_req->loc_auth_req, p_ssp_cfm_req->rmt_auth_req);/*判断是否是临时配对如果是 Just Works 模式,且:双方都没有要求配对记忆(bonding)也不是鼠标等 pointing device*//* if just_works and bonding bit is not set treat this as temporary */if (p_ssp_cfm_req->just_works &&!(p_ssp_cfm_req->loc_auth_req & BTM_AUTH_BONDS) &&!(p_ssp_cfm_req->rmt_auth_req & BTM_AUTH_BONDS) &&!(check_cod((RawAddress*)&p_ssp_cfm_req->bd_addr, COD_HID_POINTING)))pairing_cb.bond_type = tBTM_SEC_DEV_REC::BOND_TYPE_TEMPORARY; // 那么是“临时配对”,elsepairing_cb.bond_type = tBTM_SEC_DEV_REC::BOND_TYPE_PERSISTENT; // 否则就是“持久配对”(会保存)。btm_set_bond_type_dev(p_ssp_cfm_req->bd_addr, pairing_cb.bond_type); // 将配对类型同步设置到底层。pairing_cb.is_ssp = true; // 设置 pairing_cb 中当前为 SSP 配对(而不是 legacy pairing)。/* If JustWorks auto-accept */if (p_ssp_cfm_req->just_works) { // 自动接受 Just Works 模式的特殊情况/* Pairing consent for JustWorks NOT needed if:* 1. Incoming temporary pairing is detected*/if (is_incoming &&pairing_cb.bond_type == tBTM_SEC_DEV_REC::BOND_TYPE_TEMPORARY) {/*如果是 Just Works 模式,且当前是被动发起(远端请求配对)、且是临时配对:*/BTIF_TRACE_EVENT("%s: Auto-accept JustWorks pairing for temporary incoming", __func__);btif_dm_ssp_reply(bd_addr, BT_SSP_VARIANT_CONSENT, true); // 自动接受该配对,无需用户确认。这用于临时设备,比如游戏手柄、耳机等return;}}cod = devclass2uint(p_ssp_cfm_req->dev_class); // 将设备 class(3 字节)转为整型数值。if (cod == 0) { // 如果为 0(未知),设置为未分类。LOG_INFO("%s cod is 0, set as unclassified", __func__);cod = COD_UNCLASSIFIED;}pairing_cb.sdp_attempts = 0; // 刚进入配对流程,SDP 尝试次数设为 0。LOG_INFO("sdp_attempts = %d", pairing_cb.sdp_attempts);/*通知 Java 层显示配对确认弹窗调用回调函数,通知 Java 层出现确认界面。BT_SSP_VARIANT_CONSENT → Just Works 模式(只需确认,不输入)。BT_SSP_VARIANT_PASSKEY_CONFIRMATION → 数字比较模式(确认6位数字相同)。num_val 就是要比较的那6位数字。*/invoke_ssp_request_cb(bd_addr, bd_name, cod,(p_ssp_cfm_req->just_works ? BT_SSP_VARIANT_CONSENT: BT_SSP_VARIANT_PASSKEY_CONFIRMATION),p_ssp_cfm_req->num_val);
}
步骤 | 动作 |
---|---|
1 | 判断是否为当前正在配对的设备,若不是则拒绝 |
2 | 更新设备类型、属性 |
3 | 设置 pairing_cb 结构体状态 |
4 | 如果是 JustWorks 且临时设备、被动连接,直接自动接受 |
5 | 如果需要用户确认(JustWorks 或 Passkey Compare),则通知 Java 层 UI 确认 |
6 | 设置配对类型、认证信息等上下文,准备后续配对流程 |
01-02 14:20:56.052840 2146 3220 I bt_btm : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: BTM_SP_CFM_REQ_EVT: num_val: 363625
01-02 14:20:56.052917 2146 3220 I bt_btm : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btm_proc_sp_req_evt() just_works:0, io loc:1, rmt:1, auth loc:3, rmt:3
01-02 14:20:56.052933 2146 3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: bta_dm_sp_cback: 2
01-02 14:20:56.052948 2146 3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: btif_dm_upstreams_evt: ev: BTA_DM_SP_CFM_REQ_EVT
01-02 14:20:56.052958 2146 3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_dm_ssp_cfm_req_evt
01-02 14:20:56.052999 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:641 btif_update_remote_properties: class of device (cod) is 0x5a020c
01-02 14:20:56.053026 2146 3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_in_fetch_bonded_ble_device Found a LE device: 70:8f:47:91:b0:62
01-02 14:20:56.053116 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:462 get_cod: get_cod remote_cod = 0x005a020c
01-02 14:20:56.053294 2146 2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: operator(): HAL bt_hal_cbacks->remote_device_properties_cb
01-02 14:20:56.053394 2146 3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: btif_dm_ssp_cfm_req_evt: just_works:0, loc_auth_req=3, rmt_auth_req=3
01-02 14:20:56.053424 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:974 btif_dm_ssp_cfm_req_evt: sdp_attempts = 0
01-02 14:20:56.053441 2146 3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: dm status: 1
01-02 14:20:56.053461 2146 2634 I BluetoothRemoteDevices: Property type: 1
01-02 14:20:56.053486 2146 2634 I BluetoothRemoteDevices: Skip name update for 70:8F:47:91:B0:62
01-02 14:20:56.053525 2146 2634 I BluetoothRemoteDevices: Property type: 4
01-02 14:20:56.053559 2146 2634 I BluetoothRemoteDevices: Skip class update for 70:8F:47:91:B0:62
01-02 14:20:56.053570 2146 2634 I BluetoothRemoteDevices: Property type: 5
01-02 14:20:56.053601 2146 2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: operator(): HAL bt_hal_cbacks->ssp_request_cb
01-02 14:20:56.053739 2146 2634 I BluetoothBondStateMachine: sspRequestCallback: [B@c3ee023 name: [B@d224920 cod: 5898764 pairingVariant 0 passkey: 363625
01-02 14:20:56.055029 2146 2719 D BluetoothBondStateMachine: PendingCommandState: processMessage msg.what=5 dev=70:8F:47:91:B0:62 (cbx)
4. 连接阶段(Connection)
1. 作用
-
在连接发起前,查询:
-
是否已配对
-
是否支持某 UUID
-
是否要求安全连接
-
-
用于 Profile 判断:
-
是否建立连接
-
是否先 SDP
-
是否强制加密通道
-
2. 解决的问题
问题 | 如何解决 |
---|---|
每次连接都等待 SDP 完成 | 使用缓存 UUID 快速判断 |
Profile 初始化失败 | 利用缓存状态预检能力 |
重复连接失败 | 使用缓存 Key 与安全属性重用连接通道 |
3. 代码鉴赏
当 app 层调用 BluetoothDevice.connect 时,将会触发 bt.server 调用
// android/app/src/com/android/bluetooth/btservice/AdapterService.javaprivate int connectEnabledProfiles(BluetoothDevice device) {ParcelUuid[] remoteDeviceUuids = getRemoteUuids(device); // 会先获取 该设备的 uuid 信息。 该信息是在 SDP 阶段 上报上来的。ParcelUuid[] localDeviceUuids = mAdapterProperties.getUuids();// 如果 远端和 本地 都支持 a2dp 那么就连接 a2dpif (mA2dpService != null && isSupported(localDeviceUuids, remoteDeviceUuids,BluetoothProfile.A2DP, device)) {Log.i(TAG, "connectEnabledProfiles: Connecting A2dp");mA2dpService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_AUTOCONNECT);mA2dpService.connect(device);}...}public ParcelUuid[] getRemoteUuids(BluetoothDevice device) {DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);if (deviceProp == null) {return null;}return deviceProp.getUuids();}
// android/app/src/com/android/bluetooth/btservice/RemoteDevices.javaParcelUuid[] getUuids() {synchronized (mObject) {return mUuids;}}
- 下面是 sdp 完成后, 上报 uuid 的日志
01-02 14:21:00.149859 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1696 btif_dm_search_services_evt: SDP search done for 70:8f:47:91:b0:62
01-02 14:21:00.149882 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1698 btif_dm_search_services_evt: sdp_attempts = 0
01-02 14:21:00.149891 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1728 btif_dm_search_services_evt: clearing btif pairing_cb
01-02 14:21:00.150000 2146 3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_in_fetch_bonded_ble_device Found a LE device: 70:8f:47:91:b0:62
01-02 14:21:00.153761 723 723 I V4L2CameraHAL: V4L2CameraHAL:V4L2CameraHAL:71: ais_v4l2_proxy not ready. try again.
01-02 14:21:00.159003 2146 3220 I bt_bta_dm: packages/modules/Bluetooth/system/bta/dm/bta_dm_act.cc:1371 bta_dm_search_cmpl: No BLE connection, processing classic results
01-02 14:21:00.159092 2146 2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: operator(): HAL bt_hal_cbacks->remote_device_properties_cb
01-02 14:21:00.159240 2146 2634 I BluetoothRemoteDevices: Property type: 3
01-02 14:21:00.159360 2146 2634 I BluetoothAdapterService: sendUuidsInternal: Received service discovery UUIDs for device 70:8F:47:91:B0:62
01-02 14:21:00.159403 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=0 uuid=00001105-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159436 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=1 uuid=0000110a-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159462 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=2 uuid=0000110c-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159493 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=3 uuid=0000110e-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159558 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=4 uuid=00001112-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159587 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=5 uuid=00001115-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159607 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=6 uuid=00001116-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159628 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=7 uuid=0000111f-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159694 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=8 uuid=0000112d-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159725 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=9 uuid=0000112f-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159746 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=10 uuid=00001132-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159766 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=11 uuid=00001200-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159792 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=12 uuid=2c042b0a-7f57-4c0a-afcf-1762af70257c
01-02 14:21:00.159815 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=13 uuid=8fa9c715-bd1f-596c-a1b0-13162b15c892
01-02 14:21:00.159835 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=14 uuid=9fed64fd-e91a-499e-88dd-73dfe023feed
01-02 14:21:00.160431 2146 2719 D BluetoothBondStateMachine: StableState: processMessage msg.what=10 dev=70:8F:47:91:B0:62 (cbx)
01-02 14:21:00.161052 2048 2487 D BluetoothDevice: getUuids()
01-02 14:21:00.162895 2146 2719 I BluetoothBondStateMachine: Bond State Change Intent:70:8F:47:91:B0:62 (cbx) BOND_BONDING => BOND_BONDED
void devicePropertyChangedCallback(byte[] address, int[] types, byte[][] values) {Intent intent;byte[] val;int type;BluetoothDevice bdDevice = getDevice(address);DeviceProperties deviceProperties;if (bdDevice == null) {debugLog("Added new device property");deviceProperties = addDeviceProperties(address);bdDevice = getDevice(address);} else {deviceProperties = getDeviceProperties(bdDevice);}if (types.length <= 0) {errorLog("No properties to update");return;}for (int j = 0; j < types.length; j++) {type = types[j];val = values[j];if (val.length > 0) {synchronized (mObject) {infoLog("Property type: " + type);switch (type) {...case AbstractionLayer.BT_PROPERTY_UUIDS: // 0x03int numUuids = val.length / AbstractionLayer.BT_UUID_SIZE;final ParcelUuid[] newUuids = Utils.byteArrayToUuid(val);//ParcelUuid[] uuids = updateUuids(deviceProperties.mUuids, newUuids);if (areUuidsEqual(newUuids, deviceProperties.mUuids)) {infoLog( "Skip uuids update for " + bdDevice.getAddress());break;}deviceProperties.mUuids = newUuids;if (sAdapterService.getState() == BluetoothAdapter.STATE_ON) {sAdapterService.deviceUuidUpdated(bdDevice);sendUuidIntent(bdDevice, deviceProperties);} else if (sAdapterService.getState()== BluetoothAdapter.STATE_BLE_ON) {sAdapterService.deviceUuidUpdated(bdDevice);}break;...}}}}}
5. 重点问题
1. 搜索(Discovery)阶段不会写入 bt_stack.conf
的原因
1. 目的:快速获取周围设备信息,不持久化
-
搜索时获取到的是临时的广播信息(例如设备名称、地址、Class of Device、RSSI 等),系统主要用于UI 展示或后续配对流程判断。
-
尚未配对(bond)或连接的设备,不需要写入配置文件,因为:
-
这些信息随时可能变化;
-
搜索结果可能包含大量设备,不宜持久化;
-
节省写入频率,减少 I/O 消耗和 flash 损耗;
-
避免保存不可信或无效设备。
-
2. 那什么时候才会写入 bt_config.conf
或 bt_stack.conf
?
设备信息只有在以下场景中,才会被持久化到 bt_config.conf
(旧系统)或 bt_stack.conf
(AOSP 13 后被 StorageModule
管理)中:
1. 配对成功(Bonding Success)
-
成功配对后,系统将设备标记为“可信设备”(bonded),会记录:
-
地址(MAC)
-
设备名称
-
连接方式(BR/EDR 或 LE)
-
link key / LE key(加密用)
-
profile 支持(如 A2DP、HID 等)
-
services UUIDs(通常来自 SDP)
-
2. SDP 完成后(部分 Profile 使用)
-
若设备支持 classic profile,在配对后通常会进行 SDP 查询服务信息(如 A2DP、HFP)。
-
得到的 UUID 信息、PSM、版本等,也会被写入配置中供下次连接使用。
3. 主动连接成功
- 即使未配对,但连接了某些 BLE 服务,系统也可能缓存部分 GATT 服务信息用于快速重连,但不一定写入 config 文件。
2. StorageModule 与 config 文件的关系(AOSP 13+)
在 AOSP 13 中,DeviceProperties
模块已被 StorageModule
接管持久化工作:
-
StorageModule
是新的统一设备信息存取接口。 -
bt_config.conf
文件依然存在于/data/misc/bluedroid/
,但读写是通过StorageModule
来完成的。 -
StorageModule
会在合适的时机(如配对成功、服务发现)将设备信息写入文件。
3. 总结重点
阶段 | 是否写入配置文件(bt_config.conf / bt_stack.conf) | 说明 |
---|---|---|
🔍 搜索阶段 | ❌ 不写入 | 信息临时,仅用于展示 |
🤝 配对成功 | ✅ 写入 | 标志为可信设备,保存 key 和 profile |
📡 SDP 成功 | ✅ 写入 | 保存服务 UUID 等 |
🔌 BLE 连接 | 可能写入(部分缓存) | 依 profile 情况而定 |
📁 存储模块 | 由 StorageModule 统一管理配置 | 负责设备信息加载/保存 |
6. 总结
阶段 | 作用 | 是否写入配置 | 解决的问题 |
---|---|---|---|
搜索 | 缓存名称/类型/RSSI,避免重复 | 否 | 设备显示混乱、信息缺失 |
SDP | 缓存支持 UUID | 是 | 提前判断服务能力 |
配对 | 缓存 bond/key、安全能力 | 是 | 配对信息持久化,安全连接判断 |
连接 | 提供连接前判断支持 | 否(使用缓存) | 连接初始化优化,profile 可预判 |
DeviceProperties
是 AOSP 蓝牙系统中的“设备状态中台”,统一缓存、更新并与StorageModule
配合写入持久化文件,确保设备从被发现 → 服务识别 → 配对 → 连接全过程中,系统始终拥有最新、稳定、可持续利用的设备状态数据。