前面我们提到了 蓝牙协议栈中的 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.java
class 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.java
DeviceProperties 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.cc
void 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.cpp
typedef 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 the
initialized 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.cpp
method_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.java
void 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); // 创建新的 DeviceProperties
bdDevice = 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.java
deviceProperties.setDeviceType(Utils.byteArrayToInt(val));
break;
case AbstractionLayer.BT_PROPERTY_REMOTE_RSSI: // 信号强度
// RSSI from hal is in one byte
deviceProperties.setRssi(val[0]);
break;
case AbstractionLayer.BT_PROPERTY_REMOTE_IS_COORDINATED_SET_MEMBER:
// 是否属于 Coordinated Set
deviceProperties.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.cc
int 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.cc
bt_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.cc
void 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.cc
bool 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.cc
std::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_t
bt_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.cc
void 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 设备的 properties
do_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.cc
void 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 advertisment
if (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 name
if ((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 type
auto mutation = storage_module->Modify();
bluetooth::storage::Device device =
storage_module->GetDeviceByLegacyKey(address);
mutation.Add(device.SetDeviceType(device_type));
mutation.Commit();
// update address type
auto 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.cc
static 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 unclassified
01-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:22
01-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:00
01-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 Meta
Event Code: LE Meta (0x3e)
Parameter Total Length: 57
Sub Event: LE Extended Advertising Report (0x0d)
Num Reports: 1
Event Type: 0x0013, Connectable, Scannable, Legacy, Data Status: Complete
Peer 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 dBm
Periodic 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: 31
Advertising Data
Flags
Manufacturer Specific
Device 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 unclassified
01-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:a8
01-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:00
01-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 Meta
Event Code: LE Meta (0x3e)
Parameter Total Length: 38
Sub Event: LE Extended Advertising Report (0x0d)
Num Reports: 1
Event Type: 0x0013, Connectable, Scannable, Legacy, Data Status: Complete
Peer 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 dBm
Periodic 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: 12
Advertising Data
Device 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 unclassified
01-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:c6
01-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:00
01-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 Meta
Event Code: LE Meta (0x3e)
Parameter Total Length: 56
Sub Event: LE Extended Advertising Report (0x0d)
Num Reports: 1
Event Type: 0x0013, Connectable, Scannable, Legacy, Data Status: Complete
Peer 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 dBm
Periodic 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: 30
Advertising Data
Flags
Length: 2
Type: 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 UUID
Length: 17
Type: Service Data - 16 bit UUID (0x16)
UUID 16: Xiaomi Inc. (0xfe95)
Service Data: b054452d00c6af91eaf0d4080e00
Device Name: SMI-M14 // 设备名字
Length: 8
Type: 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 discovered
if (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 cleared
pairing_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 Protocol
PDU: Service Search Attribute Request (0x06)
Transaction Id: 0x0001
Parameter Length: 17
Service Search Pattern: L2CAP
Maximum Attribute Byte Count: 1008
Attribute ID List
Continuation 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 Protocol
PDU: Service Search Attribute Response (0x07)
Transaction Id: 0x0001
Parameter Length: 473
Attribute List Byte Count: 470
Data Fragment
Continuation State: no (00)
[Reassembled Attribute List]
Attribute Lists [count = 16]
Data Element: Sequence uint16 1475 bytes
0011 0... = Data Element Type: Sequence (6)
.... .110 = Data Element Size: uint16 (6)
Data Element Var Size: 1475
Data Value
Attribute 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; // 那么是“临时配对”,
else
pairing_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.java
private int connectEnabledProfiles(BluetoothDevice device) {
ParcelUuid[] remoteDeviceUuids = getRemoteUuids(device); // 会先获取 该设备的 uuid 信息。 该信息是在 SDP 阶段 上报上来的。
ParcelUuid[] localDeviceUuids = mAdapterProperties.getUuids();
// 如果 远端和 本地 都支持 a2dp 那么就连接 a2dp
if (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.java
ParcelUuid[] 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: // 0x03
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;
...
}
}
}
}
}
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
配合写入持久化文件,确保设备从被发现 → 服务识别 → 配对 → 连接全过程中,系统始终拥有最新、稳定、可持续利用的设备状态数据。