在日常使用中經常會用到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 常見問題處理
- 連接失敗:
- 檢查設備是否在廣播
- 檢查Android版本和權限
- 確保沒有超過最大連接數(通常7個)
- 數據傳輸失敗:
- 檢查MTU大小:
bluetoothGatt?.requestMtu(512) - 使用合適的Write Type(Write With Response/Without Response)
- 資源泄漏:
- 及時調用
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進行調整即可。