前面我們提到了 藍牙協議棧中的 Properties , 這篇文章是 他的補充。
- 【android bluetooth 框架分析 04】【bt-framework 層詳解 6】【Properties介紹】
1. 設計初衷與核心問題
1. 為什么要設計 DeviceProperties
?
在 Android 藍牙實際使用中,系統需反復處理設備的發現、服務解析、配對、連接等場景。在這些過程中,遠程設備的信息管理混亂、數據缺失、不一致是普遍存在的問題。
2. DeviceProperties
解決的問題:
場景 | 待解決問題 | 設計目標 |
---|---|---|
搜索 | 同一設備多次出現在列表中,名稱等信息丟失 | 唯一標識設備、統一管理搜索信息 |
SDP | 每次連接都重新做服務發現,耗時、重復 | 緩存 UUID,提高連接效率 |
配對 | 配對狀態混亂、無法判斷安全能力 | 緩存密鑰與能力,便于復用 |
連接 | 無法快速判斷設備是否支持某 profile | 統一緩存 profile 能力與狀態 |
因此,AOSP 中通過 DeviceProperties
實現了一個 以 MAC 地址為主鍵的遠程設備狀態緩存中心,并與 StorageModule
聯動實現持久化。
2. DeviceProperties 模塊設計概述
1. 核心職責
功能 | 說明 |
---|---|
緩存設備屬性 | 設備名稱、類型、Class of Device、UUID、RSSI、Bond 狀態、安全能力、Link Key 等 |
提供統一讀寫接口 | 供 btif 層、profile 層、JNI 層查詢與設置設備狀態 |
與 StorageModule 協作持久化 | 關鍵屬性寫入 bt_config.conf 配置文件,保證系統重啟后信息不丟失 |
2. 數據存儲結構
每個遠程設備(用 address 唯一標識)對應一個 DeviceProperties
實例,核心字段如:
android/app/src/com/android/bluetooth/btservice/RemoteDevices.javaclass DeviceProperties {private String mName;private byte[] mAddress;private String mIdentityAddress;private boolean mIsConsolidated = false;private int mBluetoothClass = BluetoothClass.Device.Major.UNCATEGORIZED;private int mBredrConnectionHandle = BluetoothDevice.ERROR;private int mLeConnectionHandle = BluetoothDevice.ERROR;private short mRssi;private String mAlias;private BluetoothDevice mDevice;private boolean mIsBondingInitiatedLocally;private int mBatteryLevelFromHfp = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;private int mBatteryLevelFromBatteryService = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;private boolean mIsCoordinatedSetMember;private int mAshaCapability;private int mAshaTruncatedHiSyncId;private String mModelName;@VisibleForTesting int mBondState;@VisibleForTesting int mDeviceType;@VisibleForTesting ParcelUuid[] mUuids;private BluetoothSinkAudioPolicy mAudioPolicy;...
}
1. 創建 DeviceProperties 對象
創建 DeviceProperties 的地方:
android/app/src/com/android/bluetooth/btservice/RemoteDevices.javaDeviceProperties addDeviceProperties(byte[] address) {synchronized (mDevices) {DeviceProperties prop = new DeviceProperties(); // 1. 創建 DeviceProperties 對象prop.setDevice(sAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)));prop.setAddress(address);String key = Utils.getAddressStringFromByte(address); // 2.key 是 mac 地址DeviceProperties pv = mDevices.put(key, prop); // 保存在 RemoteDevices.mDevices 中...return prop;}}
java 層在如下場景中,會調用 addDeviceProperties 創建一個 DeviceProperties 對象:
- AdapterProperties.adapterPropertyChangedCallback:BT_PROPERTY_ADAPTER_BONDED_DEVICES
- 在打開藍牙時, AdapterProperties 會收到 BT_PROPERTY_ADAPTER_BONDED_DEVICES 事件;此時會將之前 已經配對的設備 封裝為一個個 DeviceProperties 對象。
- BondStateMachine.sspRequestCallback
- 設備配對時支持 SSP 模式,進行確認、比較、輸入密鑰等操作時觸發
- BondStateMachine.pinRequestCallback
- 當連接傳統藍牙設備(BR/EDR)時需要輸入 PIN 碼進行配對時觸發
- RemoteDevices.devicePropertyChangedCallback
- 當 設備 屬性發生變化時, 從 native -> java 上報設備信息時,如果找不到對應設備的 Property 將新建一個。
2. 管理那些屬性:
在 【android bluetooth 框架分析 04】【bt-framework 層詳解 6】【Properties介紹】 中有詳細介紹。
枚舉常量 | 說明 | 使用范圍 | 數據類型 | 訪問權限 |
---|---|---|---|---|
🔁 適用于 Adapter 和 Remote Device | ||||
BT_PROPERTY_BDNAME | 設備名稱 | Adapter: 讀/寫Remote Device: 只讀 | bt_bdname_t | GET / SET(Adapter)GET(Remote) |
BT_PROPERTY_BDADDR | 設備地址 | Adapter & Remote Device | RawAddress | GET |
BT_PROPERTY_UUIDS | 支持的服務 UUID 列表 | Remote Device | bluetooth::Uuid[] | GET |
BT_PROPERTY_CLASS_OF_DEVICE | 類別碼 | Remote Device | uint32_t | GET |
BT_PROPERTY_TYPE_OF_DEVICE | 設備類型(BR/EDR/LE) | Remote Device | bt_device_type_t | GET |
BT_PROPERTY_SERVICE_RECORD | 服務記錄 | Remote Device | bt_service_record_t | GET |
枚舉常量 | 說明 | 使用范圍 | 數據類型 | 訪問權限 |
---|---|---|---|---|
📡 僅適用于 Remote Device(遠程設備) | ||||
BT_PROPERTY_REMOTE_FRIENDLY_NAME | 遠程設備名稱(用戶設定) | Remote Device | bt_bdname_t | GET / SET |
BT_PROPERTY_REMOTE_RSSI | 遠程設備 RSSI | Remote Device | int8_t | GET |
BT_PROPERTY_REMOTE_VERSION_INFO | 遠程設備協議版本信息 | Remote Device | bt_remote_version_t | GET / SET |
BT_PROPERTY_REMOTE_IS_COORDINATED_SET_MEMBER | 是否是協同設備成員 | Remote Device | bool | GET |
BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP | 屬性刷新時間戳 | Remote Device | int64_t (或自定義) | GET |
3. native -> java callback
在 搜索、 配對 、 sdp 的過程中,native 在不同階段都會觸發 回調 到 java 層,來更新 DeviceProperties .
協議棧會通過 下面兩個函數來, 層層 上報 屬性到 java 層:
- invoke_device_found_cb
- invoke_remote_device_properties_cb
接下來我們梳理一下 他們的調用邏輯。
1. invoke_device_found_cb & invoke_remote_device_properties_cb
// system/btif/src/bluetooth.ccvoid invoke_device_found_cb(int num_properties, bt_property_t* properties) {do_in_jni_thread(FROM_HERE,base::BindOnce([](int num_properties, bt_property_t* properties) {HAL_CBACK(bt_hal_cbacks, device_found_cb,num_properties, properties); // 調用 jni 函數if (properties) {osi_free(properties);}},num_properties,property_deep_copy_array(num_properties, properties)));
}void invoke_remote_device_properties_cb(bt_status_t status, RawAddress bd_addr,int num_properties,bt_property_t* properties) {do_in_jni_thread(FROM_HERE, base::BindOnce([](bt_status_t status, RawAddress bd_addr,int num_properties, bt_property_t* properties) {HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb,status, &bd_addr, num_properties, properties); // 調用 jni 函數if (properties) {osi_free(properties);}},status, bd_addr, num_properties,property_deep_copy_array(num_properties, properties)));
}
// android/app/jni/com_android_bluetooth_btservice_AdapterService.cpptypedef struct {...remote_device_properties_callback remote_device_properties_cb;device_found_callback device_found_cb;...
} bt_callbacks_t;static bt_callbacks_t sBluetoothCallbacks = {sizeof(sBluetoothCallbacks),adapter_state_change_callback,adapter_properties_callback,remote_device_properties_callback,device_found_callback,...};
- HAL_CBACK(bt_hal_cbacks, device_found_cb, num_properties, properties);
- 調用的就是 om_android_bluetooth_btservice_AdapterService.cpp::device_found_callback
- HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, status, &bd_addr, num_properties, properties);
- 調用的就是 om_android_bluetooth_btservice_AdapterService.cpp::remote_device_properties_callback
// android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static void device_found_callback(int num_properties,bt_property_t* properties) {CallbackEnv sCallbackEnv(__func__);if (!sCallbackEnv.valid()) return;ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), NULL);int addr_index;for (int i = 0; i < num_properties; i++) {if (properties[i].type == BT_PROPERTY_BDADDR) {addr.reset(sCallbackEnv->NewByteArray(properties[i].len));if (!addr.get()) {ALOGE("Address is NULL (unable to allocate) in %s", __func__);return;}sCallbackEnv->SetByteArrayRegion(addr.get(), 0, properties[i].len,(jbyte*)properties[i].val);addr_index = i;}}if (!addr.get()) {ALOGE("Address is NULL in %s", __func__);return;}ALOGV("%s: Properties: %d, Address: %s", __func__, num_properties,(const char*)properties[addr_index].val);remote_device_properties_callback(BT_STATUS_SUCCESS,(RawAddress*)properties[addr_index].val,num_properties, properties); // 1. sCallbackEnv->CallVoidMethod(sJniCallbacksObj, method_deviceFoundCallback,addr.get()); // 回調到 java 層
}
// android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static void remote_device_properties_callback(bt_status_t status,RawAddress* bd_addr,int num_properties,bt_property_t* properties) {CallbackEnv sCallbackEnv(__func__);if (!sCallbackEnv.valid()) return;ALOGV("%s: Status is: %d, Properties: %d", __func__, status, num_properties);if (status != BT_STATUS_SUCCESS) {ALOGE("%s: Status %d is incorrect", __func__, status);return;}ScopedLocalRef<jbyteArray> val(sCallbackEnv.get(),(jbyteArray)sCallbackEnv->NewByteArray(num_properties));if (!val.get()) {ALOGE("%s: Error allocating byteArray", __func__);return;}ScopedLocalRef<jclass> mclass(sCallbackEnv.get(),sCallbackEnv->GetObjectClass(val.get()));/* Initialize the jobjectArray and jintArray here itself and send theinitialized array pointers alone to get_properties */ScopedLocalRef<jobjectArray> props(sCallbackEnv.get(),sCallbackEnv->NewObjectArray(num_properties, mclass.get(), NULL));if (!props.get()) {ALOGE("%s: Error allocating object Array for properties", __func__);return;}ScopedLocalRef<jintArray> types(sCallbackEnv.get(), (jintArray)sCallbackEnv->NewIntArray(num_properties));if (!types.get()) {ALOGE("%s: Error allocating int Array for values", __func__);return;}ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));if (!addr.get()) {ALOGE("Error while allocation byte array in %s", __func__);return;}sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),(jbyte*)bd_addr);jintArray typesPtr = types.get();jobjectArray propsPtr = props.get();if (get_properties(num_properties, properties, &typesPtr, &propsPtr) < 0) {return;}sCallbackEnv->CallVoidMethod(sJniCallbacksObj,method_devicePropertyChangedCallback, addr.get(),types.get(), props.get()); // 回調到 java 層
}
// android/app/jni/com_android_bluetooth_btservice_AdapterService.cppmethod_devicePropertyChangedCallback = env->GetMethodID(jniCallbackClass, "devicePropertyChangedCallback", "([B[I[[B)V");method_deviceFoundCallback =env->GetMethodID(jniCallbackClass, "deviceFoundCallback", "([B)V");
// android/app/src/com/android/bluetooth/btservice/JniCallbacks.javavoid devicePropertyChangedCallback(byte[] address, int[] types, byte[][] val) {mRemoteDevices.devicePropertyChangedCallback(address, types, val);}void deviceFoundCallback(byte[] address) {mRemoteDevices.deviceFoundCallback(address);}
2. RemoteDevices.devicePropertyChangedCallback
是 Java 層對 native 層 method_devicePropertyChangedCallback
的響應回調,用于更新本地記錄的遠程藍牙設備屬性。
// android/app/src/com/android/bluetooth/btservice/RemoteDevices.java/*address:遠程設備的 MAC 地址(byte[] 格式)types:屬性類型數組(int 值,參照 AbstractionLayer.BT_PROPERTY_* 常量定義)values:每個屬性對應的值(數組形式,一一對應)*/void devicePropertyChangedCallback(byte[] address, int[] types, byte[][] values) {Intent intent;byte[] val;int type;BluetoothDevice bdDevice = getDevice(address);DeviceProperties deviceProperties;/*如果是第一次收到該地址設備的屬性變更,說明是新設備,需添加。DeviceProperties 是系統對一個遠程設備的本地屬性封裝類。*/if (bdDevice == null) {debugLog("Added new device property");deviceProperties = addDeviceProperties(address); // 創建新的 DevicePropertiesbdDevice = getDevice(address);} else {deviceProperties = getDeviceProperties(bdDevice); // 再次獲取 BluetoothDevice 實例}// 無屬性則退出。if (types.length <= 0) {errorLog("No properties to update");return;}// 遍歷所有變更的屬性for (int j = 0; j < types.length; j++) {type = types[j];val = values[j];if (val.length > 0) {synchronized (mObject) { // 同步鎖:避免并發問題infoLog("Property type: " + type);// 根據屬性類型更新具體字段switch (type) {case AbstractionLayer.BT_PROPERTY_BDNAME: // 設備名稱final String newName = new String(val);if (newName.equals(deviceProperties.getName())) {infoLog("Skip name update for " + bdDevice);break;}deviceProperties.setName(newName);// 廣播設備名稱改變事件intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED);intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice);intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProperties.getName());intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT,Utils.getTempAllowlistBroadcastOptions());infoLog("Remote Device name is: " + deviceProperties.getName());break;case AbstractionLayer.BT_PROPERTY_REMOTE_FRIENDLY_NAME: // 用戶自定義名稱(Alias)deviceProperties.setAlias(bdDevice, new String(val));infoLog("Remote device alias is: " + deviceProperties.getAlias());break;case AbstractionLayer.BT_PROPERTY_BDADDR: // 設備地址deviceProperties.setAddress(val);infoLog("Remote Address is:" + Utils.getAddressStringFromByte(val));break;// 設備類型標識(例如:手機、耳機)case AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE: final int newClass = Utils.byteArrayToInt(val);if (newClass == deviceProperties.getBluetoothClass()) {infoLog("Skip class update for " + bdDevice);break;}deviceProperties.setBluetoothClass(newClass);// 廣播 class 改變事件intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED);intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice);intent.putExtra(BluetoothDevice.EXTRA_CLASS,new BluetoothClass(deviceProperties.getBluetoothClass()));intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT,Utils.getTempAllowlistBroadcastOptions());infoLog("Remote class is:" + newClass);break;case AbstractionLayer.BT_PROPERTY_UUIDS: // 支持的 Profile UUID , SDP 發現結束后會通過此字段更新支持的 Profile,如 A2DP、HFP。int numUuids = val.length / AbstractionLayer.BT_UUID_SIZE;final ParcelUuid[] newUuids = Utils.byteArrayToUuid(val);//ParcelUuid[] uuids = updateUuids(deviceProperties.mUuids, newUuids);if (areUuidsEqual(newUuids, deviceProperties.mUuids)) {infoLog( "Skip uuids update for " + bdDevice.getAddress());break;}deviceProperties.mUuids = newUuids;if (sAdapterService.getState() == BluetoothAdapter.STATE_ON) {sAdapterService.deviceUuidUpdated(bdDevice);sendUuidIntent(bdDevice, deviceProperties);} else if (sAdapterService.getState()== BluetoothAdapter.STATE_BLE_ON) {sAdapterService.deviceUuidUpdated(bdDevice);}break;case AbstractionLayer.BT_PROPERTY_TYPE_OF_DEVICE:// 設備連接類型(BR/EDR, LE, Dual)if (deviceProperties.isConsolidated()) {break;}// The device type from hal layer, defined in bluetooth.h,// matches the type defined in BluetoothDevice.javadeviceProperties.setDeviceType(Utils.byteArrayToInt(val));break;case AbstractionLayer.BT_PROPERTY_REMOTE_RSSI: // 信號強度// RSSI from hal is in one bytedeviceProperties.setRssi(val[0]);break;case AbstractionLayer.BT_PROPERTY_REMOTE_IS_COORDINATED_SET_MEMBER:// 是否屬于 Coordinated SetdeviceProperties.setIsCoordinatedSetMember(val[0] != 0);break;}}}}}
功能點 | 說明 |
---|---|
📥 入口 | native 層通知 Java 層遠程設備屬性有更新(如名稱、class、UUID、RSSI) |
🗃? 存儲 | 更新本地 Java 層緩存(DeviceProperties 對象) |
📢 廣播 | 對關鍵屬性(名稱、class、UUID)變化發送系統廣播 |
💾 保存 | 最終可能通過 StorageModule 寫入 bt_config.conf (如 UUID、Alias、Class) |
🧩 用途 | 支持 UI 展示、連接判斷、profile 支持判斷等 |
3. RemoteDevices.deviceFoundCallback
此函數是在設備被掃描到時由 native 層調用 Java 層,屬于藍牙設備發現流程的重要組成部分。
當藍牙發現流程(Inquiry 或 LE Scan)中發現了一個新設備或再次發現舊設備時,會觸發此回調。它的職責是:
-
獲取設備信息
-
根據系統配置和策略決定是否廣播設備發現
-
通過
ACTION_FOUND
廣播通知系統和應用
// android/app/src/com/android/bluetooth/btservice/RemoteDevices.java// Native 層通過 JNI 調用 Java 層,傳入遠程設備的地址(6 字節 MAC 地址)。void deviceFoundCallback(byte[] address) {// The device properties are already registered - we can send the intent// now 根據 MAC 地址獲取或創建 BluetoothDevice 對象。BluetoothDevice device = getDevice(address);infoLog("deviceFoundCallback: Remote Address is:" + device);// 獲取設備的本地屬性封裝對象(DeviceProperties),包含設備名稱、class、RSSI 等。DeviceProperties deviceProp = getDeviceProperties(device);if (deviceProp == null) {// 如果屬性為空(很罕見,可能是同步未完成),直接返回。errorLog("Device Properties is null for Device:" + device);return;}// 檢查是否開啟“限制無名稱設備廣播”策略boolean restrict_device_found =SystemProperties.getBoolean("bluetooth.restrict_discovered_device.enabled", false);if (restrict_device_found && (deviceProp.mName == null || deviceProp.mName.isEmpty())) {// 讀取系統屬性,如果為 true,表示系統不希望廣播沒有名字的設備(可用于節能或隱私控制)// 如果設備沒有名字,并且限制策略開啟,則不廣播此設備。debugLog("Device name is null or empty: " + device);return;}/*應用層級過濾(如阻止某些設備類型)filterDevice() 是系統或廠商定制的設備過濾邏輯(如過濾黑名單、特殊廠商設備等)。若返回 true,跳過此設備廣播。*/if (filterDevice(device)) {warnLog("Not broadcast Device: " + device);return;}infoLog("device:" + device + " adapterIndex=" + device.getAdapterIndex());// 創建用于通知發現設備的廣播事件。Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); // 附加設備對象本身。intent.putExtra(BluetoothDevice.EXTRA_CLASS,new BluetoothClass(deviceProp.getBluetoothClass())); // 附加設備類型(如手機、耳機、電腦等)intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.getRssi()); // 附加設備的信號強度。intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.getName()); // 附加設備名稱。intent.putExtra(BluetoothDevice.EXTRA_IS_COORDINATED_SET_MEMBER,deviceProp.isCoordinatedSetMember()); // 附加是否屬于 Coordinated Set(藍牙 5.2+ 中用于群組播放,如 TWS 左右耳同時控制)。/*廣播發出發出帶權限限制的廣播,只有持有 BLUETOOTH_SCAN 權限的應用可以接收該廣播。使用 sendBroadcastMultiplePermissions() 是對 sendBroadcast() 的擴展,支持多權限、支持臨時廣播策略(如廣播延遲/前臺優先級等)。*/sAdapterService.sendBroadcastMultiplePermissions(intent,new String[] { BLUETOOTH_SCAN },Utils.getTempBroadcastOptions());}
階段 | 描述 |
---|---|
1?? 獲取 | 根據 address 獲取 BluetoothDevice 和 DeviceProperties |
2?? 檢查 | 是否開啟了過濾策略(無名稱設備、特定設備過濾) |
3?? 構建 | 創建廣播 intent 并附加設備屬性 |
4?? 廣播 | 向系統發送 ACTION_FOUND 廣播,僅供授權應用接收 |
應用場景:
- 當用戶在設置界面打開藍牙并點擊“掃描設備”時,后臺會多次觸發 deviceFoundCallback()。
- App 中注冊了 ACTION_FOUND 廣播接收器后,可以接收到附近設備并展示在列表上。
4. 小結
協議棧 native 側,會觸發上面的兩路 回調, 但是他們 所代表 的含義卻是不同的:
- invoke_device_found_cb[native] -> RemoteDevices.deviceFoundCallback[java]
- invoke_remote_device_properties_cb[native] -> RemoteDevices.devicePropertyChangedCallback[java]
devicePropertyChangedCallback
和 deviceFoundCallback
是 AOSP 藍牙框架中兩個核心的回調函數,雖然它們都與設備屬性和發現有關,但它們在觸發時機、作用、廣播內容、應用場景等方面都有明顯差異。
下面從多個維度對 相同點與不同點 進行詳細對比:
1.相同點
維度 | 描述 |
---|---|
🔧 來源 | 都是由 native 層(通過 JNI)調用 Java 層的回調函數 |
📡 與設備相關 | 都涉及對某個 BluetoothDevice 設備的處理 |
🧠 依賴 DeviceProperties | 都通過 getDeviceProperties(device) 獲取設備緩存屬性 |
🔒 權限控制 | 廣播時都依賴藍牙相關權限(如 BLUETOOTH_SCAN ) |
📲 可導致廣播 | 都有可能向上層發送 Android 廣播(如 ACTION_FOUND , ACTION_NAME_CHANGED , ACTION_UUID , 等) |
🧪 開發調試中常出現 | 都會在使用藍牙調試(如配對、掃描)過程中頻繁觸發 |
🧩 與應用層交互 | 都可能引發第三方 app 的回調(通過廣播接收器) |
2.不同點
比較維度 | deviceFoundCallback | devicePropertyChangedCallback |
---|---|---|
💥 觸發時機 | 當藍牙掃描發現設備時調用(第一次或再次發現) | 當遠程設備的屬性發生變化時調用(如名稱、RSSI、UUID 等) |
🔁 調用頻率 | 在一次掃描過程中可能多次觸發(每個設備發現一次) | 屬性每變化一次觸發一次,可能頻繁(如 RSSI 不斷變化) |
📩 廣播行為 | 廣播 BluetoothDevice.ACTION_FOUND (設備被發現) | 根據屬性類型廣播不同事件,如:
|
🔍 目的 | 表示“新設備”被發現,通知系統和應用顯示 | 表示“已知設備的屬性”發生變化,更新狀態或 UI |
🧬 廣播攜帶信息 | BluetoothDevice 、設備類型、名稱、RSSI、是否為群組成員 | 取決于變化的屬性(可能是 UUID、名稱、RSSI) |
🧰 過濾策略參與 | 參與“是否廣播”策略(如名稱為空不廣播) | 不參與過濾,始終處理屬性變化 |
📲 典型場景 | 藍牙設置頁中設備掃描列表展示 | 已配對設備列表中設備名稱、信號變化,或配對時獲取 UUID |
💡 是否依賴掃描流程 | 是,僅在藍牙掃描流程中調用 | 否,也可能在連接、配對、服務發現后調用 |
3.實際生活中的類比
情況 | deviceFoundCallback | devicePropertyChangedCallback |
---|---|---|
你在商場里發現一個新品牌店鋪 | 店鋪出現在你面前的那一刻 —— “發現設備” | 店鋪換了名字、裝修風格變了、換老板了、上了新的商品 —— “屬性改變” |
手機藍牙設置中掃描時發現設備列表刷新 | 每個設備出現一次觸發一次 | 某設備名稱更新或信號強度變化,刷新其展示項 |
4.應用開發建議
目標 | 使用哪個回調 |
---|---|
想監聽設備是否被發現(用于展示設備列表) | BluetoothDevice.ACTION_FOUND 廣播(源自 deviceFoundCallback ) |
想監聽某設備名稱是否變更(如設備重命名) | BluetoothDevice.ACTION_NAME_CHANGED (源自 devicePropertyChangedCallback ) |
想獲取設備的 UUID(服務)更新 | BluetoothDevice.ACTION_UUID (源自 devicePropertyChangedCallback ) |
想實時顯示設備信號強度(如附近藍牙設備距離) | BluetoothDevice.EXTRA_RSSI (由 devicePropertyChangedCallback 中 RSSI 更新引發) |
3. 系統接口
關于 DevicePropertyNative 有如下幾個接口:
- android/app/src/com/android/bluetooth/btservice/AdapterService.java
/*package*/native boolean setDevicePropertyNative(byte[] address, int type, byte[] val);/*package*/native boolean getDevicePropertyNative(byte[] address, int type);
1. Get 流程
getDevicePropertyNative
android/app/src/com/android/bluetooth/btservice/AdapterService.java/*package*/native boolean getDevicePropertyNative(byte[] address, int type);
// android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static jboolean getDevicePropertyNative(JNIEnv* env, jobject obj,jbyteArray address, jint type) {ALOGV("%s", __func__);if (!sBluetoothInterface) return JNI_FALSE;jbyte* addr = env->GetByteArrayElements(address, NULL);if (addr == NULL) {jniThrowIOException(env, EINVAL);return JNI_FALSE;}int ret = sBluetoothInterface->get_remote_device_property((RawAddress*)addr, (bt_property_type_t)type);env->ReleaseByteArrayElements(address, addr, 0);return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
system/btif/src/bluetooth.ccint get_remote_device_property(RawAddress* remote_addr,bt_property_type_t type) {if (!btif_is_enabled()) return BT_STATUS_NOT_READY;do_in_main_thread(FROM_HERE, base::BindOnce(btif_get_remote_device_property,*remote_addr, type));return BT_STATUS_SUCCESS;
}
system/btif/src/btif_core.cc/********************************************************************************* Function btif_get_remote_device_property** Description Fetches the remote device property from the NVRAM*******************************************************************************/
void btif_get_remote_device_property(RawAddress remote_addr,bt_property_type_t type) {char buf[1024];bt_property_t prop;prop.type = type;prop.val = (void*)buf;prop.len = sizeof(buf);bt_status_t status =btif_storage_get_remote_device_property(&remote_addr, &prop);invoke_remote_device_properties_cb(status, remote_addr, 1, &prop); // 1.
}
invoke_remote_device_properties_cb
// system/btif/src/btif_core.cc
void btif_remote_properties_evt(bt_status_t status, RawAddress* remote_addr,uint32_t num_props, bt_property_t* p_props) {invoke_remote_device_properties_cb(status, *remote_addr, num_props, p_props);
}
2. 寫配置
btif_storage_add_remote_device
system/btif/src/btif_storage.ccbt_status_t btif_storage_add_remote_device(const RawAddress* remote_bd_addr,uint32_t num_properties,bt_property_t* properties) {uint32_t i = 0;/* TODO: If writing a property, fails do we go back undo the earlier* written properties? */for (i = 0; i < num_properties; i++) {/* Ignore the RSSI as this is not stored in DB */if (properties[i].type == BT_PROPERTY_REMOTE_RSSI) continue;/* address for remote device needs special handling as we also store* timestamp */if (properties[i].type == BT_PROPERTY_BDADDR) {bt_property_t addr_prop;memcpy(&addr_prop, &properties[i], sizeof(bt_property_t));addr_prop.type = (bt_property_type_t)BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP;btif_storage_set_remote_device_property(remote_bd_addr, &addr_prop); // 1. } else {btif_storage_set_remote_device_property(remote_bd_addr, &properties[i]); // 2. }}return BT_STATUS_SUCCESS;
}
btif_storage_set_remote_device_property
// system/btif/src/btif_storage.cc
bt_status_t btif_storage_set_remote_device_property(const RawAddress* remote_bd_addr, bt_property_t* property) {return prop2cfg(remote_bd_addr, property) ? BT_STATUS_SUCCESS: BT_STATUS_FAIL;
}
prop2cfg
static int prop2cfg(const RawAddress* remote_bd_addr, bt_property_t* prop) {std::string bdstr;if (remote_bd_addr) {bdstr = remote_bd_addr->ToString();}char value[1024];if (prop->len <= 0 || prop->len > (int)sizeof(value) - 1) {LOG_WARN("Unable to save property to configuration file type:%d, "" len:%d is invalid",prop->type, prop->len);return false;}switch (prop->type) {case BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP:btif_config_set_int(bdstr, BTIF_STORAGE_PATH_REMOTE_DEVTIME,(int)time(NULL));break;case BT_PROPERTY_BDNAME: {int name_length = prop->len > BTM_MAX_LOC_BD_NAME_LEN? BTM_MAX_LOC_BD_NAME_LEN: prop->len;strncpy(value, (char*)prop->val, name_length);value[name_length] = '\0';if (remote_bd_addr) {btif_config_set_str(bdstr, BTIF_STORAGE_PATH_REMOTE_NAME, value);} else {btif_config_set_str("Adapter", BTIF_STORAGE_KEY_ADAPTER_NAME, value);btif_config_flush();}break;}case BT_PROPERTY_REMOTE_FRIENDLY_NAME:strncpy(value, (char*)prop->val, prop->len);value[prop->len] = '\0';btif_config_set_str(bdstr, BTIF_STORAGE_PATH_REMOTE_ALIASE, value);break;case BT_PROPERTY_ADAPTER_SCAN_MODE:btif_config_set_int("Adapter", BTIF_STORAGE_KEY_ADAPTER_SCANMODE,*(int*)prop->val);break;case BT_PROPERTY_LOCAL_IO_CAPS:btif_config_set_int("Adapter", BTIF_STORAGE_KEY_LOCAL_IO_CAPS,*(int*)prop->val);break;case BT_PROPERTY_LOCAL_IO_CAPS_BLE:btif_config_set_int("Adapter", BTIF_STORAGE_KEY_LOCAL_IO_CAPS_BLE,*(int*)prop->val);break;case BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT:btif_config_set_int("Adapter", BTIF_STORAGE_KEY_ADAPTER_DISC_TIMEOUT,*(int*)prop->val);break;case BT_PROPERTY_CLASS_OF_DEVICE:btif_config_set_int(bdstr, BTIF_STORAGE_PATH_REMOTE_DEVCLASS,*(int*)prop->val);break;case BT_PROPERTY_TYPE_OF_DEVICE:btif_config_set_int(bdstr, BTIF_STORAGE_PATH_REMOTE_DEVTYPE,*(int*)prop->val);break;case BT_PROPERTY_UUIDS: {std::string val;size_t cnt = (prop->len) / sizeof(Uuid);for (size_t i = 0; i < cnt; i++) {val += (reinterpret_cast<Uuid*>(prop->val) + i)->ToString() + " ";}btif_config_set_str(bdstr, BTIF_STORAGE_PATH_REMOTE_SERVICE, val);break;}case BT_PROPERTY_REMOTE_VERSION_INFO: {bt_remote_version_t* info = (bt_remote_version_t*)prop->val;if (!info) return false;btif_config_set_int(bdstr, BT_CONFIG_KEY_REMOTE_VER_MFCT,info->manufacturer);btif_config_set_int(bdstr, BT_CONFIG_KEY_REMOTE_VER_VER, info->version);btif_config_set_int(bdstr, BT_CONFIG_KEY_REMOTE_VER_SUBVER,info->sub_ver);} break;default:BTIF_TRACE_ERROR("Unknown prop type:%d", prop->type);return false;}/* No need to look for bonded device with address of NULL */if (remote_bd_addr &&btif_in_fetch_bonded_device(bdstr) == BT_STATUS_SUCCESS) {/* save changes if the device was bonded */btif_config_flush();}return true;
}
1. btif_config_set_str 和 btif_config_set_int
// system/btif/src/btif_config.cc
bool btif_config_set_int(const std::string& section, const std::string& key,int value) {CHECK(bluetooth::shim::is_gd_stack_started_up());return bluetooth::shim::BtifConfigInterface::SetInt(section, key, value);
}// system/main/shim/config.cc
bool BtifConfigInterface::SetInt(const std::string& section,const std::string& property, int value) {ConfigCacheHelper::FromConfigCache(*GetStorage()->GetConfigCache()).SetInt(section, property, value);return true;
}// system/gd/storage/config_cache_helper.cc
void ConfigCacheHelper::SetInt(const std::string& section, const std::string& property, int value) {config_cache_.SetProperty(section, property, std::to_string(value));
}
2. ConfigCache::SetProperty
功能:
- 設置一個配置項(如藍牙設備屬性或通用配置),并根據是否為設備屬性決定是否進入持久化配置或臨時配置緩存中。
// system/gd/storage/config_cache.ccvoid ConfigCache::SetProperty(std::string section, std::string property, std::string value) {/*使用遞歸互斥鎖保護對 information_sections_、persistent_devices_、temporary_devices_ 等共享數據結構的并發訪問。防止多線程同時讀寫 config。*/std::lock_guard<std::recursive_mutex> lock(mutex_);// 移除傳入字符串中可能存在的 \n 或 \r,防止注入或破壞配置格式。TrimAfterNewLine(section);TrimAfterNewLine(property);TrimAfterNewLine(value);// section 和 property 名不能為空,否則斷言失敗(開發期調試用)ASSERT_LOG(!section.empty(), "Empty section name not allowed");ASSERT_LOG(!property.empty(), "Empty property name not allowed");/* 判斷是否為“設備節一般如 [Adapter], [Metrics], [Global] 屬于非設備配置節;而類似 [Device_XX:XX:XX:XX:XX:XX] 才是設備配置節。*/if (!IsDeviceSection(section)) {/*存入 information_sections_(非設備類配置)如果該節尚不存在,則創建;將 property -> value 插入;通知配置已更改。適用于如 [Adapter] 節下的 Name、ScanMode 等普通配置項。*/auto section_iter = information_sections_.find(section);if (section_iter == information_sections_.end()) {section_iter = information_sections_.try_emplace_back(section, common::ListMap<std::string, std::string>{}).first;}section_iter->second.insert_or_assign(property, std::move(value));PersistentConfigChangedCallback();return;}/*如果是設備配置節(Device_...),檢查是否可持久化保存如果該設備還不在 persistent_devices_ 中,且當前 property 是可持久化的(如 LinkKey),嘗試從臨時設備中遷移。只有配對時,才有可能將 設備 從臨時設備列表中, 移到 可持久化列表里。*/auto section_iter = persistent_devices_.find(section);if (section_iter == persistent_devices_.end() && IsPersistentProperty(property)) {// move paired devices or create new paired device when a link key is set/*從 temporary_devices_ 遷移或新建一項若該設備的屬性存在于臨時設備列表中,則將其“轉正”遷入 persistent;否則新建。場景舉例:連接配對時第一次保存 link key,從臨時狀態遷移為持久配對狀態。*/auto section_properties = temporary_devices_.extract(section);if (section_properties) {section_iter = persistent_devices_.try_emplace_back(section, std::move(section_properties->second)).first;} else {section_iter = persistent_devices_.try_emplace_back(section, common::ListMap<std::string, std::string>{}).first;}}/*安全模式下加密敏感屬性值如果開啟了安全模式(如 CC Mode)并且屬性為敏感字段(如 LinkKey、LE_KEY_PENC 等):通過 KeyStore 接口嘗試加密存儲;若成功,則設置為標記字符串 value = "$encrypted" 表示已加密。
*/if (section_iter != persistent_devices_.end()) {bool is_encrypted = value == kEncryptedStr;if ((!value.empty()) && os::ParameterProvider::GetBtKeystoreInterface() != nullptr &&os::ParameterProvider::IsCommonCriteriaMode() && InEncryptKeyNameList(property) && !is_encrypted) {if (os::ParameterProvider::GetBtKeystoreInterface()->set_encrypt_key_or_remove_key(section + "-" + property, value)) {value = kEncryptedStr;}}/*插入持久化設備屬性 + 通知更改將處理后的值插入設備節中;通知持久化更改(通常觸發異步寫入 config 文件)。
*/section_iter->second.insert_or_assign(property, std::move(value));PersistentConfigChangedCallback();return;}/*如果該設備節仍不存在,寫入 temporary_devices_表示當前屬性不需要持久化;保存到 temporary_devices_ 中(通常用于會話屬性、未配對設備等 );掃描到的設備都一般存儲在這里*/section_iter = temporary_devices_.find(section);if (section_iter == temporary_devices_.end()) {auto triple = temporary_devices_.try_emplace(section, common::ListMap<std::string, std::string>{});section_iter = std::get<0>(triple);}section_iter->second.insert_or_assign(property, std::move(value));
}
在搜素時,沒有配對的設備 都將保存在 臨時 設備列表里面。
只有 在配對時,收到 設備的 LinkKey 時, 才會從臨時設備 列表,挪到 可持久設備列表里面。
/*如果是設備配置節(Device_...),檢查是否可持久化保存如果該設備還不在 persistent_devices_ 中,且當前 property 是可持久化的(如 LinkKey),嘗試從臨時設備中遷移。只有配對時,才有可能將 設備 從臨時設備列表中, 移到 可持久化列表里。*/auto section_iter = persistent_devices_.find(section);if (section_iter == persistent_devices_.end() && IsPersistentProperty(property)) {// move paired devices or create new paired device when a link key is set...}
1. IsPersistentProperty(property)
// system/gd/storage/config_cache.ccbool ConfigCache::IsPersistentProperty(const std::string& property) const {return persistent_property_names_.find(property) != persistent_property_names_.end();
}ConfigCache::ConfigCache(size_t temp_device_capacity, std::unordered_set<std::string_view> persistent_property_names): persistent_property_names_(std::move(persistent_property_names)),information_sections_(),persistent_devices_(),temporary_devices_(temp_device_capacity) {}
- 在 ConfigCache 構造函數里面,會初始化
persistent_property_names_
// system/gd/storage/storage_module.cc
void StorageModule::Start() {...auto config = LegacyConfigFile::FromPath(config_file_path_).Read(temp_devices_capacity_);...
}
// system/gd/storage/legacy_config_file.ccstd::optional<ConfigCache> LegacyConfigFile::Read(size_t temp_devices_capacity) {...ConfigCache cache(temp_devices_capacity, Device::kLinkKeyProperties);...
}
在 StorageModule 模塊初始化時, 將 從 /data/misc/bluedroid/bt_config.conf 讀取內容, 此時會 創建 ConfigCache 對象,此時 persistent_property_names_ = Device::kLinkKeyProperties
4. 在各階段的核心作用
1. 搜索階段(Inquiry / BLE Scan)
1. 作用
-
當 Controller 發現新設備時(classic 或 BLE):
- 沒有就創建并初始化
- 有就更新(如 RSSI、名稱、設備類型)
-
作用于:
- 避免重復設備出現在 UI(去重)
- 通知 Java 層刷新設備列表
2. 解決的問題
問題 | 如何解決 |
---|---|
同一設備出現多次 | 用地址唯一標識緩存 |
設備名稱/類型不一致 | 統一由 DeviceProperties 更新 |
3. BR/EDR 掃描流程鑒賞
1. btif_dm_search_devices_evt
這段代碼是 設備發現流程的核心處理邏輯之一,對應于系統層調用 BluetoothAdapter.startDiscovery()
時產生的設備發現過程中的事件處理。
btif_dm_search_devices_evt()
是 BTIF 層處理 BTA 層 tBTA_DM_xxx_EVT
事件的統一入口:
- 當發現遠程設備時(設備通過 Inquiry 過程或 BLE 掃描被發現),
event == BTA_DM_INQ_RES_EVT
,該事件就會觸發,并進入 case 分支處理。
// system/btif/src/btif_dm.cc
static void btif_dm_search_devices_evt(tBTA_DM_SEARCH_EVT event,tBTA_DM_SEARCH* p_search_data) {BTIF_TRACE_EVENT("%s event=%s", __func__, dump_dm_search_event(event));switch (event) {...case BTA_DM_INQ_RES_EVT: {/* inquiry result */bt_bdname_t bdname;uint8_t remote_name_len;uint8_t num_uuids = 0, num_uuids128 = 0, max_num_uuid = 32;uint8_t uuid_list[32 * Uuid::kNumBytes16];uint8_t uuid_list128[32 * Uuid::kNumBytes128];p_search_data->inq_res.remt_name_not_required =check_eir_remote_name(p_search_data, NULL, NULL);RawAddress& bdaddr = p_search_data->inq_res.bd_addr;// 打印發現設備的地址和類型(BR/EDR、BLE 或 DUMO),有助于調試日志追蹤。BTIF_TRACE_DEBUG("%s() %s device_type = 0x%x\n", __func__,bdaddr.ToString().c_str(),p_search_data->inq_res.device_type);bdname.name[0] = 0;// 優先嘗試從 EIR(Extended Inquiry Response)中解析遠程設備名稱,如果沒有,就嘗試從緩存中找if (!check_eir_remote_name(p_search_data, bdname.name, &remote_name_len))check_cached_remote_name(p_search_data, bdname.name, &remote_name_len);/* Check EIR for services */// 提取設備 EIR 中的 UUID(服務列表)if (p_search_data->inq_res.p_eir) {// 分別提取 16-bit 和 128-bit UUID,用于構建遠程設備所支持的服務,比如 A2DP Sink、HID、PAN 等BTM_GetEirUuidList(p_search_data->inq_res.p_eir,p_search_data->inq_res.eir_len, Uuid::kNumBytes16,&num_uuids, uuid_list, max_num_uuid);BTM_GetEirUuidList(p_search_data->inq_res.p_eir,p_search_data->inq_res.eir_len, Uuid::kNumBytes128,&num_uuids128, uuid_list128, max_num_uuid);}{// 組織設備屬性結構體 bt_property_tbt_property_t properties[7];bt_device_type_t dev_type;uint32_t num_properties = 0;bt_status_t status;tBLE_ADDR_TYPE addr_type = BLE_ADDR_PUBLIC;memset(properties, 0, sizeof(properties));// 下面依次填充不同類型的屬性:/* RawAddress */BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_BDADDR/*設備地址*/, sizeof(bdaddr), &bdaddr);num_properties++;/* BD_NAME *//* Don't send BDNAME if it is empty */if (bdname.name[0]) {BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_BDNAME/*設備名稱(非空時填充)*/,strlen((char*)bdname.name), &bdname);num_properties++;}/* DEV_CLASS */uint32_t cod = devclass2uint(p_search_data->inq_res.dev_class);BTIF_TRACE_DEBUG("%s cod is 0x%06x", __func__, cod);if (cod != 0) {BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_CLASS_OF_DEVICE/*COD 設備類別*/, sizeof(cod),&cod);num_properties++;}/* DEV_TYPE *//* FixMe: Assumption is that bluetooth.h and BTE enums match *//* Verify if the device is dual mode in NVRAM */int stored_device_type = 0;if (btif_get_device_type(bdaddr, &stored_device_type) &&((stored_device_type != BT_DEVICE_TYPE_BREDR &&p_search_data->inq_res.device_type == BT_DEVICE_TYPE_BREDR) ||(stored_device_type != BT_DEVICE_TYPE_BLE &&p_search_data->inq_res.device_type == BT_DEVICE_TYPE_BLE))) {dev_type = (bt_device_type_t)BT_DEVICE_TYPE_DUMO;} else {dev_type = (bt_device_type_t)p_search_data->inq_res.device_type;}if (p_search_data->inq_res.device_type == BT_DEVICE_TYPE_BLE)addr_type = p_search_data->inq_res.ble_addr_type;BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_TYPE_OF_DEVICE/*設備類型(BR/EDR、BLE、DUMO)*/, sizeof(dev_type),&dev_type);num_properties++;/* RSSI */BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_REMOTE_RSSI/*信號強度*/, sizeof(int8_t),&(p_search_data->inq_res.rssi));num_properties++;/* CSIP supported device */BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_REMOTE_IS_COORDINATED_SET_MEMBER/*是否為 Coordinated Set(例如耳機雙邊同步)*/,sizeof(bool),&(p_search_data->inq_res.include_rsi));num_properties++;/* Cache EIR queried services */if ((num_uuids + num_uuids128) > 0) {uint16_t* p_uuid16 = (uint16_t*)uuid_list;auto uuid_iter = eir_uuids_cache.find(bdaddr);Uuid new_remote_uuid[BT_MAX_NUM_UUIDS];size_t dst_max_num = sizeof(new_remote_uuid)/sizeof(Uuid);size_t new_num_uuid = 0;Uuid remote_uuid[BT_MAX_NUM_UUIDS];if (uuid_iter == eir_uuids_cache.end()) {auto triple = eir_uuids_cache.try_emplace(bdaddr, std::set<Uuid>{});uuid_iter = std::get<0>(triple);}//LOG_INFO("EIR UUIDs for %s:", bdaddr.ToString().c_str());for (int i = 0; i < num_uuids; ++i) {Uuid uuid = Uuid::From16Bit(p_uuid16[i]);//LOG_INFO(" %s", uuid.ToString().c_str());uuid_iter->second.insert(uuid);if (i < BT_MAX_NUM_UUIDS) {remote_uuid[i] = uuid;} else {LOG_INFO("%d >= %d", i, BT_MAX_NUM_UUIDS);}}for (int i = 0; i < num_uuids128; ++i) {Uuid uuid = Uuid::From128BitBE((uint8_t *)&uuid_list128[i * Uuid::kNumBytes128]);//LOG_INFO(" %s", uuid.ToString().c_str());uuid_iter->second.insert(uuid);if (i < BT_MAX_NUM_UUIDS) {remote_uuid[num_uuids + i] = uuid;} else {LOG_INFO("%d >= %d", i, BT_MAX_NUM_UUIDS);}}//LOG_INFO("%s %d : update EIR UUIDs.", __func__, __LINE__);new_num_uuid = btif_update_uuid(bdaddr, remote_uuid,(num_uuids + num_uuids128), new_remote_uuid,sizeof(new_remote_uuid),dst_max_num);BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_UUIDS/*支持的服務 UUID 列表*/,new_num_uuid * Uuid::kNumBytes128, new_remote_uuid);//LOG_INFO("%s %d : fill BT_PROPERTY_UUIDS property.", __func__, __LINE__);num_properties ++;}// 存儲到 內存中, 并更新地址類型, 將新發現的設備保存到本地數據庫中,供配對或后續連接使用。status =btif_storage_add_remote_device(&bdaddr, num_properties, properties);ASSERTC(status == BT_STATUS_SUCCESS,"failed to save remote device (inquiry)", status);status = btif_storage_set_remote_addr_type(&bdaddr, addr_type);ASSERTC(status == BT_STATUS_SUCCESS,"failed to save remote addr type (inquiry)", status);// 可選過濾(非 connectable 的 BLE 設備忽略), 防止顯示無法連接的 BLE 設備,例如 Beacon,這對某些廠商有定制用途。bool restrict_report = osi_property_get_bool("bluetooth.restrict_discovered_device.enabled", false);if (restrict_report &&p_search_data->inq_res.device_type == BT_DEVICE_TYPE_BLE &&!(p_search_data->inq_res.ble_evt_type & BTM_BLE_CONNECTABLE_MASK)) {LOG_INFO("%s: Ble device is not connectable",bdaddr.ToString().c_str());break;}/* Callback to notify upper layer of device */invoke_device_found_cb(num_properties, properties); // 通知上層 Java 層 deviceFoundCallback()}} break;}
}
btif_dm_search_devices_evt()
是 AOSP 中實現設備搜索發現的核心邏輯,負責:
-
從 Inquiry/BLE 掃描結果中提取遠程設備信息;
-
嘗試從 EIR 中解析遠程名稱和服務;
-
構建標準的
bt_property_t
屬性數組; -
存儲發現設備并通知 Java 層應用。
而它最終觸發的 deviceFoundCallback()
是應用層感知新設備發現的關鍵入口。
我們再次 devicePropertyChangedCallback vs. deviceFoundCallback 對比:
比較點 | devicePropertyChangedCallback | deviceFoundCallback |
---|---|---|
觸發條件 | 已知設備屬性變化(如名稱變化、RSSI 變化) | 發現新設備(搜索階段) |
回調數據 | 變化的 bt_property_t 屬性 | 初次發現設備的 bt_property_t 全集 |
使用場景 | 設備連接后屬性更新 | 搜索設備時通知發現 |
是否多次觸發 | 是(屬性有變化就會觸發) | 是(發現多個設備時各觸發一次) |
上層應用行為 | 更新已有設備界面信息 | 新增列表項,展示設備名稱等 |
4. Ble scan 時
當 ble 掃描到設備時, 就會觸發調用 BleScannerInterfaceImpl::OnScanResult
// system/main/shim/le_scanning_manager.ccvoid BleScannerInterfaceImpl::OnScanResult(uint16_t event_type, uint8_t address_type, bluetooth::hci::Address address,uint8_t primary_phy, uint8_t secondary_phy, uint8_t advertising_sid,int8_t tx_power, int8_t rssi, uint16_t periodic_advertising_interval,std::vector<uint8_t> advertising_data) {RawAddress raw_address = ToRawAddress(address);tBLE_ADDR_TYPE ble_addr_type = to_ble_addr_type(address_type);if (ble_addr_type != BLE_ADDR_ANONYMOUS) {btm_ble_process_adv_addr(raw_address, &ble_addr_type);}// 這里會 去觸發 更新 ble 設備的 propertiesdo_in_jni_thread(FROM_HERE,base::BindOnce(&BleScannerInterfaceImpl::handle_remote_properties,base::Unretained(this), raw_address, ble_addr_type,advertising_data));do_in_jni_thread(FROM_HERE,base::BindOnce(&ScanningCallbacks::OnScanResult,base::Unretained(scanning_callbacks_), event_type,static_cast<uint8_t>(address_type), raw_address,primary_phy, secondary_phy, advertising_sid, tx_power,rssi, periodic_advertising_interval, advertising_data));...
}
// system/main/shim/le_scanning_manager.ccvoid BleScannerInterfaceImpl::handle_remote_properties(RawAddress bd_addr, tBLE_ADDR_TYPE addr_type,std::vector<uint8_t> advertising_data) {if (!bluetooth::shim::is_gd_stack_started_up()) {LOG_WARN("Gd stack is stopped, return");return;}// skip anonymous advertismentif (addr_type == BLE_ADDR_ANONYMOUS) {return;}auto device_type = bluetooth::hci::DeviceType::LE;uint8_t flag_len;const uint8_t* p_flag = AdvertiseDataParser::GetFieldByType(advertising_data, BTM_BLE_AD_TYPE_FLAG, &flag_len);if (p_flag != NULL && flag_len != 0) {if ((BTM_BLE_BREDR_NOT_SPT & *p_flag) == 0) {device_type = bluetooth::hci::DeviceType::DUAL;}}uint8_t remote_name_len;const uint8_t* p_eir_remote_name = AdvertiseDataParser::GetFieldByType(advertising_data, HCI_EIR_COMPLETE_LOCAL_NAME_TYPE, &remote_name_len);if (p_eir_remote_name == NULL) {p_eir_remote_name = AdvertiseDataParser::GetFieldByType(advertising_data, HCI_EIR_SHORTENED_LOCAL_NAME_TYPE, &remote_name_len);}// update device nameif ((addr_type != BLE_ADDR_RANDOM) || (p_eir_remote_name)) {if (!address_cache_.find(bd_addr)) {address_cache_.add(bd_addr);if (p_eir_remote_name) {bt_bdname_t bdname;memcpy(bdname.name, p_eir_remote_name, remote_name_len);if (remote_name_len < BD_NAME_LEN + 1)bdname.name[remote_name_len] = '\0';// 這里很關鍵, 也就是說 只有 掃描到的 ble 設備 有名字, 才會去更新 properties. 其他例如 ble 的 rssi 等,都不會更新。btif_dm_update_ble_remote_properties(bd_addr, bdname.name, device_type);}}}auto* storage_module = bluetooth::shim::GetStorage();bluetooth::hci::Address address = ToGdAddress(bd_addr);// update device typeauto mutation = storage_module->Modify();bluetooth::storage::Device device =storage_module->GetDeviceByLegacyKey(address);mutation.Add(device.SetDeviceType(device_type));mutation.Commit();// update address typeauto mutation2 = storage_module->Modify();bluetooth::storage::LeDevice le_device = device.Le();mutation2.Add(le_device.SetAddressType((bluetooth::hci::AddressType)addr_type));mutation2.Commit();
}
// system/btif/src/btif_dm.cc
void btif_dm_update_ble_remote_properties(const RawAddress& bd_addr,BD_NAME bd_name,tBT_DEVICE_TYPE dev_type) {btif_update_remote_properties(bd_addr, bd_name, NULL, dev_type);
}
// system/btif/src/btif_dm.ccstatic void btif_update_remote_properties(const RawAddress& bdaddr,BD_NAME bd_name, DEV_CLASS dev_class,tBT_DEVICE_TYPE device_type) {int num_properties = 0;bt_property_t properties[3];bt_status_t status = BT_STATUS_UNHANDLED;uint32_t cod;bt_device_type_t dev_type;memset(properties, 0, sizeof(properties));/* remote name */if (strlen((const char*)bd_name)) {BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties], BT_PROPERTY_BDNAME,strlen((char*)bd_name), bd_name);if (!bluetooth::shim::is_gd_security_enabled()) {status = btif_storage_set_remote_device_property(&bdaddr, &properties[num_properties]);ASSERTC(status == BT_STATUS_SUCCESS, "failed to save remote device name",status);}num_properties++;}...invoke_remote_device_properties_cb(status, bdaddr, num_properties,properties); // 1.
}
ble 在掃描階段, 只有掃描到 設備的名字時, 才會去 調用 invoke_remote_device_properties_cb 更新 properties.
1.例子一
01-02 14:20:35.237345 2146 2634 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:637 btif_update_remote_properties: cod from storage is also unclassified01-02 14:20:35.237477 2146 2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_in_fetch_bonded_ble_device Found a LE device: ba:03:cc:1c:cd:2201-02 14:20:35.237906 2146 2634 D BtGatt.GattService: onScanResult() - eventType=0x1b, addressType=0, address=BA:03:CC:1C:CD:22, primaryPhy=1, secondaryPhy=0, advertisingSid=0xff, txPower=127, rssi=-73, periodicAdvInt=0x0, originalAddress=00:00:00:00:00:0001-02 14:20:35.238671 2146 2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: operator(): HAL bt_hal_cbacks->remote_device_properties_cb
01-02 14:20:35.238841 2146 2634 D BluetoothRemoteDevices: Added new device property
01-02 14:20:35.239122 2146 2634 I BluetoothRemoteDevices: Property type: 1
01-02 14:20:35.240002 2146 2634 I BluetoothRemoteDevices: Remote Device name is: MXD57_MI
01-02 14:20:35.240042 2146 2634 I BluetoothRemoteDevices: Property type: 4
01-02 14:20:35.240082 2146 2634 I BluetoothRemoteDevices: Skip class update for BA:03:CC:1C:CD:22
01-02 14:20:35.240104 2146 2634 I BluetoothRemoteDevices: Property type: 5
131 2025-01-02 14:20:35.236141 controller host HCI_EVT 60 Rcvd LE Meta (LE Extended Advertising Report)Frame 131: 60 bytes on wire (480 bits), 60 bytes captured (480 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI Event - LE MetaEvent Code: LE Meta (0x3e)Parameter Total Length: 57Sub Event: LE Extended Advertising Report (0x0d)Num Reports: 1Event Type: 0x0013, Connectable, Scannable, Legacy, Data Status: CompletePeer Address Type: Public Device Address (0x00)BD_ADDR: ba:03:cc:1c:cd:22 (ba:03:cc:1c:cd:22)Primary PHY: LE 1M (0x01)Secondary PHY: No packets on the secondary advertising channel (0x00)Advertising SID: 0xff (not available)TX Power: 127 dBm (not available)RSSI: -74 dBmPeriodic Advertising Interval: 0x0000 (no periodic advertising)Direct Address Type: Public Device Address (0x00)Direct BD_ADDR: 00:00:00_00:00:00 (00:00:00:00:00:00)Data Length: 31Advertising DataFlagsManufacturer SpecificDevice Name: MXD57_MI # 廣播數據中有設備名字Manufacturer Specific
2.例子二
01-02 14:20:35.311975 2146 2634 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:637 btif_update_remote_properties: cod from storage is also unclassified01-02 14:20:35.312013 2146 2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_in_fetch_bonded_ble_device Found a LE device: c0:53:c1:67:0e:a801-02 14:20:35.312128 2146 2634 D BtGatt.GattService: onScanResult() - eventType=0x1b, addressType=1, address=C0:53:C1:67:0E:A8, primaryPhy=1, secondaryPhy=0, advertisingSid=0xff, txPower=127, rssi=-82, periodicAdvInt=0x0, originalAddress=00:00:00:00:00:0001-02 14:20:35.312390 2146 2634 D BluetoothRemoteDevices: Added new device property
01-02 14:20:35.312479 2146 2634 I BluetoothRemoteDevices: Property type: 1
01-02 14:20:35.313982 2146 2634 I BluetoothRemoteDevices: Remote Device name is: LE_WF-C500
01-02 14:20:35.314013 2146 2634 I BluetoothRemoteDevices: Property type: 4
01-02 14:20:35.314035 2146 2634 I BluetoothRemoteDevices: Skip class update for C0:53:C1:67:0E:A8
01-02 14:20:35.314046 2146 2634 I BluetoothRemoteDevices: Property type: 5
158 2025-01-02 14:20:35.308074 controller host HCI_EVT 41 Rcvd LE Meta (LE Extended Advertising Report)Frame 158: 41 bytes on wire (328 bits), 41 bytes captured (328 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI Event - LE MetaEvent Code: LE Meta (0x3e)Parameter Total Length: 38Sub Event: LE Extended Advertising Report (0x0d)Num Reports: 1Event Type: 0x0013, Connectable, Scannable, Legacy, Data Status: CompletePeer Address Type: Random Device Address (0x01)BD_ADDR: c0:53:c1:67:0e:a8 (c0:53:c1:67:0e:a8)Primary PHY: LE 1M (0x01)Secondary PHY: No packets on the secondary advertising channel (0x00)Advertising SID: 0xff (not available)TX Power: 127 dBm (not available)RSSI: -81 dBmPeriodic Advertising Interval: 0x0000 (no periodic advertising)Direct Address Type: Public Device Address (0x00)Direct BD_ADDR: 00:00:00_00:00:00 (00:00:00:00:00:00)Data Length: 12Advertising DataDevice Name: LE_WF-C500 // 設備名字
3. 例子三
01-02 14:20:41.776270 2146 2634 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:637 btif_update_remote_properties: cod from storage is also unclassified01-02 14:20:41.776344 2146 2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_in_fetch_bonded_ble_device Found a LE device: d4:f0:ea:91:af:c601-02 14:20:41.776511 2146 2634 D BtGatt.GattService: onScanResult() - eventType=0x1b, addressType=0, address=D4:F0:EA:91:AF:C6, primaryPhy=1, secondaryPhy=0, advertisingSid=0xff, txPower=127, rssi=-84, periodicAdvInt=0x0, originalAddress=00:00:00:00:00:0001-02 14:20:41.776644 2146 2634 D BluetoothRemoteDevices: Added new device property
01-02 14:20:41.776740 2146 2634 I BluetoothRemoteDevices: Property type: 1
01-02 14:20:41.777297 2146 2634 I BluetoothRemoteDevices: Remote Device name is: SMI-M14
01-02 14:20:41.777325 2146 2634 I BluetoothRemoteDevices: Property type: 4
01-02 14:20:41.777346 2146 2634 I BluetoothRemoteDevices: Skip class update for D4:F0:EA:91:AF:C6
01-02 14:20:41.777359 2146 2634 I BluetoothRemoteDevices: Property type: 5
735 2025-01-02 14:20:41.775853 controller host HCI_EVT 59 Rcvd LE Meta (LE Extended Advertising Report)Frame 735: 59 bytes on wire (472 bits), 59 bytes captured (472 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI Event - LE MetaEvent Code: LE Meta (0x3e)Parameter Total Length: 56Sub Event: LE Extended Advertising Report (0x0d)Num Reports: 1Event Type: 0x0013, Connectable, Scannable, Legacy, Data Status: CompletePeer Address Type: Public Device Address (0x00)BD_ADDR: BeijingX_91:af:c6 (d4:f0:ea:91:af:c6)Primary PHY: LE 1M (0x01)Secondary PHY: No packets on the secondary advertising channel (0x00)Advertising SID: 0xff (not available)TX Power: 127 dBm (not available)RSSI: -84 dBmPeriodic Advertising Interval: 0x0000 (no periodic advertising)Direct Address Type: Public Device Address (0x00)Direct BD_ADDR: 00:00:00_00:00:00 (00:00:00:00:00:00)Data Length: 30Advertising DataFlagsLength: 2Type: Flags (0x01)0. .... = Reserved: 0x0...0 .... = Simultaneous LE and BR/EDR to Same Device Capable (Host): false (0x0).... 0... = Simultaneous LE and BR/EDR to Same Device Capable (Controller): false (0x0).... .1.. = BR/EDR Not Supported: true (0x1).... ..1. = LE General Discoverable Mode: true (0x1).... ...0 = LE Limited Discoverable Mode: false (0x0)Service Data - 16 bit UUIDLength: 17Type: Service Data - 16 bit UUID (0x16)UUID 16: Xiaomi Inc. (0xfe95)Service Data: b054452d00c6af91eaf0d4080e00Device Name: SMI-M14 // 設備名字Length: 8Type: Device Name (0x09)Device Name: SMI-M14
2. SDP / 服務發現階段
1.作用
-
在 classic 藍牙中,通過 SDP 獲取遠程設備支持的 UUID
-
在 BLE 中,通過 GATT Discover Services 獲取 UUID
-
均更新到
DeviceProperties::uuids
字段中
2.解決的問題
問題 | 如何解決 |
---|---|
重復做 SDP,浪費時間 | 已知 UUID 可直接進入連接邏輯 |
Profile 初始化失敗 | 使用緩存 UUID 預判斷是否支持 A2DP、HFP 等 |
連接效率低 | 提前緩存服務能力,減少阻塞等待時間 |
3.代碼流程鑒賞
1. btif_dm_search_services_evt
1. BTA_DM_DISC_RES_EVT
這段代碼是 Android AOSP 中藍牙設備 服務發現結果事件(BTA_DM_DISC_RES_EVT)的處理邏輯,位于 btif_dm_search_services_evt 函數中。它主要完成 SDP 服務發現結果的處理、UUID 合并、A2DP 能力識別、屬性上報,以及處理 SDP 失敗的情況。
大體流程概覽:
- 提取事件數據(設備地址、UUID 等)
- 判斷是否是配對過程的設備
- 判斷 SDP 是否失敗并重試(帶次數限制)
- 如果成功則合并 UUID、識別 A2DP 功能
- 特殊處理 LE Audio 設備
- 將 UUID 結果保存、上報到 Java 層
- 處理 SDP 失敗時嘗試使用 EIR 中的 UUID
- 最終調用回調通知 UUID 屬性變化
/********************************************************************************* Function btif_dm_search_services_evt** Description Executes search services event in btif context** Returns void*******************************************************************************/
static void btif_dm_search_services_evt(tBTA_DM_SEARCH_EVT event,tBTA_DM_SEARCH* p_data) {switch (event) {case BTA_DM_DISC_RES_EVT: {// 初始化變量,用于保存服務發現結果、UUID 集合和屬性值,a2dp_sink_capable 標記是否支持 A2DP Sink。bt_property_t prop;uint32_t i = 0;bt_status_t ret;std::vector<uint8_t> property_value;std::set<Uuid> uuids;bool a2dp_sink_capable = false;// 獲取服務發現設備的地址引用。RawAddress& bd_addr = p_data->disc_res.bd_addr;// 判斷當前 SDP 結果是否是為當前正在配對的設備服務。bool results_for_bonding_device =(bd_addr == pairing_cb.bd_addr || bd_addr == pairing_cb.static_bdaddr);// 日志輸出 SDP 的返回值和服務字段。LOG_VERBOSE("result=0x%x, services 0x%x", p_data->disc_res.result,p_data->disc_res.services);/*SDP 失敗后的重試邏輯(僅限配對設備):*/if (results_for_bonding_device && p_data->disc_res.result != BTA_SUCCESS &&pairing_cb.state == BT_BOND_STATE_BONDED &&pairing_cb.sdp_attempts < BTIF_DM_MAX_SDP_ATTEMPTS_AFTER_PAIRING) { // 如果是配對設備、SDP 失敗、當前狀態為已配對、重試次數不超過限制:// 第一次不重試,第二次以上繼續嘗試,最多三次。if (pairing_cb.sdp_attempts) {LOG_WARN("SDP failed after bonding re-attempting for %s",PRIVATE_ADDRESS(bd_addr));pairing_cb.sdp_attempts++;LOG_INFO("sdp_attempts = %d", pairing_cb.sdp_attempts);btif_dm_get_remote_services(bd_addr, BT_TRANSPORT_AUTO);} else {LOG_WARN("SDP triggered by someone failed when bonding");}return;}// 如果是配對設備,則標記經典 SDP 已完成。if (results_for_bonding_device) {LOG_INFO("SDP finished for %s:", PRIVATE_ADDRESS(bd_addr));pairing_cb.sdp_over_classic =btif_dm_pairing_cb_t::ServiceDiscoveryState::FINISHED;}// 設置 UUID 屬性類型。prop.type = BT_PROPERTY_UUIDS;prop.len = 0;if ((p_data->disc_res.result == BTA_SUCCESS) &&(p_data->disc_res.num_uuids > 0)) { // 如果 SDP 成功且發現了 UUID:LOG_INFO("New UUIDs for %s:", bd_addr.ToString().c_str());for (i = 0; i < p_data->disc_res.num_uuids; i++) { // 遍歷所有 UUID,過濾無效項,并插入到集合中。auto uuid = p_data->disc_res.p_uuid_list + i;if (btif_should_ignore_uuid(*uuid)) {continue;}LOG_INFO("index:%d uuid:%s", i, uuid->ToString().c_str());uuids.insert(*uuid);}// 將已有的 UUID 合并進去,避免覆蓋。if (results_for_bonding_device) {btif_merge_existing_uuids(pairing_cb.static_bdaddr, &uuids);btif_merge_existing_uuids(pairing_cb.bd_addr, &uuids);} else {btif_merge_existing_uuids(bd_addr, &uuids);}// 將 UUID 轉為 128bit 字節流,并識別 A2DP Sink 能力。for (auto& uuid : uuids) {auto uuid_128bit = uuid.To128BitBE();property_value.insert(property_value.end(), uuid_128bit.begin(),uuid_128bit.end());if (uuid == UUID_A2DP_SINK) {a2dp_sink_capable = true;}}prop.val = (void*)property_value.data();prop.len = Uuid::kNumBytes128 * uuids.size();}// 如果同時支持 LE Audio 且 GATT 服務發現未完成,則推遲向 Java 層報告 UUID,等待 LE GATT 結果bool skip_reporting_wait_for_le = false;/* If we are doing service discovery for device that just bonded, that is* capable of a2dp, and both sides can do LE Audio, and it haven't* finished GATT over LE yet, then wait for LE service discovery to finish* before before passing services to upper layers. */if (results_for_bonding_device &&a2dp_sink_capable && LeAudioClient::IsLeAudioClientRunning() &&pairing_cb.gatt_over_le !=btif_dm_pairing_cb_t::ServiceDiscoveryState::FINISHED &&(check_cod_le_audio(bd_addr) ||metadata_cb.le_audio_cache.contains(bd_addr) ||BTA_DmCheckLeAudioCapable(bd_addr))) {skip_reporting_wait_for_le = true;}/* onUuidChanged requires getBondedDevices to be populated.** bond_state_changed needs to be sent prior to remote_device_property*/auto num_eir_uuids = 0;Uuid uuid = {};// 只在 SDP + 配對都完成后進入。if (pairing_cb.state == BT_BOND_STATE_BONDED && pairing_cb.sdp_attempts &&results_for_bonding_device) {LOG_INFO("SDP search done for %s", bd_addr.ToString().c_str());pairing_cb.sdp_attempts = 0; // 重置重試計數。LOG_INFO("sdp_attempts = %d", pairing_cb.sdp_attempts);// Send UUIDs discovered through EIR to Java to unblock pairing intent// when SDP failed or no UUID is discoveredif (p_data->disc_res.result != BTA_SUCCESS ||p_data->disc_res.num_uuids == 0) { // 如果 SDP 失敗或沒有發現 UUID:auto uuids_iter = eir_uuids_cache.find(bd_addr);if (uuids_iter != eir_uuids_cache.end()) { // 嘗試從緩存中讀取 EIR UUID 并上報。num_eir_uuids = static_cast<int>(uuids_iter->second.size());LOG_INFO("SDP failed, send %d EIR UUIDs to unblock bonding %s",num_eir_uuids, bd_addr.ToString().c_str());for (auto eir_uuid : uuids_iter->second) {auto uuid_128bit = eir_uuid.To128BitBE();property_value.insert(property_value.end(), uuid_128bit.begin(),uuid_128bit.end());}eir_uuids_cache.erase(uuids_iter);}if (num_eir_uuids > 0) {prop.val = (void*)property_value.data();prop.len = num_eir_uuids * Uuid::kNumBytes128;} else { // 若 EIR 無內容,則構造空 UUID。LOG_WARN("SDP failed and we have no EIR UUIDs to report either");prop.val = &uuid;prop.len = Uuid::kNumBytes128;}}// Both SDP and bonding are done, clear pairing control block in case// it is not already clearedpairing_cb = {}; // 清除 pairing_cb 控制塊。LOG_INFO("clearing btif pairing_cb");}// 將 UUID 存儲到本地數據庫。if (p_data->disc_res.num_uuids != 0 || num_eir_uuids != 0) {/* Also write this to the NVRAM */ret = btif_storage_set_remote_device_property(&bd_addr, &prop);ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote services failed",ret);if (skip_reporting_wait_for_le) { // 如果不是 LE Audio 的延遲上報情況,則立即通過回調通知 Java 層屬性已更新。LOG_INFO("Bonding LE Audio sink - must wait for le services discovery ""to pass all services to java %s",PRIVATE_ADDRESS(bd_addr));/* For LE Audio capable devices, we care more about passing GATT LE* services than about just finishing pairing. Service discovery* should be scheduled when LE pairing finishes, by call to* btif_dm_get_remote_services(bd_addr, BT_TRANSPORT_LE) */return;}/* Send the event to the BTIF */invoke_remote_device_properties_cb(BT_STATUS_SUCCESS, bd_addr, 1,&prop);}} break;...}
功能點 | 實現邏輯 |
---|---|
是否是配對設備 | 比較地址匹配 pairing_cb |
SDP 重試 | 最多重試 3 次,間隔觸發 |
UUID 處理 | 收集、過濾、合并、識別 A2DP 能力 |
LE Audio | 若需 GATT 完成則跳過上報 |
SDP 失敗回退 | 通過 EIR UUID 解鎖配對流程 |
屬性更新 | 保存到數據庫 + 上報 Java 層 |
01-02 14:21:00.149273 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1638 btif_dm_search_services_evt: SDP finished for xx:xx:xx:xx:b0:62:
01-02 14:21:00.149282 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1647 btif_dm_search_services_evt: New UUIDs for 70:8f:47:91:b0:62:
01-02 14:21:00.149295 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:0 uuid:0000110a-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149309 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:1 uuid:00001105-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149320 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:2 uuid:00001115-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149332 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:3 uuid:00001116-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149343 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:4 uuid:0000112d-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149355 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:5 uuid:0000110e-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149366 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:6 uuid:0000112f-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149378 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:7 uuid:00001112-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149397 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:8 uuid:0000111f-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149409 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:9 uuid:00001132-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149423 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:12 uuid:8fa9c715-bd1f-596c-a1b0-13162b15c892
01-02 14:21:00.149441 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:13 uuid:2c042b0a-7f57-4c0a-afcf-1762af70257c
01-02 14:21:00.149453 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:14 uuid:9fed64fd-e91a-499e-88dd-73dfe023feed
01-02 14:21:00.149859 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1696 btif_dm_search_services_evt: SDP search done for 70:8f:47:91:b0:62
01-02 14:21:00.149882 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1698 btif_dm_search_services_evt: sdp_attempts = 0
01-02 14:21:00.149891 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1728 btif_dm_search_services_evt: clearing btif pairing_cb
- 車機向 手機請求 sdp
976 2025-01-02 14:21:00.107670 f8:6b:14:d1:ec:32 (cheji) vivoMobi_91:b0:62 (cbx) SDP 31 Sent Service Search Attribute Request : L2CAP: Attribute Range (0x0000 - 0xffff) Frame 976: 31 bytes on wire (248 bits), 31 bytes captured (248 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP Protocol
Bluetooth SDP ProtocolPDU: Service Search Attribute Request (0x06)Transaction Id: 0x0001Parameter Length: 17Service Search Pattern: L2CAPMaximum Attribute Byte Count: 1008Attribute ID ListContinuation State: yes (03 F0)
- 手機向 車機返回結果:
980 2025-01-02 14:21:00.117463 vivoMobi_91:b0:62 (cbx) f8:6b:14:d1:ec:32 (Mycar28825) SDP 487 Rcvd Service Search Attribute Response Frame 980: 487 bytes on wire (3896 bits), 487 bytes captured (3896 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP Protocol
Bluetooth SDP ProtocolPDU: Service Search Attribute Response (0x07)Transaction Id: 0x0001Parameter Length: 473Attribute List Byte Count: 470Data FragmentContinuation State: no (00)[Reassembled Attribute List]Attribute Lists [count = 16]Data Element: Sequence uint16 1475 bytes0011 0... = Data Element Type: Sequence (6).... .110 = Data Element Size: uint16 (6)Data Element Var Size: 1475Data ValueAttribute List [count = 4] (Generic Attribute Profile)Attribute List [count = 4] (Generic Access Profile)Attribute List [count = 6] (Headset Audio Gateway)Attribute List [count = 8] (Handsfree Audio Gateway)Attribute List [count = 8] (A/V Remote Control Target)Attribute List [count = 7] (Audio Source)Attribute List [count = 7] (A/V Remote Control)Attribute List [count = 11] (PAN NAP)Attribute List [count = 9] (PAN PANU)Attribute List [count = 6] (SIM Access)Attribute List [count = 7] (Phonebook Access Server)Attribute List [count = 10] (Message Access Server)Attribute List [count = 8] (OBEX Object Push)Attribute List [count = 5] (CustomUUID: Unknown)Attribute List [count = 4] (CustomUUID: Unknown)Attribute List [count = 4] (CustomUUID: Unknown)
2. BTA_DM_GATT_OVER_LE_RES_EVT
這段代碼,它處理的是 通過 GATT over LE 發現到的服務 UUID,這是在藍牙設備進行 LE Audio 相關配對和服務發現時的關鍵步驟之一。
static void btif_dm_search_services_evt(tBTA_DM_SEARCH_EVT event,tBTA_DM_SEARCH* p_data) {switch (event) {// 表示收到 BLE GATT 服務發現完成事件,主要處理通過 GATT over LE 獲取到的服務信息。case BTA_DM_GATT_OVER_LE_RES_EVT: {/*num_properties: 記錄即將設置的屬性數量(如 UUID、名稱)。prop: 保存即將傳遞給上層的屬性,最多兩個(UUIDs + 名稱)。property_value: 保存所有服務 UUID 的序列化值。uuids: 使用 set 記錄 UUID,防止重復。bd_addr: 當前發現服務的設備地址。*/int num_properties = 0;bt_property_t prop[2];std::vector<uint8_t> property_value;std::set<Uuid> uuids;RawAddress& bd_addr = p_data->disc_ble_res.bd_addr;if (event == BTA_DM_GATT_OVER_LE_RES_EVT) {LOG_INFO("New GATT over LE UUIDs for %s:",PRIVATE_ADDRESS(bd_addr));if ((bd_addr == pairing_cb.bd_addr ||bd_addr == pairing_cb.static_bdaddr)) {if (pairing_cb.gatt_over_le !=btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED) {LOG_ERROR("gatt_over_le should be SCHEDULED, did someone clear the ""control block for %s ?",PRIVATE_ADDRESS(bd_addr));}/*如果當前設備是正在配對的目標設備,更新配對控制塊 pairing_cb 的 GATT 狀態為已完成。同時判斷 gatt_over_le 狀態是否應該是 SCHEDULED(即之前已經調度過該設備的服務發現)——否則可能說明狀態機錯亂。*/pairing_cb.gatt_over_le =btif_dm_pairing_cb_t::ServiceDiscoveryState::FINISHED;if (pairing_cb.sdp_over_classic !=btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED) {// 如果 classic SDP 服務發現也沒有調度或已經完成,說明整個配對與服務發現流程都已完成,可以安全清除 pairing_cb。// Both SDP and bonding are either done, or not scheduled,// we are safe to clear the service discovery part of CB.LOG_INFO("clearing pairing_cb");pairing_cb = {};}}} else {// 這段用于處理 BTA_DM_GATT_OVER_SDP_RES_EVT(即通過 classic SDP 獲取 GATT UUID),但這里實際處理的是 GATT_OVER_LE_RES_EVT。LOG_INFO("New GATT over SDP UUIDs for %s:", PRIVATE_ADDRESS(bd_addr));}for (Uuid uuid : *p_data->disc_ble_res.services) {/*遍歷發現到的 GATT 服務 UUID:只保留 “有趣的” LE 服務(如 Hearing Aid、LE Audio)。忽略指定的 UUID。使用 set 自動去重。*/if (btif_is_interesting_le_service(uuid)) {if (btif_should_ignore_uuid(uuid)) {continue;}LOG_INFO("index:%d uuid:%s", static_cast<int>(uuids.size()),uuid.ToString().c_str());uuids.insert(uuid);}}if (uuids.empty()) {// 如果沒有任何服務 UUID 被記錄,直接退出。LOG_INFO("No well known GATT services discovered");return;}// 讀取該設備之前已經緩存的 UUID 并合并,防止遺漏或丟失信息。Uuid existing_uuids[BT_MAX_NUM_UUIDS] = {};btif_get_existing_uuids(&bd_addr, existing_uuids);for (int i = 0; i < BT_MAX_NUM_UUIDS; i++) {Uuid uuid = existing_uuids[i];if (uuid.IsEmpty()) {continue;}uuids.insert(uuid);}// 將 UUID 以 128bit Big-Endian 格式寫入 property_value,用于上層 Java 層接收。for (auto& uuid : uuids) {auto uuid_128bit = uuid.To128BitBE();property_value.insert(property_value.end(), uuid_128bit.begin(),uuid_128bit.end());}// 設置 UUID 屬性并保存到 內存中prop[0].type = BT_PROPERTY_UUIDS;prop[0].val = (void*)property_value.data();prop[0].len = Uuid::kNumBytes128 * uuids.size();/* Also write this to the NVRAM */bt_status_t ret =btif_storage_set_remote_device_property(&bd_addr, &prop[0]);ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote services failed", ret);num_properties++;// 如果通過 BLE GATT 回包中包含設備名,就保存遠程設備名稱屬性。/* Remote name update */if (strnlen((const char*)p_data->disc_ble_res.bd_name, BD_NAME_LEN)) {prop[1].type = BT_PROPERTY_BDNAME;prop[1].val = p_data->disc_ble_res.bd_name;prop[1].len = strnlen((char*)p_data->disc_ble_res.bd_name, BD_NAME_LEN);ret = btif_storage_set_remote_device_property(&bd_addr, &prop[1]);ASSERTC(ret == BT_STATUS_SUCCESS,"failed to save remote device property", ret);num_properties++;}// 如果當前是 GATT over SDP 的結果,不在這里直接上報,而是等待 DISC_RES_EVT 一并處理/* If services were returned as part of SDP discovery, we will immediately* send them with rest of SDP results in BTA_DM_DISC_RES_EVT */if (event == BTA_DM_GATT_OVER_SDP_RES_EVT) {return;}// 將 UUID 和名稱(如果有)回調通知上層應用,例如用于 Java API BluetoothDevice.getUuids() 或用于觸發服務綁定。/* Send the event to the BTIF */invoke_remote_device_properties_cb(BT_STATUS_SUCCESS, bd_addr,num_properties, prop);} break;default: { ASSERTC(0, "unhandled search services event", event); } break;}
此 case 的主要作用是處理通過 BLE GATT 通道 發現的服務 UUID,具體流程如下:
步驟 | 操作 |
---|---|
1 | 判斷是否是當前配對目標 |
2 | 標記 GATT 服務發現已完成 |
3 | 從 GATT 發現包中提取 UUID,并過濾無關的 |
4 | 合并之前已緩存的 UUID(避免丟失) |
5 | 保存 UUID 屬性并更新 NVRAM |
6 | 如有遠程設備名,也一并保存 |
7 | 最后回調通知 Java 層,完成 BLE 服務發現過程 |
3. 配對階段(Pairing / Bonding)
1. 作用
-
緩存配對結果:Bond 狀態、IO 能力、安全連接支持與否、LinkKey 類型與值
-
Profile 模塊可依賴
DeviceProperties
判斷是否可信 / 是否可加密傳輸
2. 寫入 bt_config.conf 內容
字段 | 示例 |
---|---|
Bonded | true |
LinkKey | base64 編碼 |
LinkKeyType | 5(Unauthenticated Combination) |
IOCapability | 3(No Input No Output) |
SecureConnection | true |
3. 解決的問題
問題 | 如何解決 |
---|---|
重啟系統后丟失配對信息 | LinkKey / Bond 信息持久化 |
多設備狀態混亂 | 每個設備獨立記錄屬性 |
不確定是否加密傳輸 | 使用緩存 IO 能力與 LinkKey 屬性判斷安全等級 |
4. 代碼鑒賞
1. btif_dm_ssp_cfm_req_evt
btif_dm_ssp_cfm_req_evt
函數
- 它在 Bluetooth Secure Simple Pairing(SSP)確認請求事件 到達時執行。
這個函數在收到遠端設備發起的 SSP 請求(特別是 Just Works 或 Passkey Confirmation 模式)時:
-
判斷是否可繼續配對;
-
設置配對狀態;
-
依據配對模式判斷是否自動接受;
-
最終將請求上報給 Java 層顯示 UI 以確認(如彈窗顯示 PIN)。
// system/btif/src/btif_dm.cc/********************************************************************************* Function btif_dm_ssp_cfm_req_evt** Description Executes SSP confirm request event in btif context** Returns void*******************************************************************************/
// 接收一個指向 SSP 確認請求的結構體指針 p_ssp_cfm_req,包含發起配對的設備地址、名稱、class、配對模式(Just Works / Passkey)、數字比較數值等信息
static void btif_dm_ssp_cfm_req_evt(tBTA_DM_SP_CFM_REQ* p_ssp_cfm_req) {bt_bdname_t bd_name;bool is_incoming = !(pairing_cb.state == BT_BOND_STATE_BONDING); // 判斷當前是否是被動配對(未處于 BONDING 狀態)。uint32_t cod; // Class of Device(設備類型編碼)。int dev_type; // 設備類型(BR/EDR、BLE 或 Dual Mode)。BTIF_TRACE_DEBUG("%s", __func__);/*優先通過 feature 判斷遠端是否為 dual-mode。若失敗,則嘗試查詢設備類型。查詢失敗,默認視為傳統 BR/EDR 設備。*//* Remote properties update */if (BTM_GetPeerDeviceTypeFromFeatures(p_ssp_cfm_req->bd_addr) ==BT_DEVICE_TYPE_DUMO) {dev_type = BT_DEVICE_TYPE_DUMO;} else if (!btif_get_device_type(p_ssp_cfm_req->bd_addr, &dev_type)) {// Failed to get device type, defaulting to BR/EDR.dev_type = BT_DEVICE_TYPE_BREDR;}// 更新設備的屬性緩存:名稱、Class of Device 和類型。btif_update_remote_properties(p_ssp_cfm_req->bd_addr, p_ssp_cfm_req->bd_name,p_ssp_cfm_req->dev_class,(tBT_DEVICE_TYPE)dev_type);// 緩存設備地址和名稱,后面用于顯示和判斷。RawAddress bd_addr = p_ssp_cfm_req->bd_addr;memcpy(bd_name.name, p_ssp_cfm_req->bd_name, BD_NAME_LEN);// 如果當前正在和某個設備配對,而新來的請求來自另一個設備,說明存在配對沖突。if (pairing_cb.state == BT_BOND_STATE_BONDING &&bd_addr != pairing_cb.bd_addr) {BTIF_TRACE_WARNING("%s(): already in bonding state, reject request",__FUNCTION__);// 拒絕該請求,防止狀態混亂或被攻擊。btif_dm_ssp_reply(bd_addr, BT_SSP_VARIANT_PASSKEY_CONFIRMATION, 0);return;}/* Set the pairing_cb based on the local & remote authentication requirements*/// 將當前設備設置為 BONDING 狀態,并上報配對事件(Java 層通過廣播感知)。bond_state_changed(BT_STATUS_SUCCESS, bd_addr, BT_BOND_STATE_BONDING);// 記錄當前配對是否為 Just Works,以及雙方的認證需求。BTIF_TRACE_EVENT("%s: just_works:%d, loc_auth_req=%d, rmt_auth_req=%d",__func__, p_ssp_cfm_req->just_works,p_ssp_cfm_req->loc_auth_req, p_ssp_cfm_req->rmt_auth_req);/*判斷是否是臨時配對如果是 Just Works 模式,且:雙方都沒有要求配對記憶(bonding)也不是鼠標等 pointing device*//* if just_works and bonding bit is not set treat this as temporary */if (p_ssp_cfm_req->just_works &&!(p_ssp_cfm_req->loc_auth_req & BTM_AUTH_BONDS) &&!(p_ssp_cfm_req->rmt_auth_req & BTM_AUTH_BONDS) &&!(check_cod((RawAddress*)&p_ssp_cfm_req->bd_addr, COD_HID_POINTING)))pairing_cb.bond_type = tBTM_SEC_DEV_REC::BOND_TYPE_TEMPORARY; // 那么是“臨時配對”,elsepairing_cb.bond_type = tBTM_SEC_DEV_REC::BOND_TYPE_PERSISTENT; // 否則就是“持久配對”(會保存)。btm_set_bond_type_dev(p_ssp_cfm_req->bd_addr, pairing_cb.bond_type); // 將配對類型同步設置到底層。pairing_cb.is_ssp = true; // 設置 pairing_cb 中當前為 SSP 配對(而不是 legacy pairing)。/* If JustWorks auto-accept */if (p_ssp_cfm_req->just_works) { // 自動接受 Just Works 模式的特殊情況/* Pairing consent for JustWorks NOT needed if:* 1. Incoming temporary pairing is detected*/if (is_incoming &&pairing_cb.bond_type == tBTM_SEC_DEV_REC::BOND_TYPE_TEMPORARY) {/*如果是 Just Works 模式,且當前是被動發起(遠端請求配對)、且是臨時配對:*/BTIF_TRACE_EVENT("%s: Auto-accept JustWorks pairing for temporary incoming", __func__);btif_dm_ssp_reply(bd_addr, BT_SSP_VARIANT_CONSENT, true); // 自動接受該配對,無需用戶確認。這用于臨時設備,比如游戲手柄、耳機等return;}}cod = devclass2uint(p_ssp_cfm_req->dev_class); // 將設備 class(3 字節)轉為整型數值。if (cod == 0) { // 如果為 0(未知),設置為未分類。LOG_INFO("%s cod is 0, set as unclassified", __func__);cod = COD_UNCLASSIFIED;}pairing_cb.sdp_attempts = 0; // 剛進入配對流程,SDP 嘗試次數設為 0。LOG_INFO("sdp_attempts = %d", pairing_cb.sdp_attempts);/*通知 Java 層顯示配對確認彈窗調用回調函數,通知 Java 層出現確認界面。BT_SSP_VARIANT_CONSENT → Just Works 模式(只需確認,不輸入)。BT_SSP_VARIANT_PASSKEY_CONFIRMATION → 數字比較模式(確認6位數字相同)。num_val 就是要比較的那6位數字。*/invoke_ssp_request_cb(bd_addr, bd_name, cod,(p_ssp_cfm_req->just_works ? BT_SSP_VARIANT_CONSENT: BT_SSP_VARIANT_PASSKEY_CONFIRMATION),p_ssp_cfm_req->num_val);
}
步驟 | 動作 |
---|---|
1 | 判斷是否為當前正在配對的設備,若不是則拒絕 |
2 | 更新設備類型、屬性 |
3 | 設置 pairing_cb 結構體狀態 |
4 | 如果是 JustWorks 且臨時設備、被動連接,直接自動接受 |
5 | 如果需要用戶確認(JustWorks 或 Passkey Compare),則通知 Java 層 UI 確認 |
6 | 設置配對類型、認證信息等上下文,準備后續配對流程 |
01-02 14:20:56.052840 2146 3220 I bt_btm : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: BTM_SP_CFM_REQ_EVT: num_val: 363625
01-02 14:20:56.052917 2146 3220 I bt_btm : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btm_proc_sp_req_evt() just_works:0, io loc:1, rmt:1, auth loc:3, rmt:3
01-02 14:20:56.052933 2146 3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: bta_dm_sp_cback: 2
01-02 14:20:56.052948 2146 3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: btif_dm_upstreams_evt: ev: BTA_DM_SP_CFM_REQ_EVT
01-02 14:20:56.052958 2146 3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_dm_ssp_cfm_req_evt
01-02 14:20:56.052999 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:641 btif_update_remote_properties: class of device (cod) is 0x5a020c
01-02 14:20:56.053026 2146 3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_in_fetch_bonded_ble_device Found a LE device: 70:8f:47:91:b0:62
01-02 14:20:56.053116 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:462 get_cod: get_cod remote_cod = 0x005a020c
01-02 14:20:56.053294 2146 2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: operator(): HAL bt_hal_cbacks->remote_device_properties_cb
01-02 14:20:56.053394 2146 3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: btif_dm_ssp_cfm_req_evt: just_works:0, loc_auth_req=3, rmt_auth_req=3
01-02 14:20:56.053424 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:974 btif_dm_ssp_cfm_req_evt: sdp_attempts = 0
01-02 14:20:56.053441 2146 3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: dm status: 1
01-02 14:20:56.053461 2146 2634 I BluetoothRemoteDevices: Property type: 1
01-02 14:20:56.053486 2146 2634 I BluetoothRemoteDevices: Skip name update for 70:8F:47:91:B0:62
01-02 14:20:56.053525 2146 2634 I BluetoothRemoteDevices: Property type: 4
01-02 14:20:56.053559 2146 2634 I BluetoothRemoteDevices: Skip class update for 70:8F:47:91:B0:62
01-02 14:20:56.053570 2146 2634 I BluetoothRemoteDevices: Property type: 5
01-02 14:20:56.053601 2146 2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: operator(): HAL bt_hal_cbacks->ssp_request_cb
01-02 14:20:56.053739 2146 2634 I BluetoothBondStateMachine: sspRequestCallback: [B@c3ee023 name: [B@d224920 cod: 5898764 pairingVariant 0 passkey: 363625
01-02 14:20:56.055029 2146 2719 D BluetoothBondStateMachine: PendingCommandState: processMessage msg.what=5 dev=70:8F:47:91:B0:62 (cbx)
4. 連接階段(Connection)
1. 作用
-
在連接發起前,查詢:
-
是否已配對
-
是否支持某 UUID
-
是否要求安全連接
-
-
用于 Profile 判斷:
-
是否建立連接
-
是否先 SDP
-
是否強制加密通道
-
2. 解決的問題
問題 | 如何解決 |
---|---|
每次連接都等待 SDP 完成 | 使用緩存 UUID 快速判斷 |
Profile 初始化失敗 | 利用緩存狀態預檢能力 |
重復連接失敗 | 使用緩存 Key 與安全屬性重用連接通道 |
3. 代碼鑒賞
當 app 層調用 BluetoothDevice.connect 時,將會觸發 bt.server 調用
// android/app/src/com/android/bluetooth/btservice/AdapterService.javaprivate int connectEnabledProfiles(BluetoothDevice device) {ParcelUuid[] remoteDeviceUuids = getRemoteUuids(device); // 會先獲取 該設備的 uuid 信息。 該信息是在 SDP 階段 上報上來的。ParcelUuid[] localDeviceUuids = mAdapterProperties.getUuids();// 如果 遠端和 本地 都支持 a2dp 那么就連接 a2dpif (mA2dpService != null && isSupported(localDeviceUuids, remoteDeviceUuids,BluetoothProfile.A2DP, device)) {Log.i(TAG, "connectEnabledProfiles: Connecting A2dp");mA2dpService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_AUTOCONNECT);mA2dpService.connect(device);}...}public ParcelUuid[] getRemoteUuids(BluetoothDevice device) {DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);if (deviceProp == null) {return null;}return deviceProp.getUuids();}
// android/app/src/com/android/bluetooth/btservice/RemoteDevices.javaParcelUuid[] getUuids() {synchronized (mObject) {return mUuids;}}
- 下面是 sdp 完成后, 上報 uuid 的日志
01-02 14:21:00.149859 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1696 btif_dm_search_services_evt: SDP search done for 70:8f:47:91:b0:62
01-02 14:21:00.149882 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1698 btif_dm_search_services_evt: sdp_attempts = 0
01-02 14:21:00.149891 2146 3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1728 btif_dm_search_services_evt: clearing btif pairing_cb
01-02 14:21:00.150000 2146 3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_in_fetch_bonded_ble_device Found a LE device: 70:8f:47:91:b0:62
01-02 14:21:00.153761 723 723 I V4L2CameraHAL: V4L2CameraHAL:V4L2CameraHAL:71: ais_v4l2_proxy not ready. try again.
01-02 14:21:00.159003 2146 3220 I bt_bta_dm: packages/modules/Bluetooth/system/bta/dm/bta_dm_act.cc:1371 bta_dm_search_cmpl: No BLE connection, processing classic results
01-02 14:21:00.159092 2146 2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: operator(): HAL bt_hal_cbacks->remote_device_properties_cb
01-02 14:21:00.159240 2146 2634 I BluetoothRemoteDevices: Property type: 3
01-02 14:21:00.159360 2146 2634 I BluetoothAdapterService: sendUuidsInternal: Received service discovery UUIDs for device 70:8F:47:91:B0:62
01-02 14:21:00.159403 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=0 uuid=00001105-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159436 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=1 uuid=0000110a-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159462 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=2 uuid=0000110c-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159493 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=3 uuid=0000110e-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159558 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=4 uuid=00001112-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159587 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=5 uuid=00001115-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159607 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=6 uuid=00001116-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159628 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=7 uuid=0000111f-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159694 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=8 uuid=0000112d-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159725 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=9 uuid=0000112f-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159746 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=10 uuid=00001132-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159766 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=11 uuid=00001200-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159792 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=12 uuid=2c042b0a-7f57-4c0a-afcf-1762af70257c
01-02 14:21:00.159815 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=13 uuid=8fa9c715-bd1f-596c-a1b0-13162b15c892
01-02 14:21:00.159835 2146 2634 D BluetoothAdapterService: sendUuidsInternal: index=14 uuid=9fed64fd-e91a-499e-88dd-73dfe023feed
01-02 14:21:00.160431 2146 2719 D BluetoothBondStateMachine: StableState: processMessage msg.what=10 dev=70:8F:47:91:B0:62 (cbx)
01-02 14:21:00.161052 2048 2487 D BluetoothDevice: getUuids()
01-02 14:21:00.162895 2146 2719 I BluetoothBondStateMachine: Bond State Change Intent:70:8F:47:91:B0:62 (cbx) BOND_BONDING => BOND_BONDED
void devicePropertyChangedCallback(byte[] address, int[] types, byte[][] values) {Intent intent;byte[] val;int type;BluetoothDevice bdDevice = getDevice(address);DeviceProperties deviceProperties;if (bdDevice == null) {debugLog("Added new device property");deviceProperties = addDeviceProperties(address);bdDevice = getDevice(address);} else {deviceProperties = getDeviceProperties(bdDevice);}if (types.length <= 0) {errorLog("No properties to update");return;}for (int j = 0; j < types.length; j++) {type = types[j];val = values[j];if (val.length > 0) {synchronized (mObject) {infoLog("Property type: " + type);switch (type) {...case AbstractionLayer.BT_PROPERTY_UUIDS: // 0x03int numUuids = val.length / AbstractionLayer.BT_UUID_SIZE;final ParcelUuid[] newUuids = Utils.byteArrayToUuid(val);//ParcelUuid[] uuids = updateUuids(deviceProperties.mUuids, newUuids);if (areUuidsEqual(newUuids, deviceProperties.mUuids)) {infoLog( "Skip uuids update for " + bdDevice.getAddress());break;}deviceProperties.mUuids = newUuids;if (sAdapterService.getState() == BluetoothAdapter.STATE_ON) {sAdapterService.deviceUuidUpdated(bdDevice);sendUuidIntent(bdDevice, deviceProperties);} else if (sAdapterService.getState()== BluetoothAdapter.STATE_BLE_ON) {sAdapterService.deviceUuidUpdated(bdDevice);}break;...}}}}}
5. 重點問題
1. 搜索(Discovery)階段不會寫入 bt_stack.conf
的原因
1. 目的:快速獲取周圍設備信息,不持久化
-
搜索時獲取到的是臨時的廣播信息(例如設備名稱、地址、Class of Device、RSSI 等),系統主要用于UI 展示或后續配對流程判斷。
-
尚未配對(bond)或連接的設備,不需要寫入配置文件,因為:
-
這些信息隨時可能變化;
-
搜索結果可能包含大量設備,不宜持久化;
-
節省寫入頻率,減少 I/O 消耗和 flash 損耗;
-
避免保存不可信或無效設備。
-
2. 那什么時候才會寫入 bt_config.conf
或 bt_stack.conf
?
設備信息只有在以下場景中,才會被持久化到 bt_config.conf
(舊系統)或 bt_stack.conf
(AOSP 13 后被 StorageModule
管理)中:
1. 配對成功(Bonding Success)
-
成功配對后,系統將設備標記為“可信設備”(bonded),會記錄:
-
地址(MAC)
-
設備名稱
-
連接方式(BR/EDR 或 LE)
-
link key / LE key(加密用)
-
profile 支持(如 A2DP、HID 等)
-
services UUIDs(通常來自 SDP)
-
2. SDP 完成后(部分 Profile 使用)
-
若設備支持 classic profile,在配對后通常會進行 SDP 查詢服務信息(如 A2DP、HFP)。
-
得到的 UUID 信息、PSM、版本等,也會被寫入配置中供下次連接使用。
3. 主動連接成功
- 即使未配對,但連接了某些 BLE 服務,系統也可能緩存部分 GATT 服務信息用于快速重連,但不一定寫入 config 文件。
2. StorageModule 與 config 文件的關系(AOSP 13+)
在 AOSP 13 中,DeviceProperties
模塊已被 StorageModule
接管持久化工作:
-
StorageModule
是新的統一設備信息存取接口。 -
bt_config.conf
文件依然存在于/data/misc/bluedroid/
,但讀寫是通過StorageModule
來完成的。 -
StorageModule
會在合適的時機(如配對成功、服務發現)將設備信息寫入文件。
3. 總結重點
階段 | 是否寫入配置文件(bt_config.conf / bt_stack.conf) | 說明 |
---|---|---|
🔍 搜索階段 | ? 不寫入 | 信息臨時,僅用于展示 |
🤝 配對成功 | ? 寫入 | 標志為可信設備,保存 key 和 profile |
📡 SDP 成功 | ? 寫入 | 保存服務 UUID 等 |
🔌 BLE 連接 | 可能寫入(部分緩存) | 依 profile 情況而定 |
📁 存儲模塊 | 由 StorageModule 統一管理配置 | 負責設備信息加載/保存 |
6. 總結
階段 | 作用 | 是否寫入配置 | 解決的問題 |
---|---|---|---|
搜索 | 緩存名稱/類型/RSSI,避免重復 | 否 | 設備顯示混亂、信息缺失 |
SDP | 緩存支持 UUID | 是 | 提前判斷服務能力 |
配對 | 緩存 bond/key、安全能力 | 是 | 配對信息持久化,安全連接判斷 |
連接 | 提供連接前判斷支持 | 否(使用緩存) | 連接初始化優化,profile 可預判 |
DeviceProperties
是 AOSP 藍牙系統中的“設備狀態中臺”,統一緩存、更新并與StorageModule
配合寫入持久化文件,確保設備從被發現 → 服務識別 → 配對 → 連接全過程中,系統始終擁有最新、穩定、可持續利用的設備狀態數據。