【android bluetooth 框架分析 04】【bt-framework 層詳解 8】【DeviceProperties介紹】

前面我們提到了 藍牙協議棧中的 Properties , 這篇文章是 他的補充。

  • 【android bluetooth 框架分析 04】【bt-framework 層詳解 6】【Properties介紹】

1. 設計初衷與核心問題

1. 為什么要設計 DeviceProperties

在 Android 藍牙實際使用中,系統需反復處理設備的發現、服務解析、配對、連接等場景。在這些過程中,遠程設備的信息管理混亂、數據缺失、不一致是普遍存在的問題。

2. DeviceProperties 解決的問題:

場景待解決問題設計目標
搜索同一設備多次出現在列表中,名稱等信息丟失唯一標識設備、統一管理搜索信息
SDP每次連接都重新做服務發現,耗時、重復緩存 UUID,提高連接效率
配對配對狀態混亂、無法判斷安全能力緩存密鑰與能力,便于復用
連接無法快速判斷設備是否支持某 profile統一緩存 profile 能力與狀態

因此,AOSP 中通過 DeviceProperties 實現了一個 以 MAC 地址為主鍵的遠程設備狀態緩存中心,并與 StorageModule 聯動實現持久化。


2. DeviceProperties 模塊設計概述

1. 核心職責

功能說明
緩存設備屬性設備名稱、類型、Class of Device、UUID、RSSI、Bond 狀態、安全能力、Link Key 等
提供統一讀寫接口供 btif 層、profile 層、JNI 層查詢與設置設備狀態
StorageModule 協作持久化關鍵屬性寫入 bt_config.conf 配置文件,保證系統重啟后信息不丟失

2. 數據存儲結構

每個遠程設備(用 address 唯一標識)對應一個 DeviceProperties 實例,核心字段如:

android/app/src/com/android/bluetooth/btservice/RemoteDevices.javaclass DeviceProperties {private String mName;private byte[] mAddress;private String mIdentityAddress;private boolean mIsConsolidated = false;private int mBluetoothClass = BluetoothClass.Device.Major.UNCATEGORIZED;private int mBredrConnectionHandle = BluetoothDevice.ERROR;private int mLeConnectionHandle = BluetoothDevice.ERROR;private short mRssi;private String mAlias;private BluetoothDevice mDevice;private boolean mIsBondingInitiatedLocally;private int mBatteryLevelFromHfp = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;private int mBatteryLevelFromBatteryService = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;private boolean mIsCoordinatedSetMember;private int mAshaCapability;private int mAshaTruncatedHiSyncId;private String mModelName;@VisibleForTesting int mBondState;@VisibleForTesting int mDeviceType;@VisibleForTesting ParcelUuid[] mUuids;private BluetoothSinkAudioPolicy mAudioPolicy;...
}

1. 創建 DeviceProperties 對象

創建 DeviceProperties 的地方:

android/app/src/com/android/bluetooth/btservice/RemoteDevices.javaDeviceProperties addDeviceProperties(byte[] address) {synchronized (mDevices) {DeviceProperties prop = new DeviceProperties();  // 1. 創建 DeviceProperties 對象prop.setDevice(sAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)));prop.setAddress(address);String key = Utils.getAddressStringFromByte(address); // 2.key 是 mac 地址DeviceProperties pv = mDevices.put(key, prop); // 保存在 RemoteDevices.mDevices 中...return prop;}}

java 層在如下場景中,會調用 addDeviceProperties 創建一個 DeviceProperties 對象:

  1. AdapterProperties.adapterPropertyChangedCallback:BT_PROPERTY_ADAPTER_BONDED_DEVICES
    • 在打開藍牙時, AdapterProperties 會收到 BT_PROPERTY_ADAPTER_BONDED_DEVICES 事件;此時會將之前 已經配對的設備 封裝為一個個 DeviceProperties 對象。
  2. BondStateMachine.sspRequestCallback
    • 設備配對時支持 SSP 模式,進行確認、比較、輸入密鑰等操作時觸發
  3. BondStateMachine.pinRequestCallback
    • 當連接傳統藍牙設備(BR/EDR)時需要輸入 PIN 碼進行配對時觸發
  4. RemoteDevices.devicePropertyChangedCallback
    • 當 設備 屬性發生變化時, 從 native -> java 上報設備信息時,如果找不到對應設備的 Property 將新建一個。

2. 管理那些屬性:

在 【android bluetooth 框架分析 04】【bt-framework 層詳解 6】【Properties介紹】 中有詳細介紹。

枚舉常量說明使用范圍數據類型訪問權限
🔁 適用于 Adapter 和 Remote Device
BT_PROPERTY_BDNAME設備名稱Adapter: 讀/寫Remote Device: 只讀bt_bdname_tGET / SET(Adapter)GET(Remote)
BT_PROPERTY_BDADDR設備地址Adapter & Remote DeviceRawAddressGET
BT_PROPERTY_UUIDS支持的服務 UUID 列表Remote Devicebluetooth::Uuid[]GET
BT_PROPERTY_CLASS_OF_DEVICE類別碼Remote Deviceuint32_tGET
BT_PROPERTY_TYPE_OF_DEVICE設備類型(BR/EDR/LE)Remote Devicebt_device_type_tGET
BT_PROPERTY_SERVICE_RECORD服務記錄Remote Devicebt_service_record_tGET
枚舉常量說明使用范圍數據類型訪問權限
📡 僅適用于 Remote Device(遠程設備)
BT_PROPERTY_REMOTE_FRIENDLY_NAME遠程設備名稱(用戶設定)Remote Devicebt_bdname_tGET / SET
BT_PROPERTY_REMOTE_RSSI遠程設備 RSSIRemote Deviceint8_tGET
BT_PROPERTY_REMOTE_VERSION_INFO遠程設備協議版本信息Remote Devicebt_remote_version_tGET / SET
BT_PROPERTY_REMOTE_IS_COORDINATED_SET_MEMBER是否是協同設備成員Remote DeviceboolGET
BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP屬性刷新時間戳Remote Deviceint64_t(或自定義)GET

3. native -> java callback

在 搜索、 配對 、 sdp 的過程中,native 在不同階段都會觸發 回調 到 java 層,來更新 DeviceProperties .

協議棧會通過 下面兩個函數來, 層層 上報 屬性到 java 層:

  1. invoke_device_found_cb
  2. invoke_remote_device_properties_cb

接下來我們梳理一下 他們的調用邏輯。

1. invoke_device_found_cb & invoke_remote_device_properties_cb
// system/btif/src/bluetooth.ccvoid invoke_device_found_cb(int num_properties, bt_property_t* properties) {do_in_jni_thread(FROM_HERE,base::BindOnce([](int num_properties, bt_property_t* properties) {HAL_CBACK(bt_hal_cbacks, device_found_cb,num_properties, properties); // 調用 jni 函數if (properties) {osi_free(properties);}},num_properties,property_deep_copy_array(num_properties, properties)));
}void invoke_remote_device_properties_cb(bt_status_t status, RawAddress bd_addr,int num_properties,bt_property_t* properties) {do_in_jni_thread(FROM_HERE, base::BindOnce([](bt_status_t status, RawAddress bd_addr,int num_properties, bt_property_t* properties) {HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb,status, &bd_addr, num_properties, properties);  // 調用 jni 函數if (properties) {osi_free(properties);}},status, bd_addr, num_properties,property_deep_copy_array(num_properties, properties)));
}

// android/app/jni/com_android_bluetooth_btservice_AdapterService.cpptypedef struct {...remote_device_properties_callback remote_device_properties_cb;device_found_callback device_found_cb;...
} bt_callbacks_t;static bt_callbacks_t sBluetoothCallbacks = {sizeof(sBluetoothCallbacks),adapter_state_change_callback,adapter_properties_callback,remote_device_properties_callback,device_found_callback,...};
  • HAL_CBACK(bt_hal_cbacks, device_found_cb, num_properties, properties);
    • 調用的就是 om_android_bluetooth_btservice_AdapterService.cpp::device_found_callback
  • HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, status, &bd_addr, num_properties, properties);
    • 調用的就是 om_android_bluetooth_btservice_AdapterService.cpp::remote_device_properties_callback
// android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static void device_found_callback(int num_properties,bt_property_t* properties) {CallbackEnv sCallbackEnv(__func__);if (!sCallbackEnv.valid()) return;ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), NULL);int addr_index;for (int i = 0; i < num_properties; i++) {if (properties[i].type == BT_PROPERTY_BDADDR) {addr.reset(sCallbackEnv->NewByteArray(properties[i].len));if (!addr.get()) {ALOGE("Address is NULL (unable to allocate) in %s", __func__);return;}sCallbackEnv->SetByteArrayRegion(addr.get(), 0, properties[i].len,(jbyte*)properties[i].val);addr_index = i;}}if (!addr.get()) {ALOGE("Address is NULL in %s", __func__);return;}ALOGV("%s: Properties: %d, Address: %s", __func__, num_properties,(const char*)properties[addr_index].val);remote_device_properties_callback(BT_STATUS_SUCCESS,(RawAddress*)properties[addr_index].val,num_properties, properties); // 1. sCallbackEnv->CallVoidMethod(sJniCallbacksObj, method_deviceFoundCallback,addr.get()); // 回調到 java 層
}
// android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static void remote_device_properties_callback(bt_status_t status,RawAddress* bd_addr,int num_properties,bt_property_t* properties) {CallbackEnv sCallbackEnv(__func__);if (!sCallbackEnv.valid()) return;ALOGV("%s: Status is: %d, Properties: %d", __func__, status, num_properties);if (status != BT_STATUS_SUCCESS) {ALOGE("%s: Status %d is incorrect", __func__, status);return;}ScopedLocalRef<jbyteArray> val(sCallbackEnv.get(),(jbyteArray)sCallbackEnv->NewByteArray(num_properties));if (!val.get()) {ALOGE("%s: Error allocating byteArray", __func__);return;}ScopedLocalRef<jclass> mclass(sCallbackEnv.get(),sCallbackEnv->GetObjectClass(val.get()));/* Initialize the jobjectArray and jintArray here itself and send theinitialized array pointers alone to get_properties */ScopedLocalRef<jobjectArray> props(sCallbackEnv.get(),sCallbackEnv->NewObjectArray(num_properties, mclass.get(), NULL));if (!props.get()) {ALOGE("%s: Error allocating object Array for properties", __func__);return;}ScopedLocalRef<jintArray> types(sCallbackEnv.get(), (jintArray)sCallbackEnv->NewIntArray(num_properties));if (!types.get()) {ALOGE("%s: Error allocating int Array for values", __func__);return;}ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));if (!addr.get()) {ALOGE("Error while allocation byte array in %s", __func__);return;}sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),(jbyte*)bd_addr);jintArray typesPtr = types.get();jobjectArray propsPtr = props.get();if (get_properties(num_properties, properties, &typesPtr, &propsPtr) < 0) {return;}sCallbackEnv->CallVoidMethod(sJniCallbacksObj,method_devicePropertyChangedCallback, addr.get(),types.get(), props.get()); // 回調到 java 層
}

// android/app/jni/com_android_bluetooth_btservice_AdapterService.cppmethod_devicePropertyChangedCallback = env->GetMethodID(jniCallbackClass, "devicePropertyChangedCallback", "([B[I[[B)V");method_deviceFoundCallback =env->GetMethodID(jniCallbackClass, "deviceFoundCallback", "([B)V");
// android/app/src/com/android/bluetooth/btservice/JniCallbacks.javavoid devicePropertyChangedCallback(byte[] address, int[] types, byte[][] val) {mRemoteDevices.devicePropertyChangedCallback(address, types, val);}void deviceFoundCallback(byte[] address) {mRemoteDevices.deviceFoundCallback(address);}
2. RemoteDevices.devicePropertyChangedCallback

是 Java 層對 native 層 method_devicePropertyChangedCallback 的響應回調,用于更新本地記錄的遠程藍牙設備屬性。

// android/app/src/com/android/bluetooth/btservice/RemoteDevices.java/*address:遠程設備的 MAC 地址(byte[] 格式)types:屬性類型數組(int 值,參照 AbstractionLayer.BT_PROPERTY_* 常量定義)values:每個屬性對應的值(數組形式,一一對應)*/void devicePropertyChangedCallback(byte[] address, int[] types, byte[][] values) {Intent intent;byte[] val;int type;BluetoothDevice bdDevice = getDevice(address);DeviceProperties deviceProperties;/*如果是第一次收到該地址設備的屬性變更,說明是新設備,需添加。DeviceProperties 是系統對一個遠程設備的本地屬性封裝類。*/if (bdDevice == null) {debugLog("Added new device property");deviceProperties = addDeviceProperties(address); // 創建新的 DevicePropertiesbdDevice = getDevice(address);} else {deviceProperties = getDeviceProperties(bdDevice); // 再次獲取 BluetoothDevice 實例}// 無屬性則退出。if (types.length <= 0) {errorLog("No properties to update");return;}// 遍歷所有變更的屬性for (int j = 0; j < types.length; j++) {type = types[j];val = values[j];if (val.length > 0) {synchronized (mObject) { // 同步鎖:避免并發問題infoLog("Property type: " + type);// 根據屬性類型更新具體字段switch (type) {case AbstractionLayer.BT_PROPERTY_BDNAME: // 設備名稱final String newName = new String(val);if (newName.equals(deviceProperties.getName())) {infoLog("Skip name update for " + bdDevice);break;}deviceProperties.setName(newName);// 廣播設備名稱改變事件intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED);intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice);intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProperties.getName());intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT,Utils.getTempAllowlistBroadcastOptions());infoLog("Remote Device name is: " + deviceProperties.getName());break;case AbstractionLayer.BT_PROPERTY_REMOTE_FRIENDLY_NAME: // 用戶自定義名稱(Alias)deviceProperties.setAlias(bdDevice, new String(val));infoLog("Remote device alias is: " + deviceProperties.getAlias());break;case AbstractionLayer.BT_PROPERTY_BDADDR: // 設備地址deviceProperties.setAddress(val);infoLog("Remote Address is:" + Utils.getAddressStringFromByte(val));break;// 設備類型標識(例如:手機、耳機)case AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE: final int newClass = Utils.byteArrayToInt(val);if (newClass == deviceProperties.getBluetoothClass()) {infoLog("Skip class update for " + bdDevice);break;}deviceProperties.setBluetoothClass(newClass);// 廣播 class 改變事件intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED);intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice);intent.putExtra(BluetoothDevice.EXTRA_CLASS,new BluetoothClass(deviceProperties.getBluetoothClass()));intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT,Utils.getTempAllowlistBroadcastOptions());infoLog("Remote class is:" + newClass);break;case AbstractionLayer.BT_PROPERTY_UUIDS: // 支持的 Profile UUID , SDP 發現結束后會通過此字段更新支持的 Profile,如 A2DP、HFP。int numUuids = val.length / AbstractionLayer.BT_UUID_SIZE;final ParcelUuid[] newUuids = Utils.byteArrayToUuid(val);//ParcelUuid[] uuids = updateUuids(deviceProperties.mUuids, newUuids);if (areUuidsEqual(newUuids, deviceProperties.mUuids)) {infoLog( "Skip uuids update for " + bdDevice.getAddress());break;}deviceProperties.mUuids = newUuids;if (sAdapterService.getState() == BluetoothAdapter.STATE_ON) {sAdapterService.deviceUuidUpdated(bdDevice);sendUuidIntent(bdDevice, deviceProperties);} else if (sAdapterService.getState()== BluetoothAdapter.STATE_BLE_ON) {sAdapterService.deviceUuidUpdated(bdDevice);}break;case AbstractionLayer.BT_PROPERTY_TYPE_OF_DEVICE:// 設備連接類型(BR/EDR, LE, Dual)if (deviceProperties.isConsolidated()) {break;}// The device type from hal layer, defined in bluetooth.h,// matches the type defined in BluetoothDevice.javadeviceProperties.setDeviceType(Utils.byteArrayToInt(val));break;case AbstractionLayer.BT_PROPERTY_REMOTE_RSSI: // 信號強度// RSSI from hal is in one bytedeviceProperties.setRssi(val[0]);break;case AbstractionLayer.BT_PROPERTY_REMOTE_IS_COORDINATED_SET_MEMBER:// 是否屬于 Coordinated SetdeviceProperties.setIsCoordinatedSetMember(val[0] != 0);break;}}}}}
功能點說明
📥 入口native 層通知 Java 層遠程設備屬性有更新(如名稱、class、UUID、RSSI)
🗃? 存儲更新本地 Java 層緩存(DeviceProperties 對象)
📢 廣播對關鍵屬性(名稱、class、UUID)變化發送系統廣播
💾 保存最終可能通過 StorageModule 寫入 bt_config.conf(如 UUID、Alias、Class)
🧩 用途支持 UI 展示、連接判斷、profile 支持判斷等
3. RemoteDevices.deviceFoundCallback

此函數是在設備被掃描到時由 native 層調用 Java 層,屬于藍牙設備發現流程的重要組成部分。

當藍牙發現流程(Inquiry 或 LE Scan)中發現了一個新設備或再次發現舊設備時,會觸發此回調。它的職責是:

  • 獲取設備信息

  • 根據系統配置和策略決定是否廣播設備發現

  • 通過 ACTION_FOUND 廣播通知系統和應用


// android/app/src/com/android/bluetooth/btservice/RemoteDevices.java// Native 層通過 JNI 調用 Java 層,傳入遠程設備的地址(6 字節 MAC 地址)。void deviceFoundCallback(byte[] address) {// The device properties are already registered - we can send the intent// now 根據 MAC 地址獲取或創建 BluetoothDevice 對象。BluetoothDevice device = getDevice(address);infoLog("deviceFoundCallback: Remote Address is:" + device);// 獲取設備的本地屬性封裝對象(DeviceProperties),包含設備名稱、class、RSSI 等。DeviceProperties deviceProp = getDeviceProperties(device);if (deviceProp == null) {// 如果屬性為空(很罕見,可能是同步未完成),直接返回。errorLog("Device Properties is null for Device:" + device);return;}// 檢查是否開啟“限制無名稱設備廣播”策略boolean restrict_device_found =SystemProperties.getBoolean("bluetooth.restrict_discovered_device.enabled", false);if (restrict_device_found && (deviceProp.mName == null || deviceProp.mName.isEmpty())) {// 讀取系統屬性,如果為 true,表示系統不希望廣播沒有名字的設備(可用于節能或隱私控制)// 如果設備沒有名字,并且限制策略開啟,則不廣播此設備。debugLog("Device name is null or empty: " + device);return;}/*應用層級過濾(如阻止某些設備類型)filterDevice() 是系統或廠商定制的設備過濾邏輯(如過濾黑名單、特殊廠商設備等)。若返回 true,跳過此設備廣播。*/if (filterDevice(device)) {warnLog("Not broadcast Device: " + device);return;}infoLog("device:" + device + " adapterIndex=" + device.getAdapterIndex());// 創建用于通知發現設備的廣播事件。Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); // 附加設備對象本身。intent.putExtra(BluetoothDevice.EXTRA_CLASS,new BluetoothClass(deviceProp.getBluetoothClass())); // 附加設備類型(如手機、耳機、電腦等)intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.getRssi()); // 附加設備的信號強度。intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.getName()); // 附加設備名稱。intent.putExtra(BluetoothDevice.EXTRA_IS_COORDINATED_SET_MEMBER,deviceProp.isCoordinatedSetMember()); // 附加是否屬于 Coordinated Set(藍牙 5.2+ 中用于群組播放,如 TWS 左右耳同時控制)。/*廣播發出發出帶權限限制的廣播,只有持有 BLUETOOTH_SCAN 權限的應用可以接收該廣播。使用 sendBroadcastMultiplePermissions() 是對 sendBroadcast() 的擴展,支持多權限、支持臨時廣播策略(如廣播延遲/前臺優先級等)。*/sAdapterService.sendBroadcastMultiplePermissions(intent,new String[] { BLUETOOTH_SCAN },Utils.getTempBroadcastOptions());}
階段描述
1?? 獲取根據 address 獲取 BluetoothDevice 和 DeviceProperties
2?? 檢查是否開啟了過濾策略(無名稱設備、特定設備過濾)
3?? 構建創建廣播 intent 并附加設備屬性
4?? 廣播向系統發送 ACTION_FOUND 廣播,僅供授權應用接收

應用場景:

  • 當用戶在設置界面打開藍牙并點擊“掃描設備”時,后臺會多次觸發 deviceFoundCallback()。
  • App 中注冊了 ACTION_FOUND 廣播接收器后,可以接收到附近設備并展示在列表上。
4. 小結

協議棧 native 側,會觸發上面的兩路 回調, 但是他們 所代表 的含義卻是不同的:

  • invoke_device_found_cb[native] -> RemoteDevices.deviceFoundCallback[java]
  • invoke_remote_device_properties_cb[native] -> RemoteDevices.devicePropertyChangedCallback[java]

devicePropertyChangedCallbackdeviceFoundCallback 是 AOSP 藍牙框架中兩個核心的回調函數,雖然它們都與設備屬性和發現有關,但它們在觸發時機、作用、廣播內容、應用場景等方面都有明顯差異。

下面從多個維度對 相同點與不同點 進行詳細對比:


1.相同點
維度描述
🔧 來源都是由 native 層(通過 JNI)調用 Java 層的回調函數
📡 與設備相關都涉及對某個 BluetoothDevice 設備的處理
🧠 依賴 DeviceProperties都通過 getDeviceProperties(device) 獲取設備緩存屬性
🔒 權限控制廣播時都依賴藍牙相關權限(如 BLUETOOTH_SCAN
📲 可導致廣播都有可能向上層發送 Android 廣播(如 ACTION_FOUND, ACTION_NAME_CHANGED, ACTION_UUID, 等)
🧪 開發調試中常出現都會在使用藍牙調試(如配對、掃描)過程中頻繁觸發
🧩 與應用層交互都可能引發第三方 app 的回調(通過廣播接收器)

2.不同點
比較維度deviceFoundCallbackdevicePropertyChangedCallback
💥 觸發時機當藍牙掃描發現設備時調用(第一次或再次發現)當遠程設備的屬性發生變化時調用(如名稱、RSSI、UUID 等)
🔁 調用頻率在一次掃描過程中可能多次觸發(每個設備發現一次)屬性每變化一次觸發一次,可能頻繁(如 RSSI 不斷變化)
📩 廣播行為廣播 BluetoothDevice.ACTION_FOUND(設備被發現)根據屬性類型廣播不同事件,如:
  • ACTION_NAME_CHANGED
  • ACTION_UUID
  • ACTION_RSSI
🔍 目的表示“新設備”被發現,通知系統和應用顯示表示“已知設備的屬性”發生變化,更新狀態或 UI
🧬 廣播攜帶信息BluetoothDevice、設備類型、名稱、RSSI、是否為群組成員取決于變化的屬性(可能是 UUID、名稱、RSSI)
🧰 過濾策略參與參與“是否廣播”策略(如名稱為空不廣播)不參與過濾,始終處理屬性變化
📲 典型場景藍牙設置頁中設備掃描列表展示已配對設備列表中設備名稱、信號變化,或配對時獲取 UUID
💡 是否依賴掃描流程是,僅在藍牙掃描流程中調用否,也可能在連接、配對、服務發現后調用

3.實際生活中的類比
情況deviceFoundCallbackdevicePropertyChangedCallback
你在商場里發現一個新品牌店鋪店鋪出現在你面前的那一刻 —— “發現設備”店鋪換了名字、裝修風格變了、換老板了、上了新的商品 —— “屬性改變”
手機藍牙設置中掃描時發現設備列表刷新每個設備出現一次觸發一次某設備名稱更新或信號強度變化,刷新其展示項

4.應用開發建議
目標使用哪個回調
想監聽設備是否被發現(用于展示設備列表)BluetoothDevice.ACTION_FOUND 廣播(源自 deviceFoundCallback
想監聽某設備名稱是否變更(如設備重命名)BluetoothDevice.ACTION_NAME_CHANGED(源自 devicePropertyChangedCallback
想獲取設備的 UUID(服務)更新BluetoothDevice.ACTION_UUID(源自 devicePropertyChangedCallback
想實時顯示設備信號強度(如附近藍牙設備距離)BluetoothDevice.EXTRA_RSSI(由 devicePropertyChangedCallback 中 RSSI 更新引發)

3. 系統接口

關于 DevicePropertyNative 有如下幾個接口:

  • android/app/src/com/android/bluetooth/btservice/AdapterService.java
/*package*/native boolean setDevicePropertyNative(byte[] address, int type, byte[] val);/*package*/native boolean getDevicePropertyNative(byte[] address, int type);

1. Get 流程

getDevicePropertyNative

android/app/src/com/android/bluetooth/btservice/AdapterService.java/*package*/native boolean getDevicePropertyNative(byte[] address, int type);
// android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static jboolean getDevicePropertyNative(JNIEnv* env, jobject obj,jbyteArray address, jint type) {ALOGV("%s", __func__);if (!sBluetoothInterface) return JNI_FALSE;jbyte* addr = env->GetByteArrayElements(address, NULL);if (addr == NULL) {jniThrowIOException(env, EINVAL);return JNI_FALSE;}int ret = sBluetoothInterface->get_remote_device_property((RawAddress*)addr, (bt_property_type_t)type);env->ReleaseByteArrayElements(address, addr, 0);return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
system/btif/src/bluetooth.ccint get_remote_device_property(RawAddress* remote_addr,bt_property_type_t type) {if (!btif_is_enabled()) return BT_STATUS_NOT_READY;do_in_main_thread(FROM_HERE, base::BindOnce(btif_get_remote_device_property,*remote_addr, type));return BT_STATUS_SUCCESS;
}
system/btif/src/btif_core.cc/********************************************************************************* Function         btif_get_remote_device_property** Description      Fetches the remote device property from the NVRAM*******************************************************************************/
void btif_get_remote_device_property(RawAddress remote_addr,bt_property_type_t type) {char buf[1024];bt_property_t prop;prop.type = type;prop.val = (void*)buf;prop.len = sizeof(buf);bt_status_t status =btif_storage_get_remote_device_property(&remote_addr, &prop);invoke_remote_device_properties_cb(status, remote_addr, 1, &prop); // 1. 
}

invoke_remote_device_properties_cb


// system/btif/src/btif_core.cc
void btif_remote_properties_evt(bt_status_t status, RawAddress* remote_addr,uint32_t num_props, bt_property_t* p_props) {invoke_remote_device_properties_cb(status, *remote_addr, num_props, p_props);
}

2. 寫配置

btif_storage_add_remote_device

system/btif/src/btif_storage.ccbt_status_t btif_storage_add_remote_device(const RawAddress* remote_bd_addr,uint32_t num_properties,bt_property_t* properties) {uint32_t i = 0;/* TODO: If writing a property, fails do we go back undo the earlier* written properties? */for (i = 0; i < num_properties; i++) {/* Ignore the RSSI as this is not stored in DB */if (properties[i].type == BT_PROPERTY_REMOTE_RSSI) continue;/* address for remote device needs special handling as we also store* timestamp */if (properties[i].type == BT_PROPERTY_BDADDR) {bt_property_t addr_prop;memcpy(&addr_prop, &properties[i], sizeof(bt_property_t));addr_prop.type = (bt_property_type_t)BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP;btif_storage_set_remote_device_property(remote_bd_addr, &addr_prop); // 1. } else {btif_storage_set_remote_device_property(remote_bd_addr, &properties[i]); // 2. }}return BT_STATUS_SUCCESS;
}

btif_storage_set_remote_device_property

// system/btif/src/btif_storage.cc
bt_status_t btif_storage_set_remote_device_property(const RawAddress* remote_bd_addr, bt_property_t* property) {return prop2cfg(remote_bd_addr, property) ? BT_STATUS_SUCCESS: BT_STATUS_FAIL;
}

prop2cfg


static int prop2cfg(const RawAddress* remote_bd_addr, bt_property_t* prop) {std::string bdstr;if (remote_bd_addr) {bdstr = remote_bd_addr->ToString();}char value[1024];if (prop->len <= 0 || prop->len > (int)sizeof(value) - 1) {LOG_WARN("Unable to save property to configuration file type:%d, "" len:%d is invalid",prop->type, prop->len);return false;}switch (prop->type) {case BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP:btif_config_set_int(bdstr, BTIF_STORAGE_PATH_REMOTE_DEVTIME,(int)time(NULL));break;case BT_PROPERTY_BDNAME: {int name_length = prop->len > BTM_MAX_LOC_BD_NAME_LEN? BTM_MAX_LOC_BD_NAME_LEN: prop->len;strncpy(value, (char*)prop->val, name_length);value[name_length] = '\0';if (remote_bd_addr) {btif_config_set_str(bdstr, BTIF_STORAGE_PATH_REMOTE_NAME, value);} else {btif_config_set_str("Adapter", BTIF_STORAGE_KEY_ADAPTER_NAME, value);btif_config_flush();}break;}case BT_PROPERTY_REMOTE_FRIENDLY_NAME:strncpy(value, (char*)prop->val, prop->len);value[prop->len] = '\0';btif_config_set_str(bdstr, BTIF_STORAGE_PATH_REMOTE_ALIASE, value);break;case BT_PROPERTY_ADAPTER_SCAN_MODE:btif_config_set_int("Adapter", BTIF_STORAGE_KEY_ADAPTER_SCANMODE,*(int*)prop->val);break;case BT_PROPERTY_LOCAL_IO_CAPS:btif_config_set_int("Adapter", BTIF_STORAGE_KEY_LOCAL_IO_CAPS,*(int*)prop->val);break;case BT_PROPERTY_LOCAL_IO_CAPS_BLE:btif_config_set_int("Adapter", BTIF_STORAGE_KEY_LOCAL_IO_CAPS_BLE,*(int*)prop->val);break;case BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT:btif_config_set_int("Adapter", BTIF_STORAGE_KEY_ADAPTER_DISC_TIMEOUT,*(int*)prop->val);break;case BT_PROPERTY_CLASS_OF_DEVICE:btif_config_set_int(bdstr, BTIF_STORAGE_PATH_REMOTE_DEVCLASS,*(int*)prop->val);break;case BT_PROPERTY_TYPE_OF_DEVICE:btif_config_set_int(bdstr, BTIF_STORAGE_PATH_REMOTE_DEVTYPE,*(int*)prop->val);break;case BT_PROPERTY_UUIDS: {std::string val;size_t cnt = (prop->len) / sizeof(Uuid);for (size_t i = 0; i < cnt; i++) {val += (reinterpret_cast<Uuid*>(prop->val) + i)->ToString() + " ";}btif_config_set_str(bdstr, BTIF_STORAGE_PATH_REMOTE_SERVICE, val);break;}case BT_PROPERTY_REMOTE_VERSION_INFO: {bt_remote_version_t* info = (bt_remote_version_t*)prop->val;if (!info) return false;btif_config_set_int(bdstr, BT_CONFIG_KEY_REMOTE_VER_MFCT,info->manufacturer);btif_config_set_int(bdstr, BT_CONFIG_KEY_REMOTE_VER_VER, info->version);btif_config_set_int(bdstr, BT_CONFIG_KEY_REMOTE_VER_SUBVER,info->sub_ver);} break;default:BTIF_TRACE_ERROR("Unknown prop type:%d", prop->type);return false;}/* No need to look for bonded device with address of NULL */if (remote_bd_addr &&btif_in_fetch_bonded_device(bdstr) == BT_STATUS_SUCCESS) {/* save changes if the device was bonded */btif_config_flush();}return true;
}
1. btif_config_set_str 和 btif_config_set_int
// system/btif/src/btif_config.cc
bool btif_config_set_int(const std::string& section, const std::string& key,int value) {CHECK(bluetooth::shim::is_gd_stack_started_up());return bluetooth::shim::BtifConfigInterface::SetInt(section, key, value);
}// system/main/shim/config.cc
bool BtifConfigInterface::SetInt(const std::string& section,const std::string& property, int value) {ConfigCacheHelper::FromConfigCache(*GetStorage()->GetConfigCache()).SetInt(section, property, value);return true;
}// system/gd/storage/config_cache_helper.cc
void ConfigCacheHelper::SetInt(const std::string& section, const std::string& property, int value) {config_cache_.SetProperty(section, property, std::to_string(value));
}
2. ConfigCache::SetProperty

功能:

  • 設置一個配置項(如藍牙設備屬性或通用配置),并根據是否為設備屬性決定是否進入持久化配置或臨時配置緩存中。
// system/gd/storage/config_cache.ccvoid ConfigCache::SetProperty(std::string section, std::string property, std::string value) {/*使用遞歸互斥鎖保護對 information_sections_、persistent_devices_、temporary_devices_ 等共享數據結構的并發訪問。防止多線程同時讀寫 config。*/std::lock_guard<std::recursive_mutex> lock(mutex_);// 移除傳入字符串中可能存在的 \n 或 \r,防止注入或破壞配置格式。TrimAfterNewLine(section);TrimAfterNewLine(property);TrimAfterNewLine(value);// section 和 property 名不能為空,否則斷言失敗(開發期調試用)ASSERT_LOG(!section.empty(), "Empty section name not allowed");ASSERT_LOG(!property.empty(), "Empty property name not allowed");/* 判斷是否為“設備節一般如 [Adapter], [Metrics], [Global] 屬于非設備配置節;而類似 [Device_XX:XX:XX:XX:XX:XX] 才是設備配置節。*/if (!IsDeviceSection(section)) {/*存入 information_sections_(非設備類配置)如果該節尚不存在,則創建;將 property -> value 插入;通知配置已更改。適用于如 [Adapter] 節下的 Name、ScanMode 等普通配置項。*/auto section_iter = information_sections_.find(section);if (section_iter == information_sections_.end()) {section_iter = information_sections_.try_emplace_back(section, common::ListMap<std::string, std::string>{}).first;}section_iter->second.insert_or_assign(property, std::move(value));PersistentConfigChangedCallback();return;}/*如果是設備配置節(Device_...),檢查是否可持久化保存如果該設備還不在 persistent_devices_ 中,且當前 property 是可持久化的(如 LinkKey),嘗試從臨時設備中遷移。只有配對時,才有可能將 設備 從臨時設備列表中, 移到 可持久化列表里。*/auto section_iter = persistent_devices_.find(section);if (section_iter == persistent_devices_.end() && IsPersistentProperty(property)) {// move paired devices or create new paired device when a link key is set/*從 temporary_devices_ 遷移或新建一項若該設備的屬性存在于臨時設備列表中,則將其“轉正”遷入 persistent;否則新建。場景舉例:連接配對時第一次保存 link key,從臨時狀態遷移為持久配對狀態。*/auto section_properties = temporary_devices_.extract(section);if (section_properties) {section_iter = persistent_devices_.try_emplace_back(section, std::move(section_properties->second)).first;} else {section_iter = persistent_devices_.try_emplace_back(section, common::ListMap<std::string, std::string>{}).first;}}/*安全模式下加密敏感屬性值如果開啟了安全模式(如 CC Mode)并且屬性為敏感字段(如 LinkKey、LE_KEY_PENC 等):通過 KeyStore 接口嘗試加密存儲;若成功,則設置為標記字符串 value = "$encrypted" 表示已加密。
*/if (section_iter != persistent_devices_.end()) {bool is_encrypted = value == kEncryptedStr;if ((!value.empty()) && os::ParameterProvider::GetBtKeystoreInterface() != nullptr &&os::ParameterProvider::IsCommonCriteriaMode() && InEncryptKeyNameList(property) && !is_encrypted) {if (os::ParameterProvider::GetBtKeystoreInterface()->set_encrypt_key_or_remove_key(section + "-" + property, value)) {value = kEncryptedStr;}}/*插入持久化設備屬性 + 通知更改將處理后的值插入設備節中;通知持久化更改(通常觸發異步寫入 config 文件)。
*/section_iter->second.insert_or_assign(property, std::move(value));PersistentConfigChangedCallback();return;}/*如果該設備節仍不存在,寫入 temporary_devices_表示當前屬性不需要持久化;保存到 temporary_devices_ 中(通常用于會話屬性、未配對設備等  );掃描到的設備都一般存儲在這里*/section_iter = temporary_devices_.find(section);if (section_iter == temporary_devices_.end()) {auto triple = temporary_devices_.try_emplace(section, common::ListMap<std::string, std::string>{});section_iter = std::get<0>(triple);}section_iter->second.insert_or_assign(property, std::move(value));
}

在搜素時,沒有配對的設備 都將保存在 臨時 設備列表里面。

只有 在配對時,收到 設備的 LinkKey 時, 才會從臨時設備 列表,挪到 可持久設備列表里面。

  /*如果是設備配置節(Device_...),檢查是否可持久化保存如果該設備還不在 persistent_devices_ 中,且當前 property 是可持久化的(如 LinkKey),嘗試從臨時設備中遷移。只有配對時,才有可能將 設備 從臨時設備列表中, 移到 可持久化列表里。*/auto section_iter = persistent_devices_.find(section);if (section_iter == persistent_devices_.end() && IsPersistentProperty(property)) {// move paired devices or create new paired device when a link key is set...}
1. IsPersistentProperty(property)
// system/gd/storage/config_cache.ccbool ConfigCache::IsPersistentProperty(const std::string& property) const {return persistent_property_names_.find(property) != persistent_property_names_.end();
}ConfigCache::ConfigCache(size_t temp_device_capacity, std::unordered_set<std::string_view> persistent_property_names): persistent_property_names_(std::move(persistent_property_names)),information_sections_(),persistent_devices_(),temporary_devices_(temp_device_capacity) {}
  • 在 ConfigCache 構造函數里面,會初始化 persistent_property_names_
// system/gd/storage/storage_module.cc
void StorageModule::Start() {...auto config = LegacyConfigFile::FromPath(config_file_path_).Read(temp_devices_capacity_);...
}
// system/gd/storage/legacy_config_file.ccstd::optional<ConfigCache> LegacyConfigFile::Read(size_t temp_devices_capacity) {...ConfigCache cache(temp_devices_capacity, Device::kLinkKeyProperties);...
}

在 StorageModule 模塊初始化時, 將 從 /data/misc/bluedroid/bt_config.conf 讀取內容, 此時會 創建 ConfigCache 對象,此時 persistent_property_names_ = Device::kLinkKeyProperties

4. 在各階段的核心作用


1. 搜索階段(Inquiry / BLE Scan)

1. 作用

  • 當 Controller 發現新設備時(classic 或 BLE):

    • 沒有就創建并初始化
    • 有就更新(如 RSSI、名稱、設備類型)
  • 作用于:

    • 避免重復設備出現在 UI(去重)
    • 通知 Java 層刷新設備列表

2. 解決的問題

問題如何解決
同一設備出現多次用地址唯一標識緩存
設備名稱/類型不一致統一由 DeviceProperties 更新

3. BR/EDR 掃描流程鑒賞

1. btif_dm_search_devices_evt

這段代碼是 設備發現流程的核心處理邏輯之一,對應于系統層調用 BluetoothAdapter.startDiscovery() 時產生的設備發現過程中的事件處理。

btif_dm_search_devices_evt() 是 BTIF 層處理 BTA 層 tBTA_DM_xxx_EVT 事件的統一入口:

  • 當發現遠程設備時(設備通過 Inquiry 過程或 BLE 掃描被發現),event == BTA_DM_INQ_RES_EVT,該事件就會觸發,并進入 case 分支處理。
// system/btif/src/btif_dm.cc
static void btif_dm_search_devices_evt(tBTA_DM_SEARCH_EVT event,tBTA_DM_SEARCH* p_search_data) {BTIF_TRACE_EVENT("%s event=%s", __func__, dump_dm_search_event(event));switch (event) {...case BTA_DM_INQ_RES_EVT: {/* inquiry result */bt_bdname_t bdname;uint8_t remote_name_len;uint8_t num_uuids = 0, num_uuids128 = 0, max_num_uuid = 32;uint8_t uuid_list[32 * Uuid::kNumBytes16];uint8_t uuid_list128[32 * Uuid::kNumBytes128];p_search_data->inq_res.remt_name_not_required =check_eir_remote_name(p_search_data, NULL, NULL);RawAddress& bdaddr = p_search_data->inq_res.bd_addr;//  打印發現設備的地址和類型(BR/EDR、BLE 或 DUMO),有助于調試日志追蹤。BTIF_TRACE_DEBUG("%s() %s device_type = 0x%x\n", __func__,bdaddr.ToString().c_str(),p_search_data->inq_res.device_type);bdname.name[0] = 0;// 優先嘗試從 EIR(Extended Inquiry Response)中解析遠程設備名稱,如果沒有,就嘗試從緩存中找if (!check_eir_remote_name(p_search_data, bdname.name, &remote_name_len))check_cached_remote_name(p_search_data, bdname.name, &remote_name_len);/* Check EIR for services */// 提取設備 EIR 中的 UUID(服務列表)if (p_search_data->inq_res.p_eir) {// 分別提取 16-bit 和 128-bit UUID,用于構建遠程設備所支持的服務,比如 A2DP Sink、HID、PAN 等BTM_GetEirUuidList(p_search_data->inq_res.p_eir,p_search_data->inq_res.eir_len, Uuid::kNumBytes16,&num_uuids, uuid_list, max_num_uuid);BTM_GetEirUuidList(p_search_data->inq_res.p_eir,p_search_data->inq_res.eir_len, Uuid::kNumBytes128,&num_uuids128, uuid_list128, max_num_uuid);}{// 組織設備屬性結構體 bt_property_tbt_property_t properties[7];bt_device_type_t dev_type;uint32_t num_properties = 0;bt_status_t status;tBLE_ADDR_TYPE addr_type = BLE_ADDR_PUBLIC;memset(properties, 0, sizeof(properties));// 下面依次填充不同類型的屬性:/* RawAddress */BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_BDADDR/*設備地址*/, sizeof(bdaddr), &bdaddr);num_properties++;/* BD_NAME *//* Don't send BDNAME if it is empty */if (bdname.name[0]) {BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_BDNAME/*設備名稱(非空時填充)*/,strlen((char*)bdname.name), &bdname);num_properties++;}/* DEV_CLASS */uint32_t cod = devclass2uint(p_search_data->inq_res.dev_class);BTIF_TRACE_DEBUG("%s cod is 0x%06x", __func__, cod);if (cod != 0) {BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_CLASS_OF_DEVICE/*COD 設備類別*/, sizeof(cod),&cod);num_properties++;}/* DEV_TYPE *//* FixMe: Assumption is that bluetooth.h and BTE enums match *//* Verify if the device is dual mode in NVRAM */int stored_device_type = 0;if (btif_get_device_type(bdaddr, &stored_device_type) &&((stored_device_type != BT_DEVICE_TYPE_BREDR &&p_search_data->inq_res.device_type == BT_DEVICE_TYPE_BREDR) ||(stored_device_type != BT_DEVICE_TYPE_BLE &&p_search_data->inq_res.device_type == BT_DEVICE_TYPE_BLE))) {dev_type = (bt_device_type_t)BT_DEVICE_TYPE_DUMO;} else {dev_type = (bt_device_type_t)p_search_data->inq_res.device_type;}if (p_search_data->inq_res.device_type == BT_DEVICE_TYPE_BLE)addr_type = p_search_data->inq_res.ble_addr_type;BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_TYPE_OF_DEVICE/*設備類型(BR/EDR、BLE、DUMO)*/, sizeof(dev_type),&dev_type);num_properties++;/* RSSI */BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_REMOTE_RSSI/*信號強度*/, sizeof(int8_t),&(p_search_data->inq_res.rssi));num_properties++;/* CSIP supported device */BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_REMOTE_IS_COORDINATED_SET_MEMBER/*是否為 Coordinated Set(例如耳機雙邊同步)*/,sizeof(bool),&(p_search_data->inq_res.include_rsi));num_properties++;/* Cache EIR queried services */if ((num_uuids + num_uuids128) > 0) {uint16_t* p_uuid16 = (uint16_t*)uuid_list;auto uuid_iter = eir_uuids_cache.find(bdaddr);Uuid    new_remote_uuid[BT_MAX_NUM_UUIDS];size_t  dst_max_num = sizeof(new_remote_uuid)/sizeof(Uuid);size_t  new_num_uuid = 0;Uuid    remote_uuid[BT_MAX_NUM_UUIDS];if (uuid_iter == eir_uuids_cache.end()) {auto triple = eir_uuids_cache.try_emplace(bdaddr, std::set<Uuid>{});uuid_iter = std::get<0>(triple);}//LOG_INFO("EIR UUIDs for %s:", bdaddr.ToString().c_str());for (int i = 0; i < num_uuids; ++i) {Uuid uuid = Uuid::From16Bit(p_uuid16[i]);//LOG_INFO("        %s", uuid.ToString().c_str());uuid_iter->second.insert(uuid);if (i < BT_MAX_NUM_UUIDS) {remote_uuid[i] = uuid;} else {LOG_INFO("%d >= %d", i, BT_MAX_NUM_UUIDS);}}for (int i = 0; i < num_uuids128; ++i) {Uuid uuid = Uuid::From128BitBE((uint8_t *)&uuid_list128[i * Uuid::kNumBytes128]);//LOG_INFO("        %s", uuid.ToString().c_str());uuid_iter->second.insert(uuid);if (i < BT_MAX_NUM_UUIDS) {remote_uuid[num_uuids + i] = uuid;} else {LOG_INFO("%d >= %d", i, BT_MAX_NUM_UUIDS);}}//LOG_INFO("%s %d : update EIR UUIDs.", __func__, __LINE__);new_num_uuid = btif_update_uuid(bdaddr, remote_uuid,(num_uuids + num_uuids128), new_remote_uuid,sizeof(new_remote_uuid),dst_max_num);BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties],BT_PROPERTY_UUIDS/*支持的服務 UUID 列表*/,new_num_uuid * Uuid::kNumBytes128, new_remote_uuid);//LOG_INFO("%s %d : fill BT_PROPERTY_UUIDS property.", __func__, __LINE__);num_properties ++;}// 存儲到 內存中, 并更新地址類型, 將新發現的設備保存到本地數據庫中,供配對或后續連接使用。status =btif_storage_add_remote_device(&bdaddr, num_properties, properties);ASSERTC(status == BT_STATUS_SUCCESS,"failed to save remote device (inquiry)", status);status = btif_storage_set_remote_addr_type(&bdaddr, addr_type);ASSERTC(status == BT_STATUS_SUCCESS,"failed to save remote addr type (inquiry)", status);//  可選過濾(非 connectable 的 BLE 設備忽略), 防止顯示無法連接的 BLE 設備,例如 Beacon,這對某些廠商有定制用途。bool restrict_report = osi_property_get_bool("bluetooth.restrict_discovered_device.enabled", false);if (restrict_report &&p_search_data->inq_res.device_type == BT_DEVICE_TYPE_BLE &&!(p_search_data->inq_res.ble_evt_type & BTM_BLE_CONNECTABLE_MASK)) {LOG_INFO("%s: Ble device is not connectable",bdaddr.ToString().c_str());break;}/* Callback to notify upper layer of device */invoke_device_found_cb(num_properties, properties); // 通知上層 Java 層 deviceFoundCallback()}} break;}
}

btif_dm_search_devices_evt() 是 AOSP 中實現設備搜索發現的核心邏輯,負責:

  • 從 Inquiry/BLE 掃描結果中提取遠程設備信息;

  • 嘗試從 EIR 中解析遠程名稱和服務;

  • 構建標準的 bt_property_t 屬性數組;

  • 存儲發現設備并通知 Java 層應用。

而它最終觸發的 deviceFoundCallback() 是應用層感知新設備發現的關鍵入口。

我們再次 devicePropertyChangedCallback vs. deviceFoundCallback 對比:

比較點devicePropertyChangedCallbackdeviceFoundCallback
觸發條件已知設備屬性變化(如名稱變化、RSSI 變化)發現新設備(搜索階段)
回調數據變化的 bt_property_t 屬性初次發現設備的 bt_property_t 全集
使用場景設備連接后屬性更新搜索設備時通知發現
是否多次觸發是(屬性有變化就會觸發)是(發現多個設備時各觸發一次)
上層應用行為更新已有設備界面信息新增列表項,展示設備名稱等

4. Ble scan 時

當 ble 掃描到設備時, 就會觸發調用 BleScannerInterfaceImpl::OnScanResult

// system/main/shim/le_scanning_manager.ccvoid BleScannerInterfaceImpl::OnScanResult(uint16_t event_type, uint8_t address_type, bluetooth::hci::Address address,uint8_t primary_phy, uint8_t secondary_phy, uint8_t advertising_sid,int8_t tx_power, int8_t rssi, uint16_t periodic_advertising_interval,std::vector<uint8_t> advertising_data) {RawAddress raw_address = ToRawAddress(address);tBLE_ADDR_TYPE ble_addr_type = to_ble_addr_type(address_type);if (ble_addr_type != BLE_ADDR_ANONYMOUS) {btm_ble_process_adv_addr(raw_address, &ble_addr_type);}// 這里會 去觸發 更新 ble 設備的 propertiesdo_in_jni_thread(FROM_HERE,base::BindOnce(&BleScannerInterfaceImpl::handle_remote_properties,base::Unretained(this), raw_address, ble_addr_type,advertising_data));do_in_jni_thread(FROM_HERE,base::BindOnce(&ScanningCallbacks::OnScanResult,base::Unretained(scanning_callbacks_), event_type,static_cast<uint8_t>(address_type), raw_address,primary_phy, secondary_phy, advertising_sid, tx_power,rssi, periodic_advertising_interval, advertising_data));...
}
// system/main/shim/le_scanning_manager.ccvoid BleScannerInterfaceImpl::handle_remote_properties(RawAddress bd_addr, tBLE_ADDR_TYPE addr_type,std::vector<uint8_t> advertising_data) {if (!bluetooth::shim::is_gd_stack_started_up()) {LOG_WARN("Gd stack is stopped, return");return;}// skip anonymous advertismentif (addr_type == BLE_ADDR_ANONYMOUS) {return;}auto device_type = bluetooth::hci::DeviceType::LE;uint8_t flag_len;const uint8_t* p_flag = AdvertiseDataParser::GetFieldByType(advertising_data, BTM_BLE_AD_TYPE_FLAG, &flag_len);if (p_flag != NULL && flag_len != 0) {if ((BTM_BLE_BREDR_NOT_SPT & *p_flag) == 0) {device_type = bluetooth::hci::DeviceType::DUAL;}}uint8_t remote_name_len;const uint8_t* p_eir_remote_name = AdvertiseDataParser::GetFieldByType(advertising_data, HCI_EIR_COMPLETE_LOCAL_NAME_TYPE, &remote_name_len);if (p_eir_remote_name == NULL) {p_eir_remote_name = AdvertiseDataParser::GetFieldByType(advertising_data, HCI_EIR_SHORTENED_LOCAL_NAME_TYPE, &remote_name_len);}// update device nameif ((addr_type != BLE_ADDR_RANDOM) || (p_eir_remote_name)) {if (!address_cache_.find(bd_addr)) {address_cache_.add(bd_addr);if (p_eir_remote_name) {bt_bdname_t bdname;memcpy(bdname.name, p_eir_remote_name, remote_name_len);if (remote_name_len < BD_NAME_LEN + 1)bdname.name[remote_name_len] = '\0';// 這里很關鍵, 也就是說 只有 掃描到的 ble 設備 有名字, 才會去更新 properties. 其他例如 ble 的 rssi  等,都不會更新。btif_dm_update_ble_remote_properties(bd_addr, bdname.name, device_type);}}}auto* storage_module = bluetooth::shim::GetStorage();bluetooth::hci::Address address = ToGdAddress(bd_addr);// update device typeauto mutation = storage_module->Modify();bluetooth::storage::Device device =storage_module->GetDeviceByLegacyKey(address);mutation.Add(device.SetDeviceType(device_type));mutation.Commit();// update address typeauto mutation2 = storage_module->Modify();bluetooth::storage::LeDevice le_device = device.Le();mutation2.Add(le_device.SetAddressType((bluetooth::hci::AddressType)addr_type));mutation2.Commit();
}
// system/btif/src/btif_dm.cc
void btif_dm_update_ble_remote_properties(const RawAddress& bd_addr,BD_NAME bd_name,tBT_DEVICE_TYPE dev_type) {btif_update_remote_properties(bd_addr, bd_name, NULL, dev_type);
}
// system/btif/src/btif_dm.ccstatic void btif_update_remote_properties(const RawAddress& bdaddr,BD_NAME bd_name, DEV_CLASS dev_class,tBT_DEVICE_TYPE device_type) {int num_properties = 0;bt_property_t properties[3];bt_status_t status = BT_STATUS_UNHANDLED;uint32_t cod;bt_device_type_t dev_type;memset(properties, 0, sizeof(properties));/* remote name */if (strlen((const char*)bd_name)) {BTIF_STORAGE_FILL_PROPERTY(&properties[num_properties], BT_PROPERTY_BDNAME,strlen((char*)bd_name), bd_name);if (!bluetooth::shim::is_gd_security_enabled()) {status = btif_storage_set_remote_device_property(&bdaddr, &properties[num_properties]);ASSERTC(status == BT_STATUS_SUCCESS, "failed to save remote device name",status);}num_properties++;}...invoke_remote_device_properties_cb(status, bdaddr, num_properties,properties); // 1. 
}

ble 在掃描階段, 只有掃描到 設備的名字時, 才會去 調用 invoke_remote_device_properties_cb 更新 properties.

1.例子一
01-02 14:20:35.237345  2146  2634 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:637 btif_update_remote_properties: cod from storage is also unclassified01-02 14:20:35.237477  2146  2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_in_fetch_bonded_ble_device Found a LE device: ba:03:cc:1c:cd:2201-02 14:20:35.237906  2146  2634 D BtGatt.GattService: onScanResult() - eventType=0x1b, addressType=0, address=BA:03:CC:1C:CD:22, primaryPhy=1, secondaryPhy=0, advertisingSid=0xff, txPower=127, rssi=-73, periodicAdvInt=0x0, originalAddress=00:00:00:00:00:0001-02 14:20:35.238671  2146  2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: operator(): HAL bt_hal_cbacks->remote_device_properties_cb
01-02 14:20:35.238841  2146  2634 D BluetoothRemoteDevices: Added new device property
01-02 14:20:35.239122  2146  2634 I BluetoothRemoteDevices: Property type: 1
01-02 14:20:35.240002  2146  2634 I BluetoothRemoteDevices: Remote Device name is: MXD57_MI
01-02 14:20:35.240042  2146  2634 I BluetoothRemoteDevices: Property type: 4
01-02 14:20:35.240082  2146  2634 I BluetoothRemoteDevices: Skip class update for BA:03:CC:1C:CD:22
01-02 14:20:35.240104  2146  2634 I BluetoothRemoteDevices: Property type: 5
131	2025-01-02 14:20:35.236141	controller	host	HCI_EVT	60	Rcvd LE Meta (LE Extended Advertising Report)Frame 131: 60 bytes on wire (480 bits), 60 bytes captured (480 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI Event - LE MetaEvent Code: LE Meta (0x3e)Parameter Total Length: 57Sub Event: LE Extended Advertising Report (0x0d)Num Reports: 1Event Type: 0x0013, Connectable, Scannable, Legacy, Data Status: CompletePeer Address Type: Public Device Address (0x00)BD_ADDR: ba:03:cc:1c:cd:22 (ba:03:cc:1c:cd:22)Primary PHY: LE 1M (0x01)Secondary PHY: No packets on the secondary advertising channel (0x00)Advertising SID: 0xff (not available)TX Power: 127 dBm (not available)RSSI: -74 dBmPeriodic Advertising Interval: 0x0000 (no periodic advertising)Direct Address Type: Public Device Address (0x00)Direct BD_ADDR: 00:00:00_00:00:00 (00:00:00:00:00:00)Data Length: 31Advertising DataFlagsManufacturer SpecificDevice Name: MXD57_MI  # 廣播數據中有設備名字Manufacturer Specific
2.例子二
01-02 14:20:35.311975  2146  2634 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:637 btif_update_remote_properties: cod from storage is also unclassified01-02 14:20:35.312013  2146  2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_in_fetch_bonded_ble_device Found a LE device: c0:53:c1:67:0e:a801-02 14:20:35.312128  2146  2634 D BtGatt.GattService: onScanResult() - eventType=0x1b, addressType=1, address=C0:53:C1:67:0E:A8, primaryPhy=1, secondaryPhy=0, advertisingSid=0xff, txPower=127, rssi=-82, periodicAdvInt=0x0, originalAddress=00:00:00:00:00:0001-02 14:20:35.312390  2146  2634 D BluetoothRemoteDevices: Added new device property
01-02 14:20:35.312479  2146  2634 I BluetoothRemoteDevices: Property type: 1
01-02 14:20:35.313982  2146  2634 I BluetoothRemoteDevices: Remote Device name is: LE_WF-C500
01-02 14:20:35.314013  2146  2634 I BluetoothRemoteDevices: Property type: 4
01-02 14:20:35.314035  2146  2634 I BluetoothRemoteDevices: Skip class update for C0:53:C1:67:0E:A8
01-02 14:20:35.314046  2146  2634 I BluetoothRemoteDevices: Property type: 5

158	2025-01-02 14:20:35.308074	controller	host	HCI_EVT	41	Rcvd LE Meta (LE Extended Advertising Report)Frame 158: 41 bytes on wire (328 bits), 41 bytes captured (328 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI Event - LE MetaEvent Code: LE Meta (0x3e)Parameter Total Length: 38Sub Event: LE Extended Advertising Report (0x0d)Num Reports: 1Event Type: 0x0013, Connectable, Scannable, Legacy, Data Status: CompletePeer Address Type: Random Device Address (0x01)BD_ADDR: c0:53:c1:67:0e:a8 (c0:53:c1:67:0e:a8)Primary PHY: LE 1M (0x01)Secondary PHY: No packets on the secondary advertising channel (0x00)Advertising SID: 0xff (not available)TX Power: 127 dBm (not available)RSSI: -81 dBmPeriodic Advertising Interval: 0x0000 (no periodic advertising)Direct Address Type: Public Device Address (0x00)Direct BD_ADDR: 00:00:00_00:00:00 (00:00:00:00:00:00)Data Length: 12Advertising DataDevice Name: LE_WF-C500  // 設備名字
3. 例子三
01-02 14:20:41.776270  2146  2634 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:637 btif_update_remote_properties: cod from storage is also unclassified01-02 14:20:41.776344  2146  2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_in_fetch_bonded_ble_device Found a LE device: d4:f0:ea:91:af:c601-02 14:20:41.776511  2146  2634 D BtGatt.GattService: onScanResult() - eventType=0x1b, addressType=0, address=D4:F0:EA:91:AF:C6, primaryPhy=1, secondaryPhy=0, advertisingSid=0xff, txPower=127, rssi=-84, periodicAdvInt=0x0, originalAddress=00:00:00:00:00:0001-02 14:20:41.776644  2146  2634 D BluetoothRemoteDevices: Added new device property
01-02 14:20:41.776740  2146  2634 I BluetoothRemoteDevices: Property type: 1
01-02 14:20:41.777297  2146  2634 I BluetoothRemoteDevices: Remote Device name is: SMI-M14
01-02 14:20:41.777325  2146  2634 I BluetoothRemoteDevices: Property type: 4
01-02 14:20:41.777346  2146  2634 I BluetoothRemoteDevices: Skip class update for D4:F0:EA:91:AF:C6
01-02 14:20:41.777359  2146  2634 I BluetoothRemoteDevices: Property type: 5
735	2025-01-02 14:20:41.775853	controller	host	HCI_EVT	59	Rcvd LE Meta (LE Extended Advertising Report)Frame 735: 59 bytes on wire (472 bits), 59 bytes captured (472 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI Event - LE MetaEvent Code: LE Meta (0x3e)Parameter Total Length: 56Sub Event: LE Extended Advertising Report (0x0d)Num Reports: 1Event Type: 0x0013, Connectable, Scannable, Legacy, Data Status: CompletePeer Address Type: Public Device Address (0x00)BD_ADDR: BeijingX_91:af:c6 (d4:f0:ea:91:af:c6)Primary PHY: LE 1M (0x01)Secondary PHY: No packets on the secondary advertising channel (0x00)Advertising SID: 0xff (not available)TX Power: 127 dBm (not available)RSSI: -84 dBmPeriodic Advertising Interval: 0x0000 (no periodic advertising)Direct Address Type: Public Device Address (0x00)Direct BD_ADDR: 00:00:00_00:00:00 (00:00:00:00:00:00)Data Length: 30Advertising DataFlagsLength: 2Type: Flags (0x01)0. .... = Reserved: 0x0...0 .... = Simultaneous LE and BR/EDR to Same Device Capable (Host): false (0x0).... 0... = Simultaneous LE and BR/EDR to Same Device Capable (Controller): false (0x0).... .1.. = BR/EDR Not Supported: true (0x1).... ..1. = LE General Discoverable Mode: true (0x1).... ...0 = LE Limited Discoverable Mode: false (0x0)Service Data - 16 bit UUIDLength: 17Type: Service Data - 16 bit UUID (0x16)UUID 16: Xiaomi Inc. (0xfe95)Service Data: b054452d00c6af91eaf0d4080e00Device Name: SMI-M14  // 設備名字Length: 8Type: Device Name (0x09)Device Name: SMI-M14

2. SDP / 服務發現階段

1.作用

  • 在 classic 藍牙中,通過 SDP 獲取遠程設備支持的 UUID

  • 在 BLE 中,通過 GATT Discover Services 獲取 UUID

  • 均更新到 DeviceProperties::uuids 字段中

2.解決的問題

問題如何解決
重復做 SDP,浪費時間已知 UUID 可直接進入連接邏輯
Profile 初始化失敗使用緩存 UUID 預判斷是否支持 A2DP、HFP 等
連接效率低提前緩存服務能力,減少阻塞等待時間

3.代碼流程鑒賞

1. btif_dm_search_services_evt
1. BTA_DM_DISC_RES_EVT

這段代碼是 Android AOSP 中藍牙設備 服務發現結果事件(BTA_DM_DISC_RES_EVT)的處理邏輯,位于 btif_dm_search_services_evt 函數中。它主要完成 SDP 服務發現結果的處理、UUID 合并、A2DP 能力識別、屬性上報,以及處理 SDP 失敗的情況。

大體流程概覽:

  1. 提取事件數據(設備地址、UUID 等)
  2. 判斷是否是配對過程的設備
  3. 判斷 SDP 是否失敗并重試(帶次數限制)
  4. 如果成功則合并 UUID、識別 A2DP 功能
  5. 特殊處理 LE Audio 設備
  6. 將 UUID 結果保存、上報到 Java 層
  7. 處理 SDP 失敗時嘗試使用 EIR 中的 UUID
  8. 最終調用回調通知 UUID 屬性變化

/********************************************************************************* Function         btif_dm_search_services_evt** Description      Executes search services event in btif context** Returns          void*******************************************************************************/
static void btif_dm_search_services_evt(tBTA_DM_SEARCH_EVT event,tBTA_DM_SEARCH* p_data) {switch (event) {case BTA_DM_DISC_RES_EVT: {// 初始化變量,用于保存服務發現結果、UUID 集合和屬性值,a2dp_sink_capable 標記是否支持 A2DP Sink。bt_property_t prop;uint32_t i = 0;bt_status_t ret;std::vector<uint8_t> property_value;std::set<Uuid> uuids;bool a2dp_sink_capable = false;// 獲取服務發現設備的地址引用。RawAddress& bd_addr = p_data->disc_res.bd_addr;// 判斷當前 SDP 結果是否是為當前正在配對的設備服務。bool results_for_bonding_device =(bd_addr == pairing_cb.bd_addr || bd_addr == pairing_cb.static_bdaddr);// 日志輸出 SDP 的返回值和服務字段。LOG_VERBOSE("result=0x%x, services 0x%x", p_data->disc_res.result,p_data->disc_res.services);/*SDP 失敗后的重試邏輯(僅限配對設備):*/if (results_for_bonding_device && p_data->disc_res.result != BTA_SUCCESS &&pairing_cb.state == BT_BOND_STATE_BONDED &&pairing_cb.sdp_attempts < BTIF_DM_MAX_SDP_ATTEMPTS_AFTER_PAIRING) { // 如果是配對設備、SDP 失敗、當前狀態為已配對、重試次數不超過限制:// 第一次不重試,第二次以上繼續嘗試,最多三次。if (pairing_cb.sdp_attempts) {LOG_WARN("SDP failed after bonding re-attempting for %s",PRIVATE_ADDRESS(bd_addr));pairing_cb.sdp_attempts++;LOG_INFO("sdp_attempts = %d", pairing_cb.sdp_attempts);btif_dm_get_remote_services(bd_addr, BT_TRANSPORT_AUTO);} else {LOG_WARN("SDP triggered by someone failed when bonding");}return;}// 如果是配對設備,則標記經典 SDP 已完成。if (results_for_bonding_device) {LOG_INFO("SDP finished for %s:", PRIVATE_ADDRESS(bd_addr));pairing_cb.sdp_over_classic =btif_dm_pairing_cb_t::ServiceDiscoveryState::FINISHED;}// 設置 UUID 屬性類型。prop.type = BT_PROPERTY_UUIDS;prop.len = 0;if ((p_data->disc_res.result == BTA_SUCCESS) &&(p_data->disc_res.num_uuids > 0)) { // 如果 SDP 成功且發現了 UUID:LOG_INFO("New UUIDs for %s:", bd_addr.ToString().c_str());for (i = 0; i < p_data->disc_res.num_uuids; i++) { // 遍歷所有 UUID,過濾無效項,并插入到集合中。auto uuid = p_data->disc_res.p_uuid_list + i;if (btif_should_ignore_uuid(*uuid)) {continue;}LOG_INFO("index:%d uuid:%s", i, uuid->ToString().c_str());uuids.insert(*uuid);}// 將已有的 UUID 合并進去,避免覆蓋。if (results_for_bonding_device) {btif_merge_existing_uuids(pairing_cb.static_bdaddr, &uuids);btif_merge_existing_uuids(pairing_cb.bd_addr, &uuids);} else {btif_merge_existing_uuids(bd_addr, &uuids);}// 將 UUID 轉為 128bit 字節流,并識別 A2DP Sink 能力。for (auto& uuid : uuids) {auto uuid_128bit = uuid.To128BitBE();property_value.insert(property_value.end(), uuid_128bit.begin(),uuid_128bit.end());if (uuid == UUID_A2DP_SINK) {a2dp_sink_capable = true;}}prop.val = (void*)property_value.data();prop.len = Uuid::kNumBytes128 * uuids.size();}// 如果同時支持 LE Audio 且 GATT 服務發現未完成,則推遲向 Java 層報告 UUID,等待 LE GATT 結果bool skip_reporting_wait_for_le = false;/* If we are doing service discovery for device that just bonded, that is* capable of a2dp, and both sides can do LE Audio, and it haven't* finished GATT over LE yet, then wait for LE service discovery to finish* before before passing services to upper layers. */if (results_for_bonding_device &&a2dp_sink_capable && LeAudioClient::IsLeAudioClientRunning() &&pairing_cb.gatt_over_le !=btif_dm_pairing_cb_t::ServiceDiscoveryState::FINISHED &&(check_cod_le_audio(bd_addr) ||metadata_cb.le_audio_cache.contains(bd_addr) ||BTA_DmCheckLeAudioCapable(bd_addr))) {skip_reporting_wait_for_le = true;}/* onUuidChanged requires getBondedDevices to be populated.** bond_state_changed needs to be sent prior to remote_device_property*/auto num_eir_uuids = 0;Uuid uuid = {};// 只在 SDP + 配對都完成后進入。if (pairing_cb.state == BT_BOND_STATE_BONDED && pairing_cb.sdp_attempts &&results_for_bonding_device) {LOG_INFO("SDP search done for %s", bd_addr.ToString().c_str());pairing_cb.sdp_attempts = 0; // 重置重試計數。LOG_INFO("sdp_attempts = %d", pairing_cb.sdp_attempts);// Send UUIDs discovered through EIR to Java to unblock pairing intent// when SDP failed or no UUID is discoveredif (p_data->disc_res.result != BTA_SUCCESS ||p_data->disc_res.num_uuids == 0) { // 如果 SDP 失敗或沒有發現 UUID:auto uuids_iter = eir_uuids_cache.find(bd_addr);if (uuids_iter != eir_uuids_cache.end()) { // 嘗試從緩存中讀取 EIR UUID 并上報。num_eir_uuids = static_cast<int>(uuids_iter->second.size());LOG_INFO("SDP failed, send %d EIR UUIDs to unblock bonding %s",num_eir_uuids, bd_addr.ToString().c_str());for (auto eir_uuid : uuids_iter->second) {auto uuid_128bit = eir_uuid.To128BitBE();property_value.insert(property_value.end(), uuid_128bit.begin(),uuid_128bit.end());}eir_uuids_cache.erase(uuids_iter);}if (num_eir_uuids > 0) {prop.val = (void*)property_value.data();prop.len = num_eir_uuids * Uuid::kNumBytes128;} else { // 若 EIR 無內容,則構造空 UUID。LOG_WARN("SDP failed and we have no EIR UUIDs to report either");prop.val = &uuid;prop.len = Uuid::kNumBytes128;}}// Both SDP and bonding are done, clear pairing control block in case// it is not already clearedpairing_cb = {}; // 清除 pairing_cb 控制塊。LOG_INFO("clearing btif pairing_cb");}// 將 UUID 存儲到本地數據庫。if (p_data->disc_res.num_uuids != 0 || num_eir_uuids != 0) {/* Also write this to the NVRAM */ret = btif_storage_set_remote_device_property(&bd_addr, &prop);ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote services failed",ret);if (skip_reporting_wait_for_le) { // 如果不是 LE Audio 的延遲上報情況,則立即通過回調通知 Java 層屬性已更新。LOG_INFO("Bonding LE Audio sink - must wait for le services discovery ""to pass all services to java %s",PRIVATE_ADDRESS(bd_addr));/* For LE Audio capable devices, we care more about passing GATT LE* services than about just finishing pairing. Service discovery* should be scheduled when LE pairing finishes, by call to* btif_dm_get_remote_services(bd_addr, BT_TRANSPORT_LE) */return;}/* Send the event to the BTIF */invoke_remote_device_properties_cb(BT_STATUS_SUCCESS, bd_addr, 1,&prop);}} break;...}
功能點實現邏輯
是否是配對設備比較地址匹配 pairing_cb
SDP 重試最多重試 3 次,間隔觸發
UUID 處理收集、過濾、合并、識別 A2DP 能力
LE Audio若需 GATT 完成則跳過上報
SDP 失敗回退通過 EIR UUID 解鎖配對流程
屬性更新保存到數據庫 + 上報 Java 層
01-02 14:21:00.149273  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1638 btif_dm_search_services_evt: SDP finished for xx:xx:xx:xx:b0:62:
01-02 14:21:00.149282  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1647 btif_dm_search_services_evt: New UUIDs for 70:8f:47:91:b0:62:
01-02 14:21:00.149295  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:0 uuid:0000110a-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149309  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:1 uuid:00001105-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149320  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:2 uuid:00001115-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149332  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:3 uuid:00001116-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149343  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:4 uuid:0000112d-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149355  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:5 uuid:0000110e-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149366  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:6 uuid:0000112f-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149378  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:7 uuid:00001112-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149397  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:8 uuid:0000111f-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149409  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:9 uuid:00001132-0000-1000-8000-00805f9b34fb
01-02 14:21:00.149423  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:12 uuid:8fa9c715-bd1f-596c-a1b0-13162b15c892
01-02 14:21:00.149441  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:13 uuid:2c042b0a-7f57-4c0a-afcf-1762af70257c
01-02 14:21:00.149453  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1653 btif_dm_search_services_evt: index:14 uuid:9fed64fd-e91a-499e-88dd-73dfe023feed
01-02 14:21:00.149859  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1696 btif_dm_search_services_evt: SDP search done for 70:8f:47:91:b0:62
01-02 14:21:00.149882  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1698 btif_dm_search_services_evt: sdp_attempts = 0
01-02 14:21:00.149891  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1728 btif_dm_search_services_evt: clearing btif pairing_cb
  • 車機向 手機請求 sdp
976	2025-01-02 14:21:00.107670	f8:6b:14:d1:ec:32 (cheji)	vivoMobi_91:b0:62 (cbx)	SDP	31	Sent Service Search Attribute Request : L2CAP: Attribute Range (0x0000 - 0xffff) Frame 976: 31 bytes on wire (248 bits), 31 bytes captured (248 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP Protocol
Bluetooth SDP ProtocolPDU: Service Search Attribute Request (0x06)Transaction Id: 0x0001Parameter Length: 17Service Search Pattern: L2CAPMaximum Attribute Byte Count: 1008Attribute ID ListContinuation State: yes (03 F0)
  • 手機向 車機返回結果:
980	2025-01-02 14:21:00.117463	vivoMobi_91:b0:62 (cbx)	f8:6b:14:d1:ec:32 (Mycar28825)	SDP	487	Rcvd Service Search Attribute Response Frame 980: 487 bytes on wire (3896 bits), 487 bytes captured (3896 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP Protocol
Bluetooth SDP ProtocolPDU: Service Search Attribute Response (0x07)Transaction Id: 0x0001Parameter Length: 473Attribute List Byte Count: 470Data FragmentContinuation State: no (00)[Reassembled Attribute List]Attribute Lists [count = 16]Data Element: Sequence uint16 1475 bytes0011 0... = Data Element Type: Sequence (6).... .110 = Data Element Size: uint16 (6)Data Element Var Size: 1475Data ValueAttribute List [count =  4] (Generic Attribute Profile)Attribute List [count =  4] (Generic Access Profile)Attribute List [count =  6] (Headset Audio Gateway)Attribute List [count =  8] (Handsfree Audio Gateway)Attribute List [count =  8] (A/V Remote Control Target)Attribute List [count =  7] (Audio Source)Attribute List [count =  7] (A/V Remote Control)Attribute List [count = 11] (PAN NAP)Attribute List [count =  9] (PAN PANU)Attribute List [count =  6] (SIM Access)Attribute List [count =  7] (Phonebook Access Server)Attribute List [count = 10] (Message Access Server)Attribute List [count =  8] (OBEX Object Push)Attribute List [count =  5] (CustomUUID: Unknown)Attribute List [count =  4] (CustomUUID: Unknown)Attribute List [count =  4] (CustomUUID: Unknown)
2. BTA_DM_GATT_OVER_LE_RES_EVT

這段代碼,它處理的是 通過 GATT over LE 發現到的服務 UUID,這是在藍牙設備進行 LE Audio 相關配對和服務發現時的關鍵步驟之一。

static void btif_dm_search_services_evt(tBTA_DM_SEARCH_EVT event,tBTA_DM_SEARCH* p_data) {switch (event) {// 表示收到 BLE GATT 服務發現完成事件,主要處理通過 GATT over LE 獲取到的服務信息。case BTA_DM_GATT_OVER_LE_RES_EVT: {/*num_properties: 記錄即將設置的屬性數量(如 UUID、名稱)。prop: 保存即將傳遞給上層的屬性,最多兩個(UUIDs + 名稱)。property_value: 保存所有服務 UUID 的序列化值。uuids: 使用 set 記錄 UUID,防止重復。bd_addr: 當前發現服務的設備地址。*/int num_properties = 0;bt_property_t prop[2];std::vector<uint8_t> property_value;std::set<Uuid> uuids;RawAddress& bd_addr = p_data->disc_ble_res.bd_addr;if (event == BTA_DM_GATT_OVER_LE_RES_EVT) {LOG_INFO("New GATT over LE UUIDs for %s:",PRIVATE_ADDRESS(bd_addr));if ((bd_addr == pairing_cb.bd_addr ||bd_addr == pairing_cb.static_bdaddr)) {if (pairing_cb.gatt_over_le !=btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED) {LOG_ERROR("gatt_over_le should be SCHEDULED, did someone clear the ""control block for %s ?",PRIVATE_ADDRESS(bd_addr));}/*如果當前設備是正在配對的目標設備,更新配對控制塊 pairing_cb 的 GATT 狀態為已完成。同時判斷 gatt_over_le 狀態是否應該是 SCHEDULED(即之前已經調度過該設備的服務發現)——否則可能說明狀態機錯亂。*/pairing_cb.gatt_over_le =btif_dm_pairing_cb_t::ServiceDiscoveryState::FINISHED;if (pairing_cb.sdp_over_classic !=btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED) {// 如果 classic SDP 服務發現也沒有調度或已經完成,說明整個配對與服務發現流程都已完成,可以安全清除 pairing_cb。// Both SDP and bonding are either done, or not scheduled,// we are safe to clear the service discovery part of CB.LOG_INFO("clearing pairing_cb");pairing_cb = {};}}} else {// 這段用于處理 BTA_DM_GATT_OVER_SDP_RES_EVT(即通過 classic SDP 獲取 GATT UUID),但這里實際處理的是 GATT_OVER_LE_RES_EVT。LOG_INFO("New GATT over SDP UUIDs for %s:", PRIVATE_ADDRESS(bd_addr));}for (Uuid uuid : *p_data->disc_ble_res.services) {/*遍歷發現到的 GATT 服務 UUID:只保留 “有趣的” LE 服務(如 Hearing Aid、LE Audio)。忽略指定的 UUID。使用 set 自動去重。*/if (btif_is_interesting_le_service(uuid)) {if (btif_should_ignore_uuid(uuid)) {continue;}LOG_INFO("index:%d uuid:%s", static_cast<int>(uuids.size()),uuid.ToString().c_str());uuids.insert(uuid);}}if (uuids.empty()) {// 如果沒有任何服務 UUID 被記錄,直接退出。LOG_INFO("No well known GATT services discovered");return;}// 讀取該設備之前已經緩存的 UUID 并合并,防止遺漏或丟失信息。Uuid existing_uuids[BT_MAX_NUM_UUIDS] = {};btif_get_existing_uuids(&bd_addr, existing_uuids);for (int i = 0; i < BT_MAX_NUM_UUIDS; i++) {Uuid uuid = existing_uuids[i];if (uuid.IsEmpty()) {continue;}uuids.insert(uuid);}// 將 UUID 以 128bit Big-Endian 格式寫入 property_value,用于上層 Java 層接收。for (auto& uuid : uuids) {auto uuid_128bit = uuid.To128BitBE();property_value.insert(property_value.end(), uuid_128bit.begin(),uuid_128bit.end());}// 設置 UUID 屬性并保存到 內存中prop[0].type = BT_PROPERTY_UUIDS;prop[0].val = (void*)property_value.data();prop[0].len = Uuid::kNumBytes128 * uuids.size();/* Also write this to the NVRAM */bt_status_t ret =btif_storage_set_remote_device_property(&bd_addr, &prop[0]);ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote services failed", ret);num_properties++;// 如果通過 BLE GATT 回包中包含設備名,就保存遠程設備名稱屬性。/* Remote name update */if (strnlen((const char*)p_data->disc_ble_res.bd_name, BD_NAME_LEN)) {prop[1].type = BT_PROPERTY_BDNAME;prop[1].val = p_data->disc_ble_res.bd_name;prop[1].len = strnlen((char*)p_data->disc_ble_res.bd_name, BD_NAME_LEN);ret = btif_storage_set_remote_device_property(&bd_addr, &prop[1]);ASSERTC(ret == BT_STATUS_SUCCESS,"failed to save remote device property", ret);num_properties++;}// 如果當前是 GATT over SDP 的結果,不在這里直接上報,而是等待 DISC_RES_EVT 一并處理/* If services were returned as part of SDP discovery, we will immediately* send them with rest of SDP results in BTA_DM_DISC_RES_EVT */if (event == BTA_DM_GATT_OVER_SDP_RES_EVT) {return;}// 將 UUID 和名稱(如果有)回調通知上層應用,例如用于 Java API BluetoothDevice.getUuids() 或用于觸發服務綁定。/* Send the event to the BTIF */invoke_remote_device_properties_cb(BT_STATUS_SUCCESS, bd_addr,num_properties, prop);} break;default: { ASSERTC(0, "unhandled search services event", event); } break;}

此 case 的主要作用是處理通過 BLE GATT 通道 發現的服務 UUID,具體流程如下:

步驟操作
1判斷是否是當前配對目標
2標記 GATT 服務發現已完成
3從 GATT 發現包中提取 UUID,并過濾無關的
4合并之前已緩存的 UUID(避免丟失)
5保存 UUID 屬性并更新 NVRAM
6如有遠程設備名,也一并保存
7最后回調通知 Java 層,完成 BLE 服務發現過程


3. 配對階段(Pairing / Bonding)

1. 作用

  • 緩存配對結果:Bond 狀態、IO 能力、安全連接支持與否、LinkKey 類型與值

  • Profile 模塊可依賴 DeviceProperties 判斷是否可信 / 是否可加密傳輸

2. 寫入 bt_config.conf 內容

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

3. 解決的問題

問題如何解決
重啟系統后丟失配對信息LinkKey / Bond 信息持久化
多設備狀態混亂每個設備獨立記錄屬性
不確定是否加密傳輸使用緩存 IO 能力與 LinkKey 屬性判斷安全等級

4. 代碼鑒賞

1. btif_dm_ssp_cfm_req_evt

btif_dm_ssp_cfm_req_evt 函數

  • 它在 Bluetooth Secure Simple Pairing(SSP)確認請求事件 到達時執行。

這個函數在收到遠端設備發起的 SSP 請求(特別是 Just Works 或 Passkey Confirmation 模式)時:

  • 判斷是否可繼續配對;

  • 設置配對狀態;

  • 依據配對模式判斷是否自動接受;

  • 最終將請求上報給 Java 層顯示 UI 以確認(如彈窗顯示 PIN)。

// system/btif/src/btif_dm.cc/********************************************************************************* Function         btif_dm_ssp_cfm_req_evt** Description      Executes SSP confirm request event in btif context** Returns          void*******************************************************************************/
// 接收一個指向 SSP 確認請求的結構體指針 p_ssp_cfm_req,包含發起配對的設備地址、名稱、class、配對模式(Just Works / Passkey)、數字比較數值等信息
static void btif_dm_ssp_cfm_req_evt(tBTA_DM_SP_CFM_REQ* p_ssp_cfm_req) {bt_bdname_t bd_name;bool is_incoming = !(pairing_cb.state == BT_BOND_STATE_BONDING); // 判斷當前是否是被動配對(未處于 BONDING 狀態)。uint32_t cod; // Class of Device(設備類型編碼)。int dev_type; //  設備類型(BR/EDR、BLE 或 Dual Mode)。BTIF_TRACE_DEBUG("%s", __func__);/*優先通過 feature 判斷遠端是否為 dual-mode。若失敗,則嘗試查詢設備類型。查詢失敗,默認視為傳統 BR/EDR 設備。*//* Remote properties update */if (BTM_GetPeerDeviceTypeFromFeatures(p_ssp_cfm_req->bd_addr) ==BT_DEVICE_TYPE_DUMO) {dev_type = BT_DEVICE_TYPE_DUMO;} else if (!btif_get_device_type(p_ssp_cfm_req->bd_addr, &dev_type)) {// Failed to get device type, defaulting to BR/EDR.dev_type = BT_DEVICE_TYPE_BREDR;}// 更新設備的屬性緩存:名稱、Class of Device 和類型。btif_update_remote_properties(p_ssp_cfm_req->bd_addr, p_ssp_cfm_req->bd_name,p_ssp_cfm_req->dev_class,(tBT_DEVICE_TYPE)dev_type);// 緩存設備地址和名稱,后面用于顯示和判斷。RawAddress bd_addr = p_ssp_cfm_req->bd_addr;memcpy(bd_name.name, p_ssp_cfm_req->bd_name, BD_NAME_LEN);// 如果當前正在和某個設備配對,而新來的請求來自另一個設備,說明存在配對沖突。if (pairing_cb.state == BT_BOND_STATE_BONDING &&bd_addr != pairing_cb.bd_addr) {BTIF_TRACE_WARNING("%s(): already in bonding state, reject request",__FUNCTION__);// 拒絕該請求,防止狀態混亂或被攻擊。btif_dm_ssp_reply(bd_addr, BT_SSP_VARIANT_PASSKEY_CONFIRMATION, 0);return;}/* Set the pairing_cb based on the local & remote authentication requirements*/// 將當前設備設置為 BONDING 狀態,并上報配對事件(Java 層通過廣播感知)。bond_state_changed(BT_STATUS_SUCCESS, bd_addr, BT_BOND_STATE_BONDING);// 記錄當前配對是否為 Just Works,以及雙方的認證需求。BTIF_TRACE_EVENT("%s: just_works:%d, loc_auth_req=%d, rmt_auth_req=%d",__func__, p_ssp_cfm_req->just_works,p_ssp_cfm_req->loc_auth_req, p_ssp_cfm_req->rmt_auth_req);/*判斷是否是臨時配對如果是 Just Works 模式,且:雙方都沒有要求配對記憶(bonding)也不是鼠標等 pointing device*//* if just_works and bonding bit is not set treat this as temporary */if (p_ssp_cfm_req->just_works &&!(p_ssp_cfm_req->loc_auth_req & BTM_AUTH_BONDS) &&!(p_ssp_cfm_req->rmt_auth_req & BTM_AUTH_BONDS) &&!(check_cod((RawAddress*)&p_ssp_cfm_req->bd_addr, COD_HID_POINTING)))pairing_cb.bond_type = tBTM_SEC_DEV_REC::BOND_TYPE_TEMPORARY; // 那么是“臨時配對”,elsepairing_cb.bond_type = tBTM_SEC_DEV_REC::BOND_TYPE_PERSISTENT;  // 否則就是“持久配對”(會保存)。btm_set_bond_type_dev(p_ssp_cfm_req->bd_addr, pairing_cb.bond_type); // 將配對類型同步設置到底層。pairing_cb.is_ssp = true; // 設置 pairing_cb 中當前為 SSP 配對(而不是 legacy pairing)。/* If JustWorks auto-accept */if (p_ssp_cfm_req->just_works) { // 自動接受 Just Works 模式的特殊情況/* Pairing consent for JustWorks NOT needed if:* 1. Incoming temporary pairing is detected*/if (is_incoming &&pairing_cb.bond_type == tBTM_SEC_DEV_REC::BOND_TYPE_TEMPORARY) {/*如果是 Just Works 模式,且當前是被動發起(遠端請求配對)、且是臨時配對:*/BTIF_TRACE_EVENT("%s: Auto-accept JustWorks pairing for temporary incoming", __func__);btif_dm_ssp_reply(bd_addr, BT_SSP_VARIANT_CONSENT, true); // 自動接受該配對,無需用戶確認。這用于臨時設備,比如游戲手柄、耳機等return;}}cod = devclass2uint(p_ssp_cfm_req->dev_class); // 將設備 class(3 字節)轉為整型數值。if (cod == 0) { // 如果為 0(未知),設置為未分類。LOG_INFO("%s cod is 0, set as unclassified", __func__);cod = COD_UNCLASSIFIED;}pairing_cb.sdp_attempts = 0; // 剛進入配對流程,SDP 嘗試次數設為 0。LOG_INFO("sdp_attempts = %d", pairing_cb.sdp_attempts);/*通知 Java 層顯示配對確認彈窗調用回調函數,通知 Java 層出現確認界面。BT_SSP_VARIANT_CONSENT → Just Works 模式(只需確認,不輸入)。BT_SSP_VARIANT_PASSKEY_CONFIRMATION → 數字比較模式(確認6位數字相同)。num_val 就是要比較的那6位數字。*/invoke_ssp_request_cb(bd_addr, bd_name, cod,(p_ssp_cfm_req->just_works ? BT_SSP_VARIANT_CONSENT: BT_SSP_VARIANT_PASSKEY_CONFIRMATION),p_ssp_cfm_req->num_val);
}
步驟動作
1判斷是否為當前正在配對的設備,若不是則拒絕
2更新設備類型、屬性
3設置 pairing_cb 結構體狀態
4如果是 JustWorks 且臨時設備、被動連接,直接自動接受
5如果需要用戶確認(JustWorks 或 Passkey Compare),則通知 Java 層 UI 確認
6設置配對類型、認證信息等上下文,準備后續配對流程

01-02 14:20:56.052840  2146  3220 I bt_btm  : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: BTM_SP_CFM_REQ_EVT:  num_val: 363625
01-02 14:20:56.052917  2146  3220 I bt_btm  : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btm_proc_sp_req_evt()  just_works:0, io loc:1, rmt:1, auth loc:3, rmt:3
01-02 14:20:56.052933  2146  3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: bta_dm_sp_cback: 2
01-02 14:20:56.052948  2146  3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: btif_dm_upstreams_evt: ev: BTA_DM_SP_CFM_REQ_EVT
01-02 14:20:56.052958  2146  3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_dm_ssp_cfm_req_evt
01-02 14:20:56.052999  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:641 btif_update_remote_properties: class of device (cod) is 0x5a020c
01-02 14:20:56.053026  2146  3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_in_fetch_bonded_ble_device Found a LE device: 70:8f:47:91:b0:62
01-02 14:20:56.053116  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:462 get_cod: get_cod remote_cod = 0x005a020c
01-02 14:20:56.053294  2146  2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: operator(): HAL bt_hal_cbacks->remote_device_properties_cb
01-02 14:20:56.053394  2146  3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: btif_dm_ssp_cfm_req_evt: just_works:0, loc_auth_req=3, rmt_auth_req=3
01-02 14:20:56.053424  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:974 btif_dm_ssp_cfm_req_evt: sdp_attempts = 0
01-02 14:20:56.053441  2146  3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: dm status: 1
01-02 14:20:56.053461  2146  2634 I BluetoothRemoteDevices: Property type: 1
01-02 14:20:56.053486  2146  2634 I BluetoothRemoteDevices: Skip name update for 70:8F:47:91:B0:62
01-02 14:20:56.053525  2146  2634 I BluetoothRemoteDevices: Property type: 4
01-02 14:20:56.053559  2146  2634 I BluetoothRemoteDevices: Skip class update for 70:8F:47:91:B0:62
01-02 14:20:56.053570  2146  2634 I BluetoothRemoteDevices: Property type: 5
01-02 14:20:56.053601  2146  2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: operator(): HAL bt_hal_cbacks->ssp_request_cb
01-02 14:20:56.053739  2146  2634 I BluetoothBondStateMachine: sspRequestCallback: [B@c3ee023 name: [B@d224920 cod: 5898764 pairingVariant 0 passkey: 363625
01-02 14:20:56.055029  2146  2719 D BluetoothBondStateMachine: PendingCommandState: processMessage msg.what=5 dev=70:8F:47:91:B0:62 (cbx)

4. 連接階段(Connection)

1. 作用

  • 在連接發起前,查詢:

    • 是否已配對

    • 是否支持某 UUID

    • 是否要求安全連接

  • 用于 Profile 判斷:

    • 是否建立連接

    • 是否先 SDP

    • 是否強制加密通道

2. 解決的問題

問題如何解決
每次連接都等待 SDP 完成使用緩存 UUID 快速判斷
Profile 初始化失敗利用緩存狀態預檢能力
重復連接失敗使用緩存 Key 與安全屬性重用連接通道

3. 代碼鑒賞

當 app 層調用 BluetoothDevice.connect 時,將會觸發 bt.server 調用

// android/app/src/com/android/bluetooth/btservice/AdapterService.javaprivate int connectEnabledProfiles(BluetoothDevice device) {ParcelUuid[] remoteDeviceUuids = getRemoteUuids(device); // 會先獲取 該設備的 uuid 信息。 該信息是在 SDP 階段 上報上來的。ParcelUuid[] localDeviceUuids = mAdapterProperties.getUuids();// 如果 遠端和 本地 都支持 a2dp 那么就連接 a2dpif (mA2dpService != null && isSupported(localDeviceUuids, remoteDeviceUuids,BluetoothProfile.A2DP, device)) {Log.i(TAG, "connectEnabledProfiles: Connecting A2dp");mA2dpService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_AUTOCONNECT);mA2dpService.connect(device);}...}public ParcelUuid[] getRemoteUuids(BluetoothDevice device) {DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);if (deviceProp == null) {return null;}return deviceProp.getUuids();}
// android/app/src/com/android/bluetooth/btservice/RemoteDevices.javaParcelUuid[] getUuids() {synchronized (mObject) {return mUuids;}}
  • 下面是 sdp 完成后, 上報 uuid 的日志
01-02 14:21:00.149859  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1696 btif_dm_search_services_evt: SDP search done for 70:8f:47:91:b0:62
01-02 14:21:00.149882  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1698 btif_dm_search_services_evt: sdp_attempts = 0
01-02 14:21:00.149891  2146  3220 I bt_btif_dm: packages/modules/Bluetooth/system/btif/src/btif_dm.cc:1728 btif_dm_search_services_evt: clearing btif pairing_cb
01-02 14:21:00.150000  2146  3220 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: btif_in_fetch_bonded_ble_device Found a LE device: 70:8f:47:91:b0:62
01-02 14:21:00.153761   723   723 I V4L2CameraHAL: V4L2CameraHAL:V4L2CameraHAL:71: ais_v4l2_proxy not ready. try again.
01-02 14:21:00.159003  2146  3220 I bt_bta_dm: packages/modules/Bluetooth/system/bta/dm/bta_dm_act.cc:1371 bta_dm_search_cmpl: No BLE connection, processing classic results
01-02 14:21:00.159092  2146  2634 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: operator(): HAL bt_hal_cbacks->remote_device_properties_cb
01-02 14:21:00.159240  2146  2634 I BluetoothRemoteDevices: Property type: 3
01-02 14:21:00.159360  2146  2634 I BluetoothAdapterService: sendUuidsInternal: Received service discovery UUIDs for device 70:8F:47:91:B0:62
01-02 14:21:00.159403  2146  2634 D BluetoothAdapterService: sendUuidsInternal: index=0 uuid=00001105-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159436  2146  2634 D BluetoothAdapterService: sendUuidsInternal: index=1 uuid=0000110a-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159462  2146  2634 D BluetoothAdapterService: sendUuidsInternal: index=2 uuid=0000110c-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159493  2146  2634 D BluetoothAdapterService: sendUuidsInternal: index=3 uuid=0000110e-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159558  2146  2634 D BluetoothAdapterService: sendUuidsInternal: index=4 uuid=00001112-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159587  2146  2634 D BluetoothAdapterService: sendUuidsInternal: index=5 uuid=00001115-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159607  2146  2634 D BluetoothAdapterService: sendUuidsInternal: index=6 uuid=00001116-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159628  2146  2634 D BluetoothAdapterService: sendUuidsInternal: index=7 uuid=0000111f-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159694  2146  2634 D BluetoothAdapterService: sendUuidsInternal: index=8 uuid=0000112d-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159725  2146  2634 D BluetoothAdapterService: sendUuidsInternal: index=9 uuid=0000112f-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159746  2146  2634 D BluetoothAdapterService: sendUuidsInternal: index=10 uuid=00001132-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159766  2146  2634 D BluetoothAdapterService: sendUuidsInternal: index=11 uuid=00001200-0000-1000-8000-00805f9b34fb
01-02 14:21:00.159792  2146  2634 D BluetoothAdapterService: sendUuidsInternal: index=12 uuid=2c042b0a-7f57-4c0a-afcf-1762af70257c
01-02 14:21:00.159815  2146  2634 D BluetoothAdapterService: sendUuidsInternal: index=13 uuid=8fa9c715-bd1f-596c-a1b0-13162b15c892
01-02 14:21:00.159835  2146  2634 D BluetoothAdapterService: sendUuidsInternal: index=14 uuid=9fed64fd-e91a-499e-88dd-73dfe023feed
01-02 14:21:00.160431  2146  2719 D BluetoothBondStateMachine: StableState: processMessage msg.what=10 dev=70:8F:47:91:B0:62 (cbx)
01-02 14:21:00.161052  2048  2487 D BluetoothDevice: getUuids()
01-02 14:21:00.162895  2146  2719 I BluetoothBondStateMachine: Bond State Change Intent:70:8F:47:91:B0:62 (cbx) BOND_BONDING => BOND_BONDED
void devicePropertyChangedCallback(byte[] address, int[] types, byte[][] values) {Intent intent;byte[] val;int type;BluetoothDevice bdDevice = getDevice(address);DeviceProperties deviceProperties;if (bdDevice == null) {debugLog("Added new device property");deviceProperties = addDeviceProperties(address);bdDevice = getDevice(address);} else {deviceProperties = getDeviceProperties(bdDevice);}if (types.length <= 0) {errorLog("No properties to update");return;}for (int j = 0; j < types.length; j++) {type = types[j];val = values[j];if (val.length > 0) {synchronized (mObject) {infoLog("Property type: " + type);switch (type) {...case AbstractionLayer.BT_PROPERTY_UUIDS: // 0x03int numUuids = val.length / AbstractionLayer.BT_UUID_SIZE;final ParcelUuid[] newUuids = Utils.byteArrayToUuid(val);//ParcelUuid[] uuids = updateUuids(deviceProperties.mUuids, newUuids);if (areUuidsEqual(newUuids, deviceProperties.mUuids)) {infoLog( "Skip uuids update for " + bdDevice.getAddress());break;}deviceProperties.mUuids = newUuids;if (sAdapterService.getState() == BluetoothAdapter.STATE_ON) {sAdapterService.deviceUuidUpdated(bdDevice);sendUuidIntent(bdDevice, deviceProperties);} else if (sAdapterService.getState()== BluetoothAdapter.STATE_BLE_ON) {sAdapterService.deviceUuidUpdated(bdDevice);}break;...}}}}}

5. 重點問題

1. 搜索(Discovery)階段不會寫入 bt_stack.conf 的原因

1. 目的:快速獲取周圍設備信息,不持久化

  • 搜索時獲取到的是臨時的廣播信息(例如設備名稱、地址、Class of Device、RSSI 等),系統主要用于UI 展示或后續配對流程判斷。

  • 尚未配對(bond)或連接的設備,不需要寫入配置文件,因為:

    • 這些信息隨時可能變化;

    • 搜索結果可能包含大量設備,不宜持久化;

    • 節省寫入頻率,減少 I/O 消耗和 flash 損耗;

    • 避免保存不可信或無效設備。


2. 那什么時候才會寫入 bt_config.confbt_stack.conf

設備信息只有在以下場景中,才會被持久化到 bt_config.conf(舊系統)或 bt_stack.conf(AOSP 13 后被 StorageModule 管理)中:

1. 配對成功(Bonding Success)
  • 成功配對后,系統將設備標記為“可信設備”(bonded),會記錄:

    • 地址(MAC)

    • 設備名稱

    • 連接方式(BR/EDR 或 LE)

    • link key / LE key(加密用)

    • profile 支持(如 A2DP、HID 等)

    • services UUIDs(通常來自 SDP)

2. SDP 完成后(部分 Profile 使用)
  • 若設備支持 classic profile,在配對后通常會進行 SDP 查詢服務信息(如 A2DP、HFP)。

  • 得到的 UUID 信息、PSM、版本等,也會被寫入配置中供下次連接使用。

3. 主動連接成功
  • 即使未配對,但連接了某些 BLE 服務,系統也可能緩存部分 GATT 服務信息用于快速重連,但不一定寫入 config 文件。

2. StorageModule 與 config 文件的關系(AOSP 13+)

在 AOSP 13 中,DeviceProperties 模塊已被 StorageModule 接管持久化工作:

  • StorageModule 是新的統一設備信息存取接口。

  • bt_config.conf 文件依然存在于 /data/misc/bluedroid/,但讀寫是通過 StorageModule 來完成的。

  • StorageModule 會在合適的時機(如配對成功、服務發現)將設備信息寫入文件。


3. 總結重點

階段是否寫入配置文件(bt_config.conf / bt_stack.conf)說明
🔍 搜索階段? 不寫入信息臨時,僅用于展示
🤝 配對成功? 寫入標志為可信設備,保存 key 和 profile
📡 SDP 成功? 寫入保存服務 UUID 等
🔌 BLE 連接可能寫入(部分緩存)依 profile 情況而定
📁 存儲模塊StorageModule 統一管理配置負責設備信息加載/保存

6. 總結

階段作用是否寫入配置解決的問題
搜索緩存名稱/類型/RSSI,避免重復設備顯示混亂、信息缺失
SDP緩存支持 UUID提前判斷服務能力
配對緩存 bond/key、安全能力配對信息持久化,安全連接判斷
連接提供連接前判斷支持否(使用緩存)連接初始化優化,profile 可預判

DeviceProperties 是 AOSP 藍牙系統中的“設備狀態中臺”,統一緩存、更新并與 StorageModule 配合寫入持久化文件,確保設備從被發現 → 服務識別 → 配對 → 連接全過程中,系統始終擁有最新、穩定、可持續利用的設備狀態數據


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/910550.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/910550.shtml
英文地址,請注明出處:http://en.pswp.cn/news/910550.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

華為OD-2024年E卷-字母組合[200分] -- python

問題描述&#xff1a; 每個數字對應多個字母&#xff0c;對應關系如下&#xff1a; 0&#xff1a;a,b,c 1&#xff1a;d,e,f 2&#xff1a;g,h,i 3&#xff1a;j,k,l 4&#xff1a;m,n,o 5&#xff1a;p,q,r 6&#xff1a;s,t 7&#xff1a;u,v 8&#xff1a;w,x 9&#xff1…

機器學習競賽中的“A榜”與“B榜”:機制解析與設計深意

在Kaggle、天池等主流機器學習競賽平臺上&#xff0c;“A榜”&#xff08;Public Leaderboard&#xff09;和“B榜”&#xff08;Private Leaderboard&#xff09;是選手們最關注的指標。但很多新人對兩者的區別和設計意圖感到困惑。本文將深入解析其差異及背后的邏輯。 &#…

云徙科技 OMS:讓訂單管理變得輕松又高效

在如今這個線上線下購物融合得越來越緊密的時代&#xff0c;企業要是想在競爭激烈的市場里站穩腳跟&#xff0c;訂單管理這一塊可得好好下功夫。云徙科技的 OMS&#xff08;訂單管理系統&#xff09;就像是給企業量身打造的一把“金鑰匙”&#xff0c;能幫企業把訂單管理得井井…

qt常用控件--02

文章目錄 qt常用控件--02toolTip屬性focusPolicy屬性styleSheet屬性補充知識點按鈕類控件QPushButton 結語 很高興和大家見面&#xff0c;給生活加點impetus&#xff01;&#xff01;開啟今天的編程之路&#xff01;&#xff01; 今天我們進一步c11中常見的新增表達 作者&…

P3258 [JLOI2014] 松鼠的新家

題目描述 松鼠的新家是一棵樹&#xff0c;前幾天剛剛裝修了新家&#xff0c;新家有 n n n 個房間&#xff0c;并且有 n ? 1 n-1 n?1 根樹枝連接&#xff0c;每個房間都可以相互到達&#xff0c;且倆個房間之間的路線都是唯一的。天哪&#xff0c;他居然真的住在“樹”上。 …

基于openfeign攔截器RequestInterceptor實現的微服務之間的夾帶轉發

需求&#xff1a; trade服務需要在下單后清空購物車 分析&#xff1a; 顯然&#xff0c;清空購物車需要調用cart服務&#xff0c;也就是這個功能的實現涉及到了微服務之間的轉發。 其次&#xff0c;清空購車還需要userId&#xff0c;所以需要使用RequestInterceptor來實現夾…

w~深度學習~合集9

我自己的原文哦~ https://blog.51cto.com/whaosoft/14010384 #UPSCALE 這里設計了一個通用算法UPSCALE&#xff0c;可以剪枝具有任意剪枝模式的模型。通過消除約束&#xff0c;UPSCALE將ImageNet精度提高2.1個點。 paper地址&#xff1a;https://arxiv.org/pdf/2307.08…

python如何刪除xml中的w:ascii屬性

可以使用Python的xml.etree.ElementTree模塊通過以下步驟刪除XML中的w:ascii屬性&#xff1a; import xml.etree.ElementTree as ET# 原始XML片段&#xff08;需包含命名空間聲明&#xff09; xml_str <w:rPr xmlns:w"http://schemas.openxmlformats.org/wordproces…

【React】React CSS 樣式設置全攻略

在 React 中設置 CSS 樣式主要有以下幾種方式&#xff0c;各有適用場景&#xff1a; 1. 內聯樣式 (Inline Styles) 直接在 JSX 元素中使用 style 屬性&#xff0c;值為 JavaScript 對象&#xff08;使用駝峰命名法&#xff09; function Component() {return (<div style…

JS紅寶書筆記 8.2 創建對象

雖然使用Object構造函數或對象字面量可以方便地創建對象&#xff0c;但這些方式有明顯不足&#xff1a;創建具有同樣接口的多個對象需要重復編寫很多代碼 工廠模式可以用不同的參數多次調用函數&#xff0c;每次都會返回一個新對象&#xff0c;這種模式雖然可以解決創建多個類…

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

【關注我,后續持續新增專題博文,謝謝!!!】 上一篇我們講了: 這一篇我們開始講: 目錄 一、問題背景 二、DumpDebugInfo原理 2.1:我們分析下代碼 2.2 :Pipeline Dump debug info 2.3 :dump Metadata Pending Node信息 2.4 :Dump Metadata Pool Debug信息 2.5 :No…

【數據結構】_二叉樹基礎OJ

目錄 1. 單值二叉樹 1.1 題目鏈接與描述 1.2 解題思路 1.3 程序 2. 相同的樹 2.1 題目鏈接與描述 2.2 解題思路 2.3 程序 3. 對稱二叉樹 3.1 題目鏈接與描述 3.2 解題思路 3.3 程序 1. 單值二叉樹 1.1 題目鏈接與描述 題目鏈接&#xff1a; 965. 單值二叉樹 - 力…

軟件工程畫圖題

目錄 1.大綱 2.數據流圖 3.程序流圖 4.流圖 5.ER圖 6.層次圖 7.結構圖 8.盒圖 9.狀態轉換圖 10.類圖 11.用例圖 12.活動圖 13.判定表和判定樹 14.基本路徑測試過程(白盒測試) 15.等價類劃分(黑盒測試) 1.大綱 (1).數據流圖 (2).程序流圖 (3).流圖 (4).ER圖…

H7-TOOL自制Flash讀寫保護算法系列,為華大電子CIU32F003制作使能和解除算法,支持在線燒錄和脫機燒錄使用2025-06-20

說明&#xff1a; 很多IC廠家僅發布了內部Flash算法文件&#xff0c;并沒有提供讀寫保護算法文件&#xff0c;也就是選項字節算法文件&#xff0c;需要我們制作。 實際上當前已經發布的TOOL版本&#xff0c;已經自制很多了&#xff0c;比如已經支持的兆易創新大部分型號&…

go channel用法

介紹 channel 在 Go 中是一種專門用來在 goroutine 之間傳遞數據的類型安全的管道。 你可以把它理解成&#xff1a; 多個 goroutine 之間的**“傳話筒”**&#xff0c;誰往通道里塞東西&#xff0c;另一個 goroutine 就能接收到。 Go 語言采用 CSP&#xff08;Communicatin…

openLayers切換基于高德、天地圖切換矢量、影像、地形圖層

1、需要先加載好地圖&#xff0c;具體點此鏈接 openLayers添加天地圖WMTS、XYZ瓦片服務圖層、高德地圖XYZ瓦片服務圖層-CSDN博客文章瀏覽閱讀31次。本文介紹了基于OpenLayers的地圖交互功能實現&#xff0c;主要包括以下內容&#xff1a; 地圖初始化&#xff1a;支持天地圖XYZ…

springMVC-15 異常處理

異常處理-基本介紹 基本介紹 1.Spring MVC通過HandlerExceptionResolver處理程序的異常&#xff0c;包括Handler映射、數據綁定以及目標方法執行時發生的異常。 2.主要處理Handler中用ExceptionHandler注解定義的方法。 3.ExceptionHandlerMethodResolver內部若找不到Excepti…

視頻匯聚EasyCVR平臺v3.7.2發布:新增全局搜索、播放器默認解碼方式等4大功能

EasyCVR視頻匯聚平臺帶著全新的v3.7.2版本重磅登場&#xff01;此次升級&#xff0c;絕非簡單的功能堆砌&#xff0c;而是從用戶體驗、操作效率以及系統性能等多維度進行的深度優化與革新&#xff0c;旨在為大家帶來更加強大、穩定且高效的視頻監控管理體驗。 一、全局功能搜索…

三、kubectl使用詳解

三、kubectl使用詳解 文章目錄 三、kubectl使用詳解1、常用基礎命令1.1 Kubectl命令格式1.2 查詢一個資源1.3 創建一個資源1.4 修改一個資源1.5 刪除一個資源1.6 其他 2、K8s隔離機制Namespace&#xff08;命名空間作用及使用&#xff09;2.1 什么是命名空間2.2 命名空間主要作…

JVM內存模型詳解

JVM內存模型詳解 Java虛擬機(JVM)內存模型是理解Java程序運行機制的核心&#xff0c;它定義了程序運行時數據的組織方式和訪問規則。與Java內存模型(JMM)關注并發不同&#xff0c;JVM內存模型主要描述運行時數據區的結構和功能。 一、JVM內存模型概述 JVM內存模型將運行時數…