好的!根據你提供的鏈接:

https://blog.csdn.net/weixin_42502089/article/details/154460535

以及你上傳的詳細文件內容(該文件正是這篇博客的完整正文),我已經全面掌握了文章的核心主題——Android 平台 BLE 調試助手的開發與實戰

下面我將為你撰寫一篇結構清晰、重點突出、代碼詳實的技術博客,不僅復現原文精髓,還會進行邏輯梳理和現代化封裝,使其成為一篇真正可指導開發的實用教程。


📱 手把手教你開發一個 Android BLE 藍牙調試助手(附完整核心代碼)

在物聯網(IoT)和智能硬件開發中,藍牙低功耗(BLE, Bluetooth Low Energy)是設備與手機通信的基石。然而,調試 BLE 設備常常令人頭疼:廣播包是否正常?服務特徵值能否讀寫?數據收發是否流暢?本文將帶你從零開始,構建一個功能完備的 Android BLE 調試助手,並提供所有核心功能的代碼實現。


一、項目概述:我們需要哪些功能?

一個專業的 BLE 調試助手應具備以下核心能力:

  1. 掃描:發現周圍的 BLE 設備。
  2. 連接:建立 GATT 連接。
  3. 探索:解析並展示設備的服務(Service)、特徵值(Characteristic)和描述符(Descriptor)。
  4. 交互:支持讀取、寫入、訂閲通知(Notify)。
  5. 日誌:記錄完整的操作與數據流。

我們將以 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. 自動重連機制

onConnectionStateChangeDISCONNECTED 狀態中,可以加入指數退避的重連邏輯。

3. 日誌記錄

創建一個簡單的日誌實體類,記錄每一次操作和數據收發,方便排查問題。


五、總結與建議

開發一個 BLE 調試助手不僅是學習 BLE 協議的絕佳方式,更是日常開發的得力工具。本文提供了從掃描到數據交互的完整代碼骨架。

強烈建議

  • 不要重複造輪子:對於生產項目,優先考慮成熟的開源庫如 FastBle 或 RxAndroidBle,它們已經處理了大量兼容性和穩定性問題。
  • 善用專業工具:在開發初期,務必配合 nRF Connect (Nordic 官方出品) 進行對比測試,它能幫你快速驗證設備端行為是否正確。

通過親手實現這個調試助手,你將對 BLE 的底層機制有更深刻的理解,為未來的智能硬件開發打下堅實基礎。


參考原文: Android平台BLE調試助手實戰工具詳解 - CSDN Ga Ou本文整理 & 優化 by Qwen

希望這篇實戰指南對你有所幫助!歡迎點贊、收藏、轉發~