好的!根據你提供的鏈接:
https://blog.csdn.net/weixin_42502089/article/details/154460535
以及你上傳的詳細文件內容(該文件正是這篇博客的完整正文),我已經全面掌握了文章的核心主題——Android 平台 BLE 調試助手的開發與實戰。
下面我將為你撰寫一篇結構清晰、重點突出、代碼詳實的技術博客,不僅復現原文精髓,還會進行邏輯梳理和現代化封裝,使其成為一篇真正可指導開發的實用教程。
📱 手把手教你開發一個 Android BLE 藍牙調試助手(附完整核心代碼)
在物聯網(IoT)和智能硬件開發中,藍牙低功耗(BLE, Bluetooth Low Energy)是設備與手機通信的基石。然而,調試 BLE 設備常常令人頭疼:廣播包是否正常?服務特徵值能否讀寫?數據收發是否流暢?本文將帶你從零開始,構建一個功能完備的 Android BLE 調試助手,並提供所有核心功能的代碼實現。
一、項目概述:我們需要哪些功能?
一個專業的 BLE 調試助手應具備以下核心能力:
- 掃描:發現周圍的 BLE 設備。
- 連接:建立 GATT 連接。
- 探索:解析並展示設備的服務(Service)、特徵值(Characteristic)和描述符(Descriptor)。
- 交互:支持讀取、寫入、訂閲通知(Notify)。
- 日誌:記錄完整的操作與數據流。
我們將以 Kotlin 語言,基於 Android 官方 BLE API 來實現這些功能。
二、前期準備:權限與依賴
1. 聲明權限(AndroidManifest.xml)
<!-- 必需權限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Android 12+ (API 31) 新權限模型 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
2. 動態申請權限(Activity 中)
private fun requestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val permissions = arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT
)
requestPermissions(permissions, REQUEST_CODE_BLUETOOTH)
} else {
// Android 11 及以下,使用舊權限
requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), REQUEST_CODE_LOCATION)
}
}
三、核心功能實現
功能 1:BLE 設備掃描
我們使用 BluetoothLeScanner 進行高效掃描,並通過回調處理結果。
class BleScanner(private val context: Context) {
private val bluetoothAdapter: BluetoothAdapter by lazy {
(context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter
}
private val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
super.onScanResult(callbackType, result)
val device = result.device
val rssi = result.rssi
val name = result.scanRecord?.deviceName ?: "Unknown"
// 將設備信息傳遞給 UI 層
onDeviceDiscovered(device, name, rssi)
}
override fun onScanFailed(errorCode: Int) {
Log.e("BleScanner", "Scan failed with error code: $errorCode")
}
}
fun startScan() {
val scanner = bluetoothAdapter.bluetoothLeScanner ?: return
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_BALANCED) // 平衡模式
.build()
scanner.startScan(null, settings, scanCallback) // null 表示不過濾
}
fun stopScan() {
bluetoothAdapter.bluetoothLeScanner?.stopScan(scanCallback)
}
// 回調接口,用於通知上層發現新設備
var onDeviceDiscovered: (BluetoothDevice, String, Int) -> Unit = { _, _, _ -> }
}
功能 2:GATT 連接管理
連接是後續所有操作的基礎。我們必須妥善管理 BluetoothGatt 的生命週期。
class BleConnectionManager(private val context: Context) {
private var gatt: BluetoothGatt? = null
private var isConnected = false
private val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
when (newState) {
BluetoothProfile.STATE_CONNECTED -> {
Log.d("BleConn", "Connected successfully!")
this@BleConnectionManager.gatt = gatt
isConnected = true
// 關鍵:連接成功後必須主動發現服務
gatt.discoverServices()
}
BluetoothProfile.STATE_DISCONNECTED -> {
Log.w("BleConn", "Disconnected.")
isConnected = false
this@BleConnectionManager.gatt = null
onDisconnected()
}
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// 服務發現成功,通知上層可以開始交互了
onServicesReady(gatt.services)
} else {
Log.e("BleConn", "Service discovery failed.")
}
}
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
onDataReceived(characteristic.uuid, characteristic.value)
}
}
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic
) {
// 接收 Notify/Indicate 數據
onDataReceived(characteristic.uuid, characteristic.value)
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d("BleConn", "Write successful.")
}
}
}
fun connect(device: BluetoothDevice) {
gatt = device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
}
fun disconnect() {
gatt?.disconnect()
// 注意:不要在此處立即 close(),等待 onConnectionStateChange 回調後再 close
}
fun close() {
gatt?.close()
gatt = null
}
// ... 其他交互方法(read, write, enableNotification)將在下文介紹
var onDisconnected: () -> Unit = {}
var onServicesReady: (List<BluetoothGattService>) -> Unit = {}
var onDataReceived: (UUID, ByteArray) -> Unit = { _, _ -> }
}
功能 3:服務與特徵值探索
一旦 onServicesDiscovered 回調觸發,我們就可以遍歷整個 GATT 樹。
// 工具函數:打印 GATT 結構
fun printGattServices(services: List<BluetoothGattService>) {
for (service in services) {
Log.d("GATT", "Service: ${service.uuid}")
for (characteristic in service.characteristics) {
val props = getPropertiesString(characteristic.properties)
Log.d("GATT", " -> Char: ${characteristic.uuid} [$props]")
for (descriptor in characteristic.descriptors) {
Log.d("GATT", " -> Desc: ${descriptor.uuid}")
}
}
}
}
private fun getPropertiesString(properties: Int): String {
val list = mutableListOf<String>()
if (properties and BluetoothGattCharacteristic.PROPERTY_READ != 0) list.add("READ")
if (properties and BluetoothGattCharacteristic.PROPERTY_WRITE != 0) list.add("WRITE")
if (properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0) list.add("NOTIFY")
return list.joinToString(" | ")
}
功能 4:數據交互(讀、寫、訂閲)
讀取特徵值
fun readCharacteristic(serviceUuid: UUID, charUuid: UUID) {
val service = gatt?.getService(serviceUuid) ?: return
val characteristic = service.getCharacteristic(charUuid) ?: return
gatt?.readCharacteristic(characteristic)
}
寫入特徵值
fun writeCharacteristic(serviceUuid: UUID, charUuid: UUID, data: ByteArray) {
val service = gatt?.getService(serviceUuid) ?: return
val characteristic = service.getCharacteristic(charUuid) ?: return
characteristic.value = data
// 選擇寫入模式
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT // 帶響應
// characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE // 不帶響應
gatt?.writeCharacteristic(characteristic)
}
訂閲通知(關鍵步驟!)
要接收設備推送的數據,必須先向 CCCD (Client Characteristic Configuration Descriptor) 寫入啓用指令。
fun enableNotification(serviceUuid: UUID, charUuid: UUID, enable: Boolean) {
val service = gatt?.getService(serviceUuid) ?: return
val characteristic = service.getCharacteristic(charUuid) ?: return
// 第一步:啓用本地通知
gatt?.setCharacteristicNotification(characteristic, enable)
// 第二步:找到 CCCD 描述符並寫入
val descriptor = characteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))
if (descriptor != null) {
descriptor.value = if (enable) {
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
} else {
BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
}
gatt?.writeDescriptor(descriptor)
}
}
四、高級技巧:提升穩定性和用户體驗
1. 連接超時處理
Android 系統沒有直接的連接超時 API,我們可以在應用層用 Handler 實現。
private val connectionTimeoutHandler = Handler(Looper.getMainLooper())
private val connectionTimeoutRunnable = Runnable {
Log.e("BleConn", "Connection timeout!")
gatt?.disconnect()
}
// 發起連接後啓動計時器
connectionTimeoutHandler.postDelayed(connectionTimeoutRunnable, 15_000) // 15秒超時
// 連接成功後移除計時器
connectionTimeoutHandler.removeCallbacks(connectionTimeoutRunnable)
2. 自動重連機制
在 onConnectionStateChange 的 DISCONNECTED 狀態中,可以加入指數退避的重連邏輯。
3. 日誌記錄
創建一個簡單的日誌實體類,記錄每一次操作和數據收發,方便排查問題。
五、總結與建議
開發一個 BLE 調試助手不僅是學習 BLE 協議的絕佳方式,更是日常開發的得力工具。本文提供了從掃描到數據交互的完整代碼骨架。
強烈建議:
- 不要重複造輪子:對於生產項目,優先考慮成熟的開源庫如 FastBle 或 RxAndroidBle,它們已經處理了大量兼容性和穩定性問題。
- 善用專業工具:在開發初期,務必配合 nRF Connect (Nordic 官方出品) 進行對比測試,它能幫你快速驗證設備端行為是否正確。
通過親手實現這個調試助手,你將對 BLE 的底層機制有更深刻的理解,為未來的智能硬件開發打下堅實基礎。
參考原文: Android平台BLE調試助手實戰工具詳解 - CSDN Ga Ou本文整理 & 優化 by Qwen
希望這篇實戰指南對你有所幫助!歡迎點贊、收藏、轉發~