BluetoothGatt — клас Android, що надає API для роботи GATT-клієнта (Generic Attribute Profile) над BLE-з'єднанням. BluetoothGatt інкапсулює підключення до віддаленого GATT-сервера (периферійного BLE-пристрою) та керує всіма операціями профілю: виявлення сервісів, читання та запис характеристик, підписка на сповіщення та індикації. Екземпляр BluetoothGatt отримується через BluetoothDevice.connectGatt() з callbackом 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 — обов'язковий callback для всіх 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("ЗАПИС")
if (and(BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0) props.add("ЗАПИС_БЕЗ_ВІДПОВІДІ")
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 автоматично фрагментує та збирає дані послідовністю read-запитів через BLE-стек. Для BLE 5.0+ з extended MTU (до 251 байта) фрагментація не потрібна — одне читання повертає повні дані. Перед читанням можна викликати requestMtu() для узгодження максимального MTU.
// Читання характеристики та дескриптора через BluetoothGatt
class GattReader {
fun readHeartRate(gatt: BluetoothGatt) {
// UUID сервісу пульсу = 0x180D
val service = gatt.getService(UUID.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
?: return
// UUID вимірювання пульсу = 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) }
}
// Обробка даних в callback onCharacteristicRead:
fun parseHeartRate(value: ByteArray): Int {
// BLE пульс: bytes = flags, second = 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 демонструє читання характеристики пульсу. Сервіс 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)
}
}
// Callback 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 Level. 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. Callback сповіщення
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 ->
// Зберегти тег характеристики для ідентифікації callback
gatt.setCharacteristic(char, null) // для API < 33
gatt.readCharacteristic(char)
}
}
// Закрити з'єднання
fun release() {
gatt?.disconnect()
gatt?.close()
gatt = null
}
}
GATT-клієнт GattClient використовує корутини (suspendCoroutine) для перетворення callback-based API BluetoothGatt в послідовні виклики. 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 мс, перевірте RSSI пристрою та переконайтеся, що периферія підтримує GATT виявлення в поточному стані.
Для запису з підтвердженням викличте writeCharacteristic() з WRITE_TYPE_DEFAULT (API 33+: BluetoothGattCharacteristicWriteRequest). При успіху BLE-пристрій надсилає підтвердження, і Android викликає onCharacteristicWrite з GATT_SUCCESS. Якщо пристрій не відповідає протягом 30 секунд (таймаут стеку), callback повертає статус помилки. Для 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.