在日常使用中經常會用到Android中使用GATT連接藍牙設備(BLE),下面整理了一篇藍牙連接的完整流程。

1. 基礎概念

GATT (Generic Attribute Profile) 是BLE設備之間數據傳輸的標準協議,基於Client-Server架構:

  • Central設備 (客户端):Android手機,主動連接
  • Peripheral設備 (服務器):BLE設備,提供服務和特徵

2. AndroidManifest權限配置

<manifest>
    <!-- 藍牙權限 -->
    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    
    <!-- Android 12+ 需要額外權限 -->
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"
        android:usesPermissionFlags="neverForLocation"/>
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
    
    <!-- Android 6.0+ 需要位置權限用於掃描 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    
    <uses-feature android:name="android.hardware.bluetooth_le" 
                  android:required="true"/>
</manifest>

3. 基礎連接流程

3.1 初始化BluetoothAdapter

class BluetoothGattManager(
    private val context: Context
) {
    private var bluetoothAdapter: BluetoothAdapter? = null
    private var bluetoothLeScanner: BluetoothLeScanner? = null
    private var bluetoothGatt: BluetoothGatt? = null
    
    fun initialize(): Boolean {
        val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) 
                as BluetoothManager
        bluetoothAdapter = bluetoothManager.adapter
        
        return bluetoothAdapter != null && bluetoothAdapter!!.isEnabled
    }
    
    // 檢查並請求藍牙權限
    private fun checkPermissions(): Boolean {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            ContextCompat.checkSelfPermission(context, 
                Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED &&
            ContextCompat.checkSelfPermission(context, 
                Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED
        } else {
            ContextCompat.checkSelfPermission(context, 
                Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
        }
    }
}

3.2 掃描BLE設備

class BluetoothScanner(
    private val onDeviceFound: (BluetoothDevice) -> Unit
) {
    private val scanCallback = object : ScanCallback() {
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            super.onScanResult(callbackType, result)
            result.device?.let { device ->
                onDeviceFound(device)
            }
        }
        
        override fun onScanFailed(errorCode: Int) {
            Log.e("BluetoothScanner", "Scan failed with error: $errorCode")
        }
    }
    
    fun startScan(bluetoothAdapter: BluetoothAdapter) {
        val scanner = bluetoothAdapter.bluetoothLeScanner
        val filters = listOf<ScanFilter>()
        val settings = ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .build()
        
        scanner.startScan(filters, settings, scanCallback)
    }
    
    fun stopScan(bluetoothAdapter: BluetoothAdapter) {
        bluetoothAdapter.bluetoothLeScanner?.stopScan(scanCallback)
    }
}

3.3 GATT連接和回調

class BluetoothGattClient : BluetoothGattCallback() {
    
    // GATT連接狀態回調
    override fun onConnectionStateChange(
        gatt: BluetoothGatt, 
        status: Int, 
        newState: Int
    ) {
        when (newState) {
            BluetoothProfile.STATE_CONNECTED -> {
                Log.i("GATT", "Connected to GATT server")
                // 發現服務
                gatt.discoverServices()
            }
            BluetoothProfile.STATE_DISCONNECTED -> {
                Log.i("GATT", "Disconnected from GATT server")
                // 清理資源
                gatt.close()
            }
        }
    }
    
    // 發現服務回調
    override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
        when (status) {
            BluetoothGatt.GATT_SUCCESS -> {
                Log.i("GATT", "Services discovered")
                // 遍歷所有服務
                gatt.services?.forEach { service ->
                    Log.d("GATT", "Service: ${service.uuid}")
                    // 獲取特徵
                    service.characteristics?.forEach { characteristic ->
                        Log.d("GATT", "Characteristic: ${characteristic.uuid}")
                    }
                }
            }
            else -> Log.e("GATT", "Service discovery failed: $status")
        }
    }
    
    // 特徵值讀取回調
    override fun onCharacteristicRead(
        gatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic,
        status: Int
    ) {
        if (status == BluetoothGatt.GATT_SUCCESS) {
            val value = characteristic.value
            // 處理讀取的數據
            Log.d("GATT", "Read value: ${value.toHexString()}")
        }
    }
    
    // 特徵值寫入回調
    override fun onCharacteristicWrite(
        gatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic,
        status: Int
    ) {
        if (status == BluetoothGatt.GATT_SUCCESS) {
            Log.d("GATT", "Write successful")
        }
    }
    
    // 特徵值改變通知(設備主動發送數據)
    override fun onCharacteristicChanged(
        gatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic
    ) {
        val value = characteristic.value
        // 處理通知數據
        Log.d("GATT", "Notification received: ${value.toHexString()}")
    }
}

3.4 完整的連接管理器

class BluetoothConnectionManager(
    private val context: Context
) {
    private var bluetoothGatt: BluetoothGatt? = null
    private val gattCallback = BluetoothGattClient()
    
    // 連接設備
    fun connectDevice(device: BluetoothDevice) {
        // Android 6.0+ 需要指定傳輸模式
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            bluetoothGatt = device.connectGatt(
                context, 
                false,  // autoConnect
                gattCallback,
                BluetoothDevice.TRANSPORT_LE
            )
        } else {
            bluetoothGatt = device.connectGatt(
                context, 
                false, 
                gattCallback
            )
        }
    }
    
    // 斷開連接
    fun disconnect() {
        bluetoothGatt?.disconnect()
    }
    
    // 發現服務
    fun discoverServices() {
        bluetoothGatt?.discoverServices()
    }
    
    // 讀取特徵值
    fun readCharacteristic(serviceUuid: UUID, characteristicUuid: UUID) {
        val service = bluetoothGatt?.getService(serviceUuid)
        val characteristic = service?.getCharacteristic(characteristicUuid)
        characteristic?.let {
            bluetoothGatt?.readCharacteristic(it)
        }
    }
    
    // 寫入特徵值
    fun writeCharacteristic(
        serviceUuid: UUID, 
        characteristicUuid: UUID, 
        data: ByteArray,
        writeType: Int = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
    ) {
        val service = bluetoothGatt?.getService(serviceUuid)
        val characteristic = service?.getCharacteristic(characteristicUuid)
        characteristic?.let {
            it.value = data
            it.writeType = writeType
            bluetoothGatt?.writeCharacteristic(it)
        }
    }
    
    // 啓用通知
    fun enableNotification(serviceUuid: UUID, characteristicUuid: UUID) {
        val service = bluetoothGatt?.getService(serviceUuid)
        val characteristic = service?.getCharacteristic(characteristicUuid)
        
        characteristic?.let { char ->
            // 啓用本地通知
            bluetoothGatt?.setCharacteristicNotification(char, true)
            
            // 寫入客户端特徵配置描述符
            val descriptor = char.getDescriptor(
                UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
            )
            descriptor?.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
            bluetoothGatt?.writeDescriptor(descriptor)
        }
    }
    
    // 清理資源
    fun cleanup() {
        bluetoothGatt?.close()
        bluetoothGatt = null
    }
}

4. 使用示例

class MainActivity : AppCompatActivity() {
    private lateinit var connectionManager: BluetoothConnectionManager
    private lateinit var scanner: BluetoothScanner
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        connectionManager = BluetoothConnectionManager(this)
        
        scanner = BluetoothScanner { device ->
            // 掃描到設備後連接
            if (device.name == "Your-Device-Name") {
                scanner.stopScan()
                connectionManager.connectDevice(device)
            }
        }
        
        // 開始掃描
        startScan()
    }
    
    private fun startScan() {
        // 檢查權限
        if (checkPermissions()) {
            scanner.startScan()
        } else {
            requestPermissions()
        }
    }
    
    // 設備連接成功後操作
    private fun performOperations() {
        // 假設已知服務UUID和特徵UUID
        val serviceUuid = UUID.fromString("0000180f-0000-1000-8000-00805f9b34fb")
        val charUuid = UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb")
        
        // 啓用通知
        connectionManager.enableNotification(serviceUuid, charUuid)
        
        // 讀取特徵值
        connectionManager.readCharacteristic(serviceUuid, charUuid)
        
        // 寫入特徵值
        val data = byteArrayOf(0x01, 0x02, 0x03)
        connectionManager.writeCharacteristic(serviceUuid, charUuid, data)
    }
    
    override fun onDestroy() {
        super.onDestroy()
        connectionManager.cleanup()
    }
}

5. 最佳實踐和注意事項

5.1 連接參數優化

// Android 7.0+ 可以優化連接參數
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    bluetoothGatt?.requestConnectionPriority(
        BluetoothGatt.CONNECTION_PRIORITY_HIGH
    )
    
    // 設置PHY層參數(Android 8.0+)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        bluetoothGatt?.setPreferredPhy(
            BluetoothDevice.PHY_LE_2M_MASK,
            BluetoothDevice.PHY_LE_2M_MASK,
            BluetoothDevice.PHY_OPTION_NO_PREFERRED
        )
    }
}

5.2 自動重連機制

class AutoReconnectManager {
    private var reconnectAttempts = 0
    private val maxReconnectAttempts = 3
    
    fun onDisconnected() {
        if (reconnectAttempts < maxReconnectAttempts) {
            Handler(Looper.getMainLooper()).postDelayed({
                attemptReconnect()
                reconnectAttempts++
            }, 2000) // 2秒後重試
        }
    }
}

5.3 常見問題處理

  1. 連接失敗
  • 檢查設備是否在廣播
  • 檢查Android版本和權限
  • 確保沒有超過最大連接數(通常7個)
  1. 數據傳輸失敗
  • 檢查MTU大小:bluetoothGatt?.requestMtu(512)
  • 使用合適的Write Type(Write With Response/Without Response)
  1. 資源泄漏
  • 及時調用gatt.close()
  • 在Activity生命週期中正確管理連接

6. 擴展工具函數

// ByteArray轉十六進制字符串
fun ByteArray.toHexString(): String = 
    joinToString("") { "%02X".format(it) }

// 檢查服務是否存在
fun BluetoothGatt.hasService(uuid: UUID): Boolean {
    return getService(uuid) != null
}

// 批量寫入特徵值
fun writeLargeData(
    characteristic: BluetoothGattCharacteristic,
    data: ByteArray,
    chunkSize: Int = 20
) {
    var offset = 0
    while (offset < data.size) {
        val chunk = data.copyOfRange(
            offset, 
            minOf(offset + chunkSize, data.size)
        )
        characteristic.value = chunk
        // 寫入邏輯...
        offset += chunkSize
    }
}

實際使用時需要根據具體設備的服務UUID和特徵UUID進行調整即可。