博客 / 詳情

返回

Android 車機與 BLE 設備交互

Android 車機與 BLE 設備交互全鏈路實踐指南

從廣播地址、配對綁定到隱私機制的完整理解

最近在開發車機系統與無屏 BLE 設備(比如智能冰箱)的連接功能,過程中遇到了一連串看似獨立、實則緊密關聯的問題:為什麼掃描到的地址和配對時一樣?RPA 地址到底能不能看到?解綁為什麼沒有公開 API?回連時直接用連接成功時保存的 MAC 行不行?ADB 怎麼清空配對列表?

這些問題背後其實是一套完整的 BLE 安全與隱私機制。這篇文章把整個探索過程串起來,記錄下驗證過的方法和踩過的坑,希望能幫到正在做類似工作的你。


一、設備廣播地址:你以為的“MAC”可能不是真 MAC

一開始我在連接成功後將設備的MAC保存到SP中,後續APP啓動或者藍牙開啓後直接根據整個mac直接去連接設備,

大致流程如圖:

connect_process

但是有的設備是OK的,有的設備不行,後來發現不行的設備是開啓了BLE Privacy機制,無法直接根據連接時返回的mac直接連接

後來發現這涉及到 BLE(Bluetooth Low Energy)隱私保護機制 的核心設計 —— RPA(Resolvable Private Address,可解析私有地址)

首先看下BLE設備的廣播類型

BLE 廣播地址的類型

根據 BLE 規範(Core Spec Vol 6, Part B, Section 1.3),廣播包中的 AdvA(Advertiser Address) 可以是以下之一:

地址類型 是否可變 是否可被解析 説明
Public Device Address ❌ 固定 ✅ 是 廠商燒錄的 MAC
Static Device Address ❌ 固定 ✅ 是 設備自定義的靜態隨機地址
Resolvable Private Address (RPA) ✅ 動態(如每15分鐘換) ✅ 僅對有 IRK 的設備 隱私保護,可被配對設備解析
Non-resolvable Private Address ✅ 動態 ❌ 否 完全匿名,無法追蹤也無法連接(通常用於 beacon)
📌 大多數支持配對的智能設備(如你的冰箱)會使用 RPA 來廣播,以保護用户隱私。

傳統 MAC 地址的問題

早期 BLE 設備使用 靜態公開地址(Public Static Address),比如:

AA:BB:CC:11:22:33

這個地址是固定的、全球唯一(理論上),但也帶來嚴重隱私問題:

🕵️‍♂️ 攻擊者可以在商場、地鐵等公共場所通過掃描 BLE 廣播包,追蹤某個設備(比如你的手機或冰箱)的行蹤。

為了解決這個問題,BLE 4.0 引入了 Privacy Feature(隱私特性),允許設備使用 RPA 隨機變化的地址,而不是固定 MAC。


🛡️ RPA(Resolvable Private Address)是什麼?

RPA 是一種 動態變化但可被“信任設備”識別 的地址機制。

工作原理簡述:

  1. 配對(Bonding)時,雙方交換一個密鑰:IRK(Identity Resolving Key)
  2. 此後,設備會定期(如每 15 分鐘)生成一個新的 隨機地址(RPA),該地址由 IRK + 隨機數加密生成。
  3. 只有擁有相同 IRK 的設備(即已配對設備)才能 “解析”這個 RPA,確認它來自同一個物理設備
  4. 對未配對的第三方來説,看到的只是一個不斷變化的隨機地址,無法追蹤。

優點:既保護隱私,又不影響已配對設備之間的通信


什麼是Identify Address

  • Identity Address

    是設備在配對(Pairing + Bonding)過程中交換的“真實身份”,格式為:

    • Public Address(如廠商燒錄的 MAC)
    • 或 Static Random Address(由設備製造商設定,固定不變)
  • 這個地址 在設備生命週期內是固定的,也是 Android 系統在 BluetoothDevice.getAddress() 中返回的值(對於 bonded 設備)。
  • IRK(Identity Resolving Key)就是用來將 RPA 映射回這個 Identity Address 的。

📱 Android 如何處理 RPA?

  • 當你和一個支持隱私特性的 BLE 設備(如現代智能冰箱)完成 配對(bonding) 後:

    • Android 系統會 自動保存該設備的 IRK
    • 即使冰箱下次廣播的是一個全新的 RPA(比如 D4:E5:F6:77:88:99),Android 也能通過 IRK 識別出:“這是之前配對過的那台冰箱”。
  • 此時,你在代碼中調用:

    BluetoothAdapter.getDefaultAdapter().getBondedDevices()

    返回的 BluetoothDevice 對象的 .getAddress() 始終是配對時的“身份地址”(Identity Address),通常是 Public 或 Static 地址(如 AA:BB:CC:11:22:33),而不是當前廣播的 RPA

✅ 所以:系統內部已經幫你完成了 RPA → Identity Address 的映射

這就回到了為什麼直接根據掃描到的mac地址直接回連設備會失敗的問題了

❌ 為什麼不要直接用存儲的 MAC 字符串調用 getRemoteDevice(mac)

假設你把掃描到的 MAC(如 "AA:BB:CC:11:22:33")存到 SharedPreferences,下次直接:

String savedMac = prefs.getString("fridge_mac", null);
BluetoothDevice dev = adapter.getRemoteDevice(savedMac); 
dev.connectGatt(...);
  1. getRemoteDevice(mac) 僅根據地址字符串返回一個設備引用,它不保證能訪問到該設備的綁定上下文(如 IRK)。如果傳入的地址不是已配對設備的 Identity Address(例如是一個 RPA),即使物理設備已綁定,系統也無法自動解析隱私地址
  2. 如果此時冰箱正在使用 RPA(比如廣播地址是 D4:E5:F6:77:88:99),而你傳入的是舊的 Identity Address(AA:BB:CC:...);
  3. Android 不會自動用 IRK 去解析或關聯這個 RPA,因為 getRemoteDevice() 不知道這個設備是否已配對;
  4. 結果:連接失敗(GATT ERROR 133 或 timeout),即使物理設備就在旁邊!
💡 換句話説:getRemoteDevice() 繞過了系統的 bonding 數據庫和 IRK 解析機制。

✅ 正確做法:從 getBondedDevices() 中查找

String savedIdentityAddress = "AA:BB:CC:11:22:33"; // 這是你配對時記錄的身份地址

BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
for (BluetoothDevice device : adapter.getBondedDevices()) {
    if (device.getAddress().equals(savedIdentityAddress)) {
        // ✅ 這個 device 對象是系統管理的 bonded 設備
        // 即使冰箱當前用 RPA 廣播,系統也會自動解析並建立連接
        device.connectGatt(context, false, gattCallback);
        break;
    }
}

這樣做的好處:

  • 利用了 Android 內置的 IRK 解析能力
  • 無論冰箱當前使用什麼 RPA,系統都能正確路由到物理設備;
  • 連接成功率高,符合 BLE 規範。

🔧 補充建議

  • 要記錄配對時的mac地址而不是掃描到的mac地址,因為掃描的到mac可能是PRA,但是綁定時的mac一定是Identify Address
  • 在配對完成後,記錄的是 device.getAddress()(即 Identity Address),這個地址在 bonding 生命週期內是穩定的;
  • 不要嘗試自己解析 RPA(除非你實現完整的 BLE Host 層,不推薦);
  • 如果目標設備 不支持 Privacy(即始終用 Public Address),那麼 getRemoteDevice() 也能工作,但為了兼容性和未來升級,仍建議走 bonded devices 路徑。

✅ 總結

方式 是否推薦 原因
getRemoteDevice(savedMac) ❌ 不推薦 無法利用 IRK 解析 RPA,連接可能失敗
getBondedDevices() 查找匹配地址 ✅ 強烈推薦 系統自動處理 RPA,連接可靠

所以,永遠優先使用系統提供的 bonded device 對象來發起連接,而不是自己構造設備對象。這不僅是最佳實踐,也是應對現代 BLE 隱私機制的必要手段。

Android 在設備綁定後,所有 API 返回的都是 Identity Address(身份地址),而不是設備當前廣播的地址

BLE 設備可以廣播四種地址:

  • Public Address:芯片的固定 MAC;
  • Static Random Address:開機生成、運行期間不變的隨機地址;
  • Non-Resolvable Private Address (NRPA):頻繁變化、無法追蹤的臨時地址;
  • Resolvable Private Address (RPA):頻繁變化,但持有 IRK 的設備能解析回 Identity Address。

真正的 Privacy 機制 = 使用 RPA 廣播 + IRK 解析

當你在車機上調用 device.getAddress(),如果設備已綁定,Android 會自動用 IRK 把 RPA “翻譯” 成 Identity Address 再返回給你。所以你永遠看不到那個變化的 RPA —— 這不是 bug,而是 Privacy 正常工作的表現。

✅ 驗證方法:用手機裝 nRF Connect,在未綁定狀態下掃描設備,隔 15 分鐘看地址是否變化。如果變了,説明 Privacy 生效了。

二、RPA 地址能看到嗎?怎麼獲取原始廣播地址?

既然系統把 RPA 隱藏了,那我們還能不能拿到真實的廣播地址?

答案是:只有在未綁定狀態下才可能看到

當你調用 BluetoothLeScanner.startScan(),如果設備還沒配對,且它廣播的是 RPA,那麼 ScanResult.device.getAddress() 返回的就是這個原始 RPA(比如 D3:A1:F5:09:88:77)。但一旦你完成綁定,下次再掃,系統就會直接給你 Identity Address。

所以,不要試圖在綁定後獲取 RPA——你不需要它。Identity Address 才是穩定的設備標識,RPA 只是空中傳輸的“馬甲”。

如果你是在調試固件,建議用 nRF Connect 或藍牙嗅探器抓包;如果是開發車機 App,請完全忽略 RPA 的存在,只認 getBondedDevices() 裏的地址。


三、回連時直接用保存的 MAC 地址行不行?

早期我們圖省事,配對成功後把設備地址存下來,下次啓動直接用:

val device = adapter.getRemoteDevice(savedMac)
device.connectGatt(...)

結果某天測試新固件(啓用了 RPA)時,連接直接超時失敗。

原因很簡單:getRemoteDevice() 創建的是一個“裸設備對象”,它沒有 IRK,也不知道這個地址對應的是誰。而冰箱此刻廣播的是 RPA,根本不在 savedMac 這個地址上。

正確做法是:

val device = adapter.bondedDevices.find { it.address == savedMac }
device?.connectGatt(...)

因為 bondedDevices 裏的設備對象帶着完整的綁定上下文(包括 IRK),系統能自動把 RPA 解析出來並建立連接。

📌 記住:地址字符串相同 ≠ 設備對象等價。安全上下文才是關鍵。

2. Android 如何處理這個地址?

  • onLeScan() 中,Android 直接把廣播包裏的 AdvA 字段原樣封裝成 BluetoothDevice 對象的地址
  • 此時系統 還不知道這個設備是否已配對,也沒有嘗試用 IRK 去解析它(因為還沒建立 bonding 上下文);
  • 所以:device.getAddress() 就是原始廣播地址(raw advertising address)

✅ 舉例:

  • 冰箱的 Identity Address 是 AA:BB:CC:11:22:33(Public);
  • 當前廣播使用 RPA:D4:E5:F6:77:88:99
  • 你在 onLeScan() 中拿到的 device.getAddress() 就是 "D4:E5:F6:77:88:99"
  • 即使你之前已經和這台冰箱配對過,掃描回調仍然返回 RPA,因為這是物理層看到的內容。
⚠️ 這就是為什麼不能在掃描階段存儲這個地址作為設備唯一標識!

3. 那系統怎麼知道這是“老朋友”?

  • 當你調用

    device.createBond() 
    或
    device.connectGatt(...)

    時,Android 會:

    1. 檢查本地是否有該 Identity Address 對應的 IRK(即是否已配對);
    2. 如果有,就嘗試用 IRK 解析當前 RPA;
    3. 如果解析成功(RPA 能還原出已知 Identity Address),就走快速重連流程(無需重新配對);
    4. 連接成功後,BluetoothDevice.getAddress() 在後續 API 調用中(如 GATT 回調、bonded devices 列表)會返回 Identity Address
  • public Address和identify Address 是一回事嗎?

  • Public Address 是 Identity Address 的一種,但 Identity Address 不一定是 Public Address。

    換句話説:

    • Identity Address(身份地址)是一個邏輯概念,用於唯一標識一個 BLE 設備;
    • 它可以是以下兩種之一:

      1. Public Device Address(公開地址,即傳統 MAC 地址)
      2. Static Random Address(靜態隨機地址)

    所以:
    ✅ 所有 Public Address 都是 Identity Address,
    ❌ 但不是所有 Identity Address 都是 Public Address。


    1. 什麼是 Identity Address(身份地址)?

    根據 Bluetooth Core Specification(BLE 核心規範)

    The Identity Address is the address used to identify a device during the pairing and bonding process. It is either:

    • A Public Device Address, or
    • A Static Random Address

    這個地址在設備的整個生命週期中是 固定不變的,並且會和 IRK(Identity Resolving Key) 一起在配對時交換,用於後續解析 RPA(Resolvable Private Address)。


    2. Public Device Address(公開地址)

    • 就是我們熟悉的 48-bit IEEE MAC 地址,如 AA:BB:CC:11:22:33
    • 由廠商燒錄,全球唯一(理論上);
    • 地址的 最高有效位(MSB)為 0(即“公共地址”標誌);
    • 需要向 IEEE(通過 SIG 或直接)購買
    • 示例:D0:CF:5E:xx:xx:xx(很多手機/模塊使用)。

    ✅ 特點:固定、可識別、無隱私保護。


    3. Static Random Address(靜態隨機地址)

    • 由設備製造商或開發者設定的一個 隨機生成但永不改變 的地址;
    • 必須滿足:

      • 最高兩位為 11(表示是靜態隨機地址);
      • 不能是全 0 或全 1;
    • 示例:DE:AD:BE:EF:CA:FE(只要符合格式且固定即可)。

    ✅ 特點:固定、不依賴 IEEE 分配、有一定匿名性,但仍可作為身份標識。

    📌 很多 IoT 設備(如低成本 BLE 模塊)沒有 Public Address,就用 Static Random Address 作為 Identity Address。

    4. 為什麼需要區分?

    因為 BLE 隱私機制(RPA)依賴於 Identity Address + IRK 的組合:

    • 當設備啓用隱私功能時,它會用 IRK 生成 RPA 來廣播;
    • 配對設備收到 RPA 後,用本地存儲的 IRK 嘗試還原出 Identity Address
    • 如果匹配成功,就知道“這是之前配對過的那台設備”。

    所以,無論 Identity Address 是 Public 還是 Static Random,只要它是固定的,就能作為“身份錨點”。


💡 實際開發中,你不需要關心它是 Public 還是 Static Random —— 只需知道:這是該設備的唯一身份標識,配對後穩定不變,應該存儲它。

------

## ✅ 總結表

概念 是否固定 是否用於配對 是否可用於長期標識 備註
Public Address ✅ 是 ✅ 是 ✅ 是 傳統 MAC,IEEE 分配
Static Random Address ✅ 是 ✅ 是 ✅ 是 設備自定義,高位為 11
Identity Address ✅ 是 ✅ 是 ✅ 是 = Public 或 Static Random
RPA(Resolvable Private Address) ❌ 否(動態) ❌ 否 ❌ 否 用於廣播,保護隱私
Non-resolvable Private Address ❌ 否 ❌ 否 ❌ 否 完全匿名,通常不可連接

------

### 🎯 開發建議

  • 不要嘗試解析地址類型,只需在 BOND_BONDED 時存儲 device.getAddress()
  • 這個地址就是系統認可的 Identity Address,無論底層是 Public 還是 Static Random;
  • 後續通過 getBondedDevices() 匹配該地址即可可靠連接。

四、配對彈窗是怎麼來的?

我們從來沒調 createBond(),為什麼也會彈出系統配對窗口?

後來發現,觸發配對的不是你的代碼,而是 GATT 特徵的安全屬性

如果你的冰箱聲明某個特徵需要“認證後才能讀寫”(比如設置了 AUTHEN 權限),而當前連接還沒加密,那 Android 在收到 Insufficient Authentication 錯誤後,會自動啓動配對流程。

所以,配對彈窗其實是系統在幫你補安全課。你只需要在配對成功後重試 GATT 操作即可。


五、配對流程

tongyi-mermaid-2026-01-06-194500.png

在標準 BLE 通信模型中,配對(Pairing)流程是由 Central(車機)發起的,但實際觸發時機往往由 Peripheral(BLE設備,如冰箱)的安全需求間接驅動

一、協議層面:誰“發起”配對?

根據 Bluetooth Core Specification(BLE 協議規範)

  • Central(主設備,如車機) 負責發送 Pairing Request
  • Peripheral(從設備,如冰箱) 回覆 Pairing Response
  • 後續密鑰交換、確認值計算等均由 Central 主導。

✅ 所以從協議動作看,配對是由 Central(車機)主動發起的


二、應用層面:誰“觸發”配對?

雖然 Central 發起配對請求,但它通常不是憑空發起,而是因為:

Peripheral 在 GATT 層拒絕了未加密的訪問請求,從而迫使 Central 啓動配對。

典型流程如下:

  1. 車機(Central)連接冰箱(Peripheral);
  2. 車機嘗試讀取一個被標記為 “需要認證” 的特徵(例如 read authen);
  3. 冰箱返回錯誤:Insufficient Authentication (0x05)
  4. Android 系統檢測到該錯誤,自動調用 createBond() 併發送 Pairing Request
  5. 配對流程啓動,彈出系統彈窗或走 Just Works 模式。

✅ 所以從觸發原因看,是 Peripheral 的安全策略“迫使” Central 發起配對

既然是車機發起的配對請求,那車機-Android系統怎麼還能收到配對請求廣播呢?

車機(App)收到的 ACTION_PAIRING_REQUEST 廣播,不是來自遠端設備的“請求”,而是 Android 系統在自己發起配對前,向 App 發出的一個“協商/干預”通知

這個廣播的目的是:讓 App 有機會干預配對行為

例如:

  • 自動確認 Just Works 配對(免彈窗);
  • 填入預共享的 PIN 碼;
  • 拒絕某些設備的配對。

Pairing 和 Bonding 區別?

這兩個概念經常混用,但職責完全不同:

  • Pairing(配對):協商密鑰的過程(生成 LTK、IRK 等),確保本次連接加密;
  • Bonding(綁定):把配對成果(密鑰 + 身份)存到本地,供以後複用。

你可以只配對不綁定(每次連都重新確認),但不能只綁定不配對。在 Android 裏,配對成功默認就會綁定,設備進入 getBondedDevices() 列表。


六、怎麼用代碼解綁設備?

Android 沒有公開 removeBond() 方法,但它確實存在,只是被標為 @hide。通過反射調用是行業通用做法:

fun removeBond(device: BluetoothDevice): Boolean {
    return try {
        val method = device::class.java.getMethod("removeBond")
        method.invoke(device) as Boolean
    } catch (e: Exception) {
        false
    }
}

注意:

  • 需要 BLUETOOTH_ADMIN 權限;
  • 解綁是異步的,必須監聽 ACTION_BOND_STATE_CHANGED 廣播,等狀態變成 BOND_NONE
  • 最好加兜底:如果反射失敗,引導用户去系統設置手動解綁。

至於為什麼不公開?主要是怕 App 濫用——比如偷偷刪掉用户的耳機配對。但又不能完全禁掉,畢竟車機、IoT 設備確實需要程序化解綁。所以成了“能用,但不鼓勵”的狀態。


總結:幾個關鍵原則

  1. 地址不變是正常的:那是 Identity Address,不是廣播地址;
  2. RPA 不需要你關心:系統會自動處理,你只認 bonded 列表;
  3. 回連必須從 getBondedDevices() 取設備,否則 Privacy 一開就斷連;
  4. 配對由 GATT 安全需求驅動,不是靠你調不調 createBond()
  5. 解綁用反射沒問題,但要做好兼容和用户引導;
  6. Privacy 是好東西,但前提是兩端都正確實現——設備要真啓用 RPA,車機要會解析。

BLE 看似簡單,但安全和隱私細節很多。理解這套機制,才能做出既好用又安全的產品。希望這篇總結能幫你少走點彎路。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.