前面我们提到了 蓝牙协议栈中的 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 对象:

  1. AdapterProperties.adapterPropertyChangedCallback:BT_PROPERTY_ADAPTER_BONDED_DEVICES
    • 在打开蓝牙时, AdapterProperties 会收到 BT_PROPERTY_ADAPTER_BONDED_DEVICES 事件;此时会将之前 已经配对的设备 封装为一个个 DeviceProperties 对象。
  2. BondStateMachine.sspRequestCallback
    • 设备配对时支持 SSP 模式,进行确认、比较、输入密钥等操作时触发
  3. BondStateMachine.pinRequestCallback
    • 当连接传统蓝牙设备(BR/EDR)时需要输入 PIN 码进行配对时触发
  4. RemoteDevices.devicePropertyChangedCallback
    • 当 设备 属性发生变化时, 从 native -> java 上报设备信息时,如果找不到对应设备的 Property 将新建一个。

2. 管理那些属性:

在 【android bluetooth 框架分析 04】【bt-framework 层详解 6】【Properties介绍】 中有详细介绍。

枚举常量说明使用范围数据类型访问权限
🔁 适用于 Adapter 和 Remote Device
BT_PROPERTY_BDNAME设备名称Adapter: 读/写Remote Device: 只读bt_bdname_tGET / SET(Adapter)GET(Remote)
BT_PROPERTY_BDADDR设备地址Adapter & Remote DeviceRawAddressGET
BT_PROPERTY_UUIDS支持的服务 UUID 列表Remote Devicebluetooth::Uuid[]GET
BT_PROPERTY_CLASS_OF_DEVICE类别码Remote Deviceuint32_tGET
BT_PROPERTY_TYPE_OF_DEVICE设备类型(BR/EDR/LE)Remote Devicebt_device_type_tGET
BT_PROPERTY_SERVICE_RECORD服务记录Remote Devicebt_service_record_tGET
枚举常量说明使用范围数据类型访问权限
📡 仅适用于 Remote Device(远程设备)
BT_PROPERTY_REMOTE_FRIENDLY_NAME远程设备名称(用户设定)Remote Devicebt_bdname_tGET / SET
BT_PROPERTY_REMOTE_RSSI远程设备 RSSIRemote Deviceint8_tGET
BT_PROPERTY_REMOTE_VERSION_INFO远程设备协议版本信息Remote Devicebt_remote_version_tGET / SET
BT_PROPERTY_REMOTE_IS_COORDINATED_SET_MEMBER是否是协同设备成员Remote DeviceboolGET
BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP属性刷新时间戳Remote Deviceint64_t(或自定义)GET

3. native -> java callback

在 搜索、 配对 、 sdp 的过程中,native 在不同阶段都会触发 回调 到 java 层,来更新 DeviceProperties .

协议栈会通过 下面两个函数来, 层层 上报 属性到 java 层:

  1. invoke_device_found_cb
  2. 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]

devicePropertyChangedCallbackdeviceFoundCallback 是 AOSP 蓝牙框架中两个核心的回调函数,虽然它们都与设备属性和发现有关,但它们在触发时机、作用、广播内容、应用场景等方面都有明显差异。

下面从多个维度对 相同点与不同点 进行详细对比:


1.相同点
维度描述
🔧 来源都是由 native 层(通过 JNI)调用 Java 层的回调函数
📡 与设备相关都涉及对某个 BluetoothDevice 设备的处理
🧠 依赖 DeviceProperties都通过 getDeviceProperties(device) 获取设备缓存属性
🔒 权限控制广播时都依赖蓝牙相关权限(如 BLUETOOTH_SCAN
📲 可导致广播都有可能向上层发送 Android 广播(如 ACTION_FOUND, ACTION_NAME_CHANGED, ACTION_UUID, 等)
🧪 开发调试中常出现都会在使用蓝牙调试(如配对、扫描)过程中频繁触发
🧩 与应用层交互都可能引发第三方 app 的回调(通过广播接收器)

2.不同点
比较维度deviceFoundCallbackdevicePropertyChangedCallback
💥 触发时机当蓝牙扫描发现设备时调用(第一次或再次发现)当远程设备的属性发生变化时调用(如名称、RSSI、UUID 等)
🔁 调用频率在一次扫描过程中可能多次触发(每个设备发现一次)属性每变化一次触发一次,可能频繁(如 RSSI 不断变化)
📩 广播行为广播 BluetoothDevice.ACTION_FOUND(设备被发现)根据属性类型广播不同事件,如:
  • ACTION_NAME_CHANGED
  • ACTION_UUID
  • ACTION_RSSI
🔍 目的表示“新设备”被发现,通知系统和应用显示表示“已知设备的属性”发生变化,更新状态或 UI
🧬 广播携带信息BluetoothDevice、设备类型、名称、RSSI、是否为群组成员取决于变化的属性(可能是 UUID、名称、RSSI)
🧰 过滤策略参与参与“是否广播”策略(如名称为空不广播)不参与过滤,始终处理属性变化
📲 典型场景蓝牙设置页中设备扫描列表展示已配对设备列表中设备名称、信号变化,或配对时获取 UUID
💡 是否依赖扫描流程是,仅在蓝牙扫描流程中调用否,也可能在连接、配对、服务发现后调用

3.实际生活中的类比
情况deviceFoundCallbackdevicePropertyChangedCallback
你在商场里发现一个新品牌店铺店铺出现在你面前的那一刻 —— “发现设备”店铺换了名字、装修风格变了、换老板了、上了新的商品 —— “属性改变”
手机蓝牙设置中扫描时发现设备列表刷新每个设备出现一次触发一次某设备名称更新或信号强度变化,刷新其展示项

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 对比:

比较点devicePropertyChangedCallbackdeviceFoundCallback
触发条件已知设备属性变化(如名称变化、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 失败的情况。

大体流程概览:

  1. 提取事件数据(设备地址、UUID 等)
  2. 判断是否是配对过程的设备
  3. 判断 SDP 是否失败并重试(带次数限制)
  4. 如果成功则合并 UUID、识别 A2DP 功能
  5. 特殊处理 LE Audio 设备
  6. 将 UUID 结果保存、上报到 Java 层
  7. 处理 SDP 失败时尝试使用 EIR 中的 UUID
  8. 最终调用回调通知 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 内容

字段示例
Bondedtrue
LinkKeybase64 编码
LinkKeyType5(Unauthenticated Combination)
IOCapability3(No Input No Output)
SecureConnectiontrue

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.confbt_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 配合写入持久化文件,确保设备从被发现 → 服务识别 → 配对 → 连接全过程中,系统始终拥有最新、稳定、可持续利用的设备状态数据


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/910550.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/910550.shtml
英文地址,请注明出处:http://en.pswp.cn/news/910550.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为OD-2024年E卷-字母组合[200分] -- python

问题描述&#xff1a; 每个数字对应多个字母&#xff0c;对应关系如下&#xff1a; 0&#xff1a;a,b,c 1&#xff1a;d,e,f 2&#xff1a;g,h,i 3&#xff1a;j,k,l 4&#xff1a;m,n,o 5&#xff1a;p,q,r 6&#xff1a;s,t 7&#xff1a;u,v 8&#xff1a;w,x 9&#xff1…

机器学习竞赛中的“A榜”与“B榜”:机制解析与设计深意

在Kaggle、天池等主流机器学习竞赛平台上&#xff0c;“A榜”&#xff08;Public Leaderboard&#xff09;和“B榜”&#xff08;Private Leaderboard&#xff09;是选手们最关注的指标。但很多新人对两者的区别和设计意图感到困惑。本文将深入解析其差异及背后的逻辑。 &#…

云徙科技 OMS:让订单管理变得轻松又高效

在如今这个线上线下购物融合得越来越紧密的时代&#xff0c;企业要是想在竞争激烈的市场里站稳脚跟&#xff0c;订单管理这一块可得好好下功夫。云徙科技的 OMS&#xff08;订单管理系统&#xff09;就像是给企业量身打造的一把“金钥匙”&#xff0c;能帮企业把订单管理得井井…

qt常用控件--02

文章目录 qt常用控件--02toolTip属性focusPolicy属性styleSheet属性补充知识点按钮类控件QPushButton 结语 很高兴和大家见面&#xff0c;给生活加点impetus&#xff01;&#xff01;开启今天的编程之路&#xff01;&#xff01; 今天我们进一步c11中常见的新增表达 作者&…

P3258 [JLOI2014] 松鼠的新家

题目描述 松鼠的新家是一棵树&#xff0c;前几天刚刚装修了新家&#xff0c;新家有 n n n 个房间&#xff0c;并且有 n − 1 n-1 n−1 根树枝连接&#xff0c;每个房间都可以相互到达&#xff0c;且俩个房间之间的路线都是唯一的。天哪&#xff0c;他居然真的住在“树”上。 …

基于openfeign拦截器RequestInterceptor实现的微服务之间的夹带转发

需求&#xff1a; trade服务需要在下单后清空购物车 分析&#xff1a; 显然&#xff0c;清空购物车需要调用cart服务&#xff0c;也就是这个功能的实现涉及到了微服务之间的转发。 其次&#xff0c;清空购车还需要userId&#xff0c;所以需要使用RequestInterceptor来实现夹…

w~深度学习~合集9

我自己的原文哦~ https://blog.51cto.com/whaosoft/14010384 #UPSCALE 这里设计了一个通用算法UPSCALE&#xff0c;可以剪枝具有任意剪枝模式的模型。通过消除约束&#xff0c;UPSCALE将ImageNet精度提高2.1个点。 paper地址&#xff1a;https://arxiv.org/pdf/2307.08…

python如何删除xml中的w:ascii属性

可以使用Python的xml.etree.ElementTree模块通过以下步骤删除XML中的w:ascii属性&#xff1a; import xml.etree.ElementTree as ET# 原始XML片段&#xff08;需包含命名空间声明&#xff09; xml_str <w:rPr xmlns:w"http://schemas.openxmlformats.org/wordproces…

【React】React CSS 样式设置全攻略

在 React 中设置 CSS 样式主要有以下几种方式&#xff0c;各有适用场景&#xff1a; 1. 内联样式 (Inline Styles) 直接在 JSX 元素中使用 style 属性&#xff0c;值为 JavaScript 对象&#xff08;使用驼峰命名法&#xff09; function Component() {return (<div style…

JS红宝书笔记 8.2 创建对象

虽然使用Object构造函数或对象字面量可以方便地创建对象&#xff0c;但这些方式有明显不足&#xff1a;创建具有同样接口的多个对象需要重复编写很多代码 工厂模式可以用不同的参数多次调用函数&#xff0c;每次都会返回一个新对象&#xff0c;这种模式虽然可以解决创建多个类…

高通camx hal进程dump日志分析三:Pipeline DumpDebugInfo原理分析

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、问题背景 二、DumpDebugInfo原理 2.1:我们分析下代码 2.2 :Pipeline Dump debug info 2.3 :dump Metadata Pending Node信息 2.4 :Dump Metadata Pool Debug信息 2.5 :No…

【数据结构】_二叉树基础OJ

目录 1. 单值二叉树 1.1 题目链接与描述 1.2 解题思路 1.3 程序 2. 相同的树 2.1 题目链接与描述 2.2 解题思路 2.3 程序 3. 对称二叉树 3.1 题目链接与描述 3.2 解题思路 3.3 程序 1. 单值二叉树 1.1 题目链接与描述 题目链接&#xff1a; 965. 单值二叉树 - 力…

软件工程画图题

目录 1.大纲 2.数据流图 3.程序流图 4.流图 5.ER图 6.层次图 7.结构图 8.盒图 9.状态转换图 10.类图 11.用例图 12.活动图 13.判定表和判定树 14.基本路径测试过程(白盒测试) 15.等价类划分(黑盒测试) 1.大纲 (1).数据流图 (2).程序流图 (3).流图 (4).ER图…

H7-TOOL自制Flash读写保护算法系列,为华大电子CIU32F003制作使能和解除算法,支持在线烧录和脱机烧录使用2025-06-20

说明&#xff1a; 很多IC厂家仅发布了内部Flash算法文件&#xff0c;并没有提供读写保护算法文件&#xff0c;也就是选项字节算法文件&#xff0c;需要我们制作。 实际上当前已经发布的TOOL版本&#xff0c;已经自制很多了&#xff0c;比如已经支持的兆易创新大部分型号&…

go channel用法

介绍 channel 在 Go 中是一种专门用来在 goroutine 之间传递数据的类型安全的管道。 你可以把它理解成&#xff1a; 多个 goroutine 之间的**“传话筒”**&#xff0c;谁往通道里塞东西&#xff0c;另一个 goroutine 就能接收到。 Go 语言采用 CSP&#xff08;Communicatin…

openLayers切换基于高德、天地图切换矢量、影像、地形图层

1、需要先加载好地图&#xff0c;具体点此链接 openLayers添加天地图WMTS、XYZ瓦片服务图层、高德地图XYZ瓦片服务图层-CSDN博客文章浏览阅读31次。本文介绍了基于OpenLayers的地图交互功能实现&#xff0c;主要包括以下内容&#xff1a; 地图初始化&#xff1a;支持天地图XYZ…

springMVC-15 异常处理

异常处理-基本介绍 基本介绍 1.Spring MVC通过HandlerExceptionResolver处理程序的异常&#xff0c;包括Handler映射、数据绑定以及目标方法执行时发生的异常。 2.主要处理Handler中用ExceptionHandler注解定义的方法。 3.ExceptionHandlerMethodResolver内部若找不到Excepti…

视频汇聚EasyCVR平台v3.7.2发布:新增全局搜索、播放器默认解码方式等4大功能

EasyCVR视频汇聚平台带着全新的v3.7.2版本重磅登场&#xff01;此次升级&#xff0c;绝非简单的功能堆砌&#xff0c;而是从用户体验、操作效率以及系统性能等多维度进行的深度优化与革新&#xff0c;旨在为大家带来更加强大、稳定且高效的视频监控管理体验。 一、全局功能搜索…

三、kubectl使用详解

三、kubectl使用详解 文章目录 三、kubectl使用详解1、常用基础命令1.1 Kubectl命令格式1.2 查询一个资源1.3 创建一个资源1.4 修改一个资源1.5 删除一个资源1.6 其他 2、K8s隔离机制Namespace&#xff08;命名空间作用及使用&#xff09;2.1 什么是命名空间2.2 命名空间主要作…

JVM内存模型详解

JVM内存模型详解 Java虚拟机(JVM)内存模型是理解Java程序运行机制的核心&#xff0c;它定义了程序运行时数据的组织方式和访问规则。与Java内存模型(JMM)关注并发不同&#xff0c;JVM内存模型主要描述运行时数据区的结构和功能。 一、JVM内存模型概述 JVM内存模型将运行时数…