BluetoothGatt — клас Android, предоставящ API за работа на GATT клиент (Generic Attribute Profile) през BLE връзка. BluetoothGatt капсулира свързването към отдалечен GATT сървър (периферно BLE устройство) и управлява всички операции на профила: откриване на услуги, четене и запис на характеристики, абониране за известия и индикации. Инстанция на BluetoothGatt се получава чрез BluetoothDevice.connectGatt() с обратно извикване BluetoothGattCallback. Според Android Developers, 2026, BluetoothGatt е централният клас за двупосочна BLE комуникация, поддържащ GATT операции от BLE 4.0 до BLE 5.4.
Основни точки
BluetoothGatt — е прокси обект, представляващ GATT връзката между Android устройство (централа) и BLE периферия (сървър). Всяка инстанция на BluetoothGatt съответства на една активна BLE връзка. Чрез нея се изпълняват всички GATT профилни операции: откриване, четене, запис, известия. BluetoothGatt не се създава директно — връща се от метода BluetoothDevice.connectGatt().
Създаването на BluetoothGatt изисква четири параметъра. Context — контекст на приложението (Activity или Application). autoConnect — ако е false, Android незабавно започва директно свързване; ако е true, Android се свързва автоматично при откриване на устройството (полезно за фоново свързване). BluetoothGattCallback — задължително обратно извикване за всички GATT събития. transport — BluetoothDevice.TRANSPORT_LE (BLE) или TRANSPORT_BREDR (Classic). На BLE устройства винаги използвайте TRANSPORT_LE.
Жизнен цикъл на BluetoothGatt се състои от пет състояния. DISCONNECTED — начално състояние. CONNECTING — след извикване на connectGatt, до потвърждение. CONNECTED — след onConnectionStateChange със STATE_CONNECTED. След свързване се извиква discoverServices() за получаване на GATT йерархията. След приключване на работата — disconnect() и close() за освобождаване на системни ресурси. Без close() приложението може да изчерпи лимита за BLE връзки на Android (обикновено 4–8).
// Създаване на BluetoothGatt връзка
import android.bluetooth.*
class GattConnector(private val context: Context) {
private var bluetoothGatt: BluetoothGatt? = null
fun connect(device: BluetoothDevice): BluetoothGatt? {
// Затвори предишната връзка ако съществува
close()
bluetoothGatt = device.connectGatt(
context,
false, // autoConnect = false ( )
object : BluetoothGattCallback() {
override fun onConnectionStateChange(
gatt: BluetoothGatt, status: Int, newState: Int
) {
when (newState) {
BluetoothProfile.STATE_CONNECTED -> {
// 2. Връзката е установена → Откриване
gatt.discoverServices()
}
BluetoothProfile.STATE_DISCONNECTED -> {
// 3. Връзката е загубена
close()
}
}
}
override fun onServicesDiscovered(
gatt: BluetoothGatt, status: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// 4. GATT йерархията е получена
onGattReady(gatt)
}
}
},
BluetoothDevice.TRANSPORT_LE
)
return bluetoothGatt
}
private fun onGattReady(gatt: BluetoothGatt) {
// GATT готов за операции четене/запис
}
fun close() {
bluetoothGatt?.disconnect()
bluetoothGatt?.close()
bluetoothGatt = null
}
}
Класът GattConnector демонстрира правилно създаване на BluetoothGatt. Параметър autoConnect=false — директно свързване (за сканирани устройства). При onConnectionStateChange със STATE_CONNECTED незабавно се извиква discoverServices(). onServicesDiscovered сигнализира готовност на GATT. close() последователно извиква disconnect() и close() — без close() системните ресурси не се освобождават, което води до изтичане на BLE връзки.
discoverServices() — първият GATT метод, извикан след свързване. Инициира асинхронно търсене на всички услуги на BLE периферията. Резултатът идва в onServicesDiscovered() с код на състоянието: GATT_SUCCESS (0) — успех, 133 — GATT_ERROR, 8 — GATT_CONNECTION_TIMEOUT. След успешно откриване BluetoothGatt попълва списъка с услуги, достъпни чрез getServices().
Всеки BluetoothGattService съдържа списък от BluetoothGattCharacteristic. Характеристиката има UUID, свойства (PROPERTY_READ, PROPERTY_WRITE, PROPERTY_NOTIFY) и опционални дескриптори. Свойствата определят кои операции са разрешени: ако характеристиката няма PROPERTY_READ, извикването на readCharacteristic ще върне грешка. За получаване на дескрипторите на характеристиката се използва getDescriptors().
// Откриване на услуги и търсене на характеристики
class GattServiceExplorer {
// Намери услуга по UUID
fun findService(gatt: BluetoothGatt, uuid: UUID): BluetoothGattService? {
return gatt.services?.firstOrNull { it.uuid == uuid }
}
// Намери характеристика в услугата
fun findCharacteristic(
service: BluetoothGattService,
uuid: UUID
): BluetoothGattCharacteristic? {
return service.characteristics?.firstOrNull { it.uuid == uuid }
}
// Вземи всички поддържани операции на характеристиката
fun getCharacteristicProperties(chars: BluetoothGattCharacteristic): List<String> {
val props = mutableListOf<String>()
with(chars.properties) {
if (and(BluetoothGattCharacteristic.PROPERTY_READ) != 0) props.add("READ")
if (and(BluetoothGattCharacteristic.PROPERTY_WRITE) != 0) props.add("WRITE")
if (and(BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0) props.add("WRITE_NO_RESP")
if (and(BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) props.add("NOTIFY")
if (and(BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) props.add("INDICATE")
}
return props
}
// Запиши цялата GATT йерархия
fun dumpGattTree(gatt: BluetoothGatt) {
gatt.services?.forEach { service ->
print("Service: ${service.uuid}")
service.characteristics?.forEach { char ->
print(" Characteristic: ${char.uuid}, properties: ${char.properties}")
char.descriptors?.forEach { desc ->
print(" Descriptor: ${desc.uuid}")
}
}
}
}
}
Класът GattServiceExplorer предоставя помощни средства за навигация в GATT йерархията. findService и findCharacteristic търсят услуги и характеристики по UUID. getCharacteristicProperties проверява битови маски на свойствата чрез and(). dumpGattTree извежда цялата йерархия в лога — полезна при отстраняване на грешки на BLE устройства. Всички операции върху BluetoothGatt трябва да се изпълняват след успешно onServicesDiscovered, в противен случай getServices() ще върне празен списък.
readCharacteristic() — асинхронен метод на BluetoothGatt за четене на стойността на характеристика от отдалечено BLE устройство. Резултатът идва в onCharacteristicRead() на BluetoothGattCallback. Ако на устройството има 2+ съвпадащи характеристики (малко вероятно, но възможно), readCharacteristic() може да прочете нецелевата — по-безопасно е да извикате readCharacteristic() на инстанцията BluetoothGattCharacteristic, а не по UUID.
readDescriptor() — метод за четене на стойността на дескриптор на характеристика. Типичен дескриптор — CCCD (Client Characteristic Configuration Descriptor, UUID 0x2902), който определя дали известията са включени. Резултат в onDescriptorRead(). Четенето на дескриптори рядко се изисква на практика — CCCD се управлява от setCharacteristicNotification(), но за персонализирани дескриптори (User Description 0x2901, Presentation Format 0x2904) readDescriptor() е единственият начин за получаване на метаданни.
MTU и четене на големи данни — ако стойността на характеристиката надвишава MTU (23 байта за BLE 4.0), Android автоматично фрагментира и сглобява данните чрез последователност от заявки за четене през BLE стека. За BLE 5.0+ с разширен MTU (до 251 байта) не се изисква фрагментация — едно четене връща пълни данни. Преди четене може да се извика requestMtu() за договаряне на максимален MTU.
// Четене на характеристика и дескриптор чрез BluetoothGatt
class GattReader {
fun readHeartRate(gatt: BluetoothGatt) {
// UUID на услугата Heart Rate = 0x180D
val service = gatt.getService(UUID.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
?: return
// UUID на измерване на Heart Rate = 0x2A37
val characteristic = service.getCharacteristic(
UUID.fromString("00002A37-0000-1000-8000-00805F9B34FB")
) ?: return
if (hasProperty(characteristic, BluetoothGattCharacteristic.PROPERTY_READ)) {
gatt.readCharacteristic(characteristic)
}
}
fun readDescriptor(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
// CCCD (0x2902)
val cccd = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
)
cccd?.let { gatt.readDescriptor(it) }
}
// Обработка на данни в обратното извикване onCharacteristicRead:
fun parseHeartRate(value: ByteArray): Int {
// BLE Heart Rate: байтове = flags, втори = bpm
return if (value.isNotEmpty()) value[1].toInt() and 0xFF else 0
}
private fun hasProperty(char: BluetoothGattCharacteristic, prop: Int): Boolean {
return char.properties and prop != 0
}
}
Класът GattReader демонстрира четене на характеристиката Heart Rate. Услугата 0x180D съдържа характеристика 0x2A37 (Heart Rate Measurement) — стандартен BLE профил на Bluetooth SIG. Преди четене се проверява свойството PROPERTY_READ чрез hasProperty. parseHeartRate анализира BLE формата на пулса: първи байт — flags (формат на данните), втори — стойност bpm. CCCD дескрипторът (0x2902) се чете за проверка на състоянието на известията.
writeCharacteristic() — метод на BluetoothGatt за запис на данни към BLE периферия. На Android API 33+ writeCharacteristic() е заменен от writeCharacteristic(request), където BluetoothGattCharacteristicWriteRequest е обект на заявка, съдържащ характеристиката, масив от байтове и WriteType. Старият метод writeCharacteristic(characteristic) със setValue() е остарял. WriteType определя поведението на заявката: WRITE_TYPE_DEFAULT (зависи от свойствата на характеристиката), WRITE_TYPE_NO_RESPONSE (withoutResponse) и WRITE_TYPE_SIGNED (оторизация).
Изборът на WriteType влияе върху скоростта и надеждността. WRITE_TYPE_DEFAULT обикновено съответства на withResponse (ако характеристиката има PROPERTY_WRITE) или withoutResponse (ако има PROPERTY_WRITE_NO_RESPONSE). За поточни данни (OTA актуализации, логове) използвайте WRITE_TYPE_NO_RESPONSE — максимална честотна лента. За команди с гаранция за доставка (активиране, конфигурация) — WRITE_TYPE_DEFAULT с потвърждение чрез onCharacteristicWrite.
// Запис на BLE характеристика на Android API 33+
class GattWriter {
// Запис с отговор (withResponse)
fun writeWithResponse(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, data: ByteArray) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// API 33+: writeCharacteristic
val request = BluetoothGattCharacteristicWriteRequest(
characteristic,
data,
BluetoothGattCharacteristicWriteRequest.WRITE_TYPE_DEFAULT,
@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
)
gatt.writeCharacteristic(request)
} else {
// API < 33: (deprecated)
characteristic.setValue(data)
gatt.writeCharacteristic(characteristic)
}
}
// Запис без отговор (максимална скорост)
fun writeWithoutResponse(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, data: ByteArray) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val request = BluetoothGattCharacteristicWriteRequest(
characteristic,
data,
BluetoothGattCharacteristicWriteRequest.WRITE_TYPE_NO_RESPONSE,
@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
)
gatt.writeCharacteristic(request)
} else {
characteristic.setValue(data)
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
gatt.writeCharacteristic(characteristic)
}
}
// Обратно извикване onCharacteristicWrite (API 33+)
private val writeCallback = object : BluetoothGattCallback() {
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
status: Int,
callbackType: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
print("Write success: ${value.size} bytes")
}
}
}
}
Класът GattWriter поддържа двата WriteType за различни API нива. writeWithResponse използва WRITE_TYPE_DEFAULT — BLE устройството потвърждава записа чрез onCharacteristicWrite. writeWithoutResponse използва WRITE_TYPE_NO_RESPONSE — данните се изпращат без потвърждение, максимална честотна лента. На API 33+ се използва новият writeCharacteristic(request) с BluetoothGattCharacteristicWriteRequest. На API < 33 — старият setValue() + writeCharacteristic().
setCharacteristicNotification() — метод на BluetoothGatt за абониране за известия за промяна на характеристика на периферията. След активиране на абонамента BLE устройството изпраща нови стойности чрез onCharacteristicChanged(). Въпреки това, setCharacteristicNotification() само активира локалното известие на Android — за включване на известия на самото BLE устройство е необходимо също да се запише стойност 0x0100 в CCCD дескриптора (0x2902).
CCCD (Client Characteristic Configuration Descriptor) — дескриптор, управляващ изпращането на известия от BLE периферията. Стойност 0x0000 — известията са изключени, 0x0100 — известията са включени (notifications), 0x0200 — индикациите са включени (indications). Записът в CCCD се извършва чрез writeDescriptor() на BluetoothGatt след извикване на setCharacteristicNotification(). Android не записва CCCD автоматично — това задължение е на разработчика.
// Правилно абониране за BLE известия
class GattNotificationManager {
// 1. Включване на известия
fun enableNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
// Стъпка 1: локален Android абонамент
val success = gatt.setCharacteristicNotification(characteristic, true)
if (!success) {
print("Неуспешно абониране")
return
}
// Стъпка 2: запиши CCCD (0x2902) на BLE устройството
val cccdDescriptor = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
) ?: return
// 0x0100 = известие, 0x0200 = индикация
val cccdValue = if (characteristic.properties
and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0) {
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE // [0x01, 0x00]
} else {
BluetoothGattDescriptor.ENABLE_INDICATION_VALUE // [0x02, 0x00]
}
cccdDescriptor.setValue(cccdValue)
gatt.writeDescriptor(cccdDescriptor)
}
// 2. Откриване на характеристика
fun disableNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
gatt.setCharacteristicNotification(characteristic, false)
val cccdDescriptor = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
) ?: return
cccdDescriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)
gatt.writeDescriptor(cccdDescriptor)
}
// 3. Обратно извикване за известие
private val notificationCallback = object : BluetoothGattCallback() {
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
callbackType: Int
) {
// Нова стойност от BLE периферия
print("Notification: ${value.size} bytes")
}
}
}
Класът GattNotificationManager имплементира правилния двустъпков протокол за абониране за BLE известия. enableNotification първо извиква setCharacteristicNotification(true) на Android, след което записва 0x0100 в CCCD дескриптора чрез writeDescriptor. disableNotification извършва обратните операции. Без запис на CCCD BLE устройството не изпраща известия — това е най-честата грешка на BLE разработчиците на Android.
Пълен пример за GATT клиент на Kotlin, който обединява създаване на BluetoothGatt, откриване, четене и абониране за известия в един мениджър, използвайки корутини за асинхронна обработка.
// Пълен GATT клиент с корутини в Kotlin
class GattClient(context: Context) {
private val context = context.applicationContext
private var gatt: BluetoothGatt? = null
// Свържи се с корутина
suspend fun connect(device: BluetoothDevice): Boolean =
suspendCoroutine { continuation ->
gatt = device.connectGatt(
context, false,
object : BluetoothGattCallback() {
override fun onConnectionStateChange(
gatt: BluetoothGatt, status: Int, newState: Int
) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices()
} else {
continuation.resume(false)
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
continuation.resume(status == BluetoothGatt.GATT_SUCCESS)
}
},
BluetoothDevice.TRANSPORT_LE
)
}
// Прочети характеристика чрез корутина
suspend fun readCharacteristicValue(char: BluetoothGattCharacteristic): ByteArray? =
suspendCoroutine { continuation ->
gatt?.let { gatt ->
// Запази етикет на характеристика за идентификация на обратното извикване
gatt.setCharacteristic(char, null) // за API < 33
gatt.readCharacteristic(char)
}
}
// Затвори връзката
fun release() {
gatt?.disconnect()
gatt?.close()
gatt = null
}
}
GATT клиентът GattClient използва корутини (suspendCoroutine) за преобразуване на базираното на callback BluetoothGatt API в последователни извиквания. connect() изчаква onServicesDiscovered, след което GATT йерархията е достъпна. readCharacteristicValue() изчаква onCharacteristicRead. Този подход елиминира вложените callback-и и прави BLE кода линеен. release() гарантира освобождаване на ресурси — задължително извикване в onDestroy на Activity или ViewModel.onCleared.
Често задавани въпроси
BluetoothGatt — клас за GATT клиент на Android, управляващ BLE връзка с периферно устройство. Създава се чрез BluetoothDevice.connectGatt(), предоставя методи discoverServices(), readCharacteristic(), writeCharacteristic(), setCharacteristicNotification(). Резултатите от всички операции идват асинхронно чрез BluetoothGattCallback. Без BluetoothGatt двупосочната BLE комуникация на Android е невъзможна.
Статус 133 (GATT_ERROR) означава вътрешна грешка на BLE стека на Android. Причини: устройството се е изключило по време на откриване, MTU е по-малък от минималния (23 байта) или BLE стекът е претоварен. Решение: повторете discoverServices() със закъснение от 500 ms, проверете RSSI на устройството и се уверете, че периферията поддържа GATT откриване в текущото състояние.
За запис с потвърждение извикайте writeCharacteristic() с WRITE_TYPE_DEFAULT (API 33+: BluetoothGattCharacteristicWriteRequest). При успех BLE устройството изпраща потвърждение и Android извиква onCharacteristicWrite с GATT_SUCCESS. Ако устройството не отговори в рамките на 30 секунди (таймаут на стека), обратното извикване връща статус на грешка. За watchdog използвайте Handler с postDelayed.
На Android 13+ (API 33) методите на BluetoothGatt са променени: writeCharacteristic() вече приема BluetoothGattCharacteristicWriteRequest, readCharacteristic() — BluetoothGattCharacteristicReadRequest. Старите setValue()/writeCharacteristic() са остарели. BluetoothGattCallback също е променен: onCharacteristicRead(), onCharacteristicWrite(), onCharacteristicChanged() получават ByteArray value и callbackType. Използвайте Build.VERSION.SDK_INT за разклоняване.
Android поддържа 4–8 едновременни BLE-GATT връзки (зависи от производителя и версията на Android). Pixel/Google: до 7, Samsung: до 5, Xiaomi: до 4. При превишаване на лимита connectGatt връща null или onConnectionStateChange се извиква с грешка. За работа с голям брой устройства използвайте циклично свързване или Bluetooth Mesh.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също