BluetoothGatt — класс Android, предоставляющий API для работы GATT-клиента (Generic Attribute Profile) над BLE-соединением. BluetoothGatt инкапсулирует подключение к удалённому GATT-серверу (периферийному BLE-devicesу) и управляет всеми операциями профиля: discovery сервисов, чтение и запись характеристик, подписка на уведомления и индикации. Экземпляр BluetoothGatt получается через BluetoothDevice.connectGatt() с callbackом BluetoothGattCallback. По данным Android Developers, 2026, BluetoothGatt — центральный класс для двусторонней BLE-коммуникации, поддерживающий GATT-операции с BLE 4.0 до BLE 5.4.
Главное
BluetoothGatt — это прокси-объект, представляющий GATT-соединение между Android-devicesом (централь) и BLE-периферией (сервер). Каждый экземпляр BluetoothGatt соответствует одному активному BLE-соединению. Через него выполняются все GATT-профильные операции: discovery, чтение, запись, уведомления. BluetoothGatt не создаётся напрямую — он возвращается методом BluetoothDevice.connectGatt().
Создание BluetoothGatt требует четырёх параметров. Context — контекст приложения (Activity или Application). autoConnect — если false, Android немедленно инициирует прямое подключение; если true, Android подключается автоматически при обнаружении devicesа (полезно для фонового подключения). BluetoothGattCallback — обязательный callback для всех GATT-событий. transport — BluetoothDevice.TRANSPORT_LE (BLE) или TRANSPORT_BREDR (Classic). На BLE-devicesах всегда используйте TRANSPORT_LE.
Жизненный цикл BluetoothGatt состоит из пяти состояний. DISCONNECTED — начальное состояние. CONNECTING — после вызова connectGatt, до подтверждения. CONNECTED — после onConnectionStateChange с STATE_CONNECTED. После подключения вызывается discoverServices() для получения GATT-иерархии. После завершения работы — disconnect() и close() для освобождения системных ресурсов. Без вызова close() приложение может исчерпать лимит BLE-соединений Android (обычно 4–8).
// Creating BluetoothGatt connection
import android.bluetooth.*
class GattConnector(private val context: Context) {
private var bluetoothGatt: BluetoothGatt? = null
fun connect(device: BluetoothDevice): BluetoothGatt? {
// Close previous connection if exists
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. Connection established → Discovery
gatt.discoverServices()
}
BluetoothProfile.STATE_DISCONNECTED -> {
// 3. Connection lost
close()
}
}
}
override fun onServicesDiscovered(
gatt: BluetoothGatt, status: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// 4. GATT hierarchy received
onGattReady(gatt)
}
}
},
BluetoothDevice.TRANSPORT_LE
)
return bluetoothGatt
}
private fun onGattReady(gatt: BluetoothGatt) {
// GATT ready for read/write operations
}
fun close() {
bluetoothGatt?.disconnect()
bluetoothGatt?.close()
bluetoothGatt = null
}
}
Класс GattConnector демонстрирует правильное создание BluetoothGatt. Параметр autoConnect=false — прямое подключение (для сканированных devices). При 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. После успешного discovery BluetoothGatt заполняет список сервисов, доступных через getServices().
Каждый BluetoothGattService содержит список BluetoothGattCharacteristic. Характеристика имеет UUID, свойства (PROPERTY_READ, PROPERTY_WRITE, PROPERTY_NOTIFY) и опциональные дескрипторы. Свойства определяют, какие операции разрешены: если у характеристики нет PROPERTY_READ, вызов readCharacteristic вернёт ошибку. Для получения дескрипторов характеристики используется getDescriptors().
// Service discovery and characteristic search
class GattServiceExplorer {
// Find service by UUID
fun findService(gatt: BluetoothGatt, uuid: UUID): BluetoothGattService? {
return gatt.services?.firstOrNull { it.uuid == uuid }
}
// Find characteristic in service
fun findCharacteristic(
service: BluetoothGattService,
uuid: UUID
): BluetoothGattCharacteristic? {
return service.characteristics?.firstOrNull { it.uuid == uuid }
}
// Get all supported characteristic operations
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
}
// Log entire GATT hierarchy
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-devices. Все операции над BluetoothGatt должны выполняться после успешного onServicesDiscovered, иначе getServices() вернёт пустой список.
readCharacteristic() — асинхронный метод BluetoothGatt для чтения значения характеристики с удалённого BLE-devicesа. Результат приходит в onCharacteristicRead() BluetoothGattCallback. Если на devicesе 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 bytesа для BLE 4.0), Android автоматически фрагментирует и собирает данные последовательностью read-запросов через BLE-стек. Для BLE 5.0+ с extended MTU (до 251 bytesа) фрагментация не требуется — одно чтение возвращает полные данные. Перед чтением можно вызвать requestMtu() для согласования максимального MTU.
// Read characteristic and descriptor via BluetoothGatt
class GattReader {
fun readHeartRate(gatt: BluetoothGatt) {
// Heart Rate Service UUID = 0x180D
val service = gatt.getService(UUID.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
?: return
// Heart Rate Measurement 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) }
}
// Process data in onCharacteristicRead callback:
fun parseHeartRate(value: ByteArray): Int {
// BLE Heart Rate: 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 демонстрирует чтение Heart Rate характеристики. Сервис 0x180D содержит характеристику 0x2A37 (Heart Rate Measurement) — стандартный BLE-профиль Bluetooth SIG. Перед чтением проверяется свойство PROPERTY_READ через hasProperty. parseHeartRate парсит BLE-формат пульса: первый bytes — flags (формат данных), second — значение bpm. CCCD-дескриптор (0x2902) читается для проверки статуса уведомлений.
writeCharacteristic() — метод BluetoothGatt для записи данных на BLE-периферию. На Android API 33+ writeCharacteristic() заменён на writeCharacteristic(request), где BluetoothGattCharacteristicWriteRequest — объект запроса, содержащий характеристику, массив bytes и WriteType. Старый метод writeCharacteristic(characteristic) с setValue() depreceted. WriteType определяет поведение запроса: WRITE_TYPE_DEFAULT (зависит от свойств характеристики), WRITE_TYPE_NO_RESPONSE (withoutResponse) и WRITE_TYPE_SIGNED (авторизация).
Выбор WriteType влияет на скорость и надёжность. WRITE_TYPE_DEFAULT обычно соответствует withResponse (если у характеристики есть PROPERTY_WRITE) или безResponse (если есть PROPERTY_WRITE_NO_RESPONSE). Для потоковых данных (OTA-обновления, логи) используйте WRITE_TYPE_NO_RESPONSE — максимальная пропускная способность. Для команд с гарантией доставки (включение, настройка) — WRITE_TYPE_DEFAULT с подтверждением через onCharacteristicWrite.
// BLE characteristic write on Android API 33+
class GattWriter {
// Write with response (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)
}
}
// Write without response (max speed)
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 callback (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-devicesо подтверждает запись через onCharacteristicWrite. writeWithoutResponse использует WRITE_TYPE_NO_RESPONSE — данные отправляются без подтверждения, максимальная пропускная способность. На API 33+ используется новый writeCharacteristic(request) с BluetoothGattCharacteristicWriteRequest. На API < 33 — старый setValue() + writeCharacteristic().
setCharacteristicNotification() — метод BluetoothGatt для подписки на уведомления об изменении характеристики на периферии. После активации подписки BLE-devicesо отправляет новые значения через onCharacteristicChanged(). Однако setCharacteristicNotification() только активирует локальное уведомление Android — для включения уведомлений на самом BLE-devicesе необходимо также записать значение 0x0100 в CCCD-дескриптор (0x2902).
CCCD (Client Characteristic Configuration Descriptor) — дескриптор, управляющий отправкой уведомлений с BLE-периферии. Значение 0x0000 — уведомления выключены, 0x0100 — уведомления включены (notifications), 0x0200 — индикации включены (indications). Запись в CCCD выполняется через writeDescriptor() на BluetoothGatt после вызова setCharacteristicNotification(). Android не записывает CCCD автоматически — эта обязанность лежит на разработчике.
// Correct BLE notification subscription
class GattNotificationManager {
// 1. Enable notifications
fun enableNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
// Step 1: local Android subscription
val success = gatt.setCharacteristicNotification(characteristic, true)
if (!success) {
print("Failed to subscribe")
return
}
// Step 2: write CCCD (0x2902) on BLE device
val cccdDescriptor = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
) ?: return
// 0x0100 = notification, 0x0200 = indicate
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. Characteristic discovery
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. Notification callback
private val notificationCallback = object : BluetoothGattCallback() {
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
callbackType: Int
) {
// New value from BLE peripheral
print("Notification: ${value.size} bytes")
}
}
}
Класс GattNotificationManager реализует правильный двухшаговый протокол подписки на BLE-уведомления. enableNotification сначала вызывает setCharacteristicNotification(true) на Android, затем записывает 0x0100 в CCCD-дескриптор через writeDescriptor. disableNotification выполняет обратные операции. Без записи CCCD BLE-devicesо не отправляет уведомления — это самая частая ошибка BLE-разработчиков на Android.
Полный пример GATT-клиента на Kotlin, объединяющий создание BluetoothGatt, discovery, чтение и подписку на уведомления в едином менеджере с использованием корутин для асинхронной обработки.
// Full GATT client with coroutines in Kotlin
class GattClient(context: Context) {
private val context = context.applicationContext
private var gatt: BluetoothGatt? = null
// Connect with coroutine
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
)
}
// Read characteristic via coroutine
suspend fun readCharacteristicValue(char: BluetoothGattCharacteristic): ByteArray? =
suspendCoroutine { continuation ->
gatt?.let { gatt ->
// Save characteristic tag for callback identification
gatt.setCharacteristic(char, null) // for API < 33
gatt.readCharacteristic(char)
}
}
// Close connection
fun release() {
gatt?.disconnect()
gatt?.close()
gatt = null
}
}
GATT-клиент GattClient использует корутины (suspendCoroutine) для преобразования callback-based API BluetoothGatt в последовательные вызовы. connect() ожидает onServicesDiscovered, после чего GATT-hierarchy доступна. readCharacteristicValue() ожидает onCharacteristicRead. Такой подход избавляет от вложенности callbackов и делает BLE-код линейным. release() гарантирует освобождение ресурсов — обязательный вызов в onDestroy Activity или ViewModel.onCleared.
Часто задаваемые вопросы
BluetoothGatt — класс для GATT-клиента Android, управляющий BLE-соединением с периферийным devicesом. Создаётся через BluetoothDevice.connectGatt(), предоставляет методы discoverServices(), readCharacteristic(), writeCharacteristic(), setCharacteristicNotification(). Результаты всех операций асинхронно приходят через BluetoothGattCallback. Без BluetoothGatt невозможна двусторонняя BLE-коммуникация на Android.
Статус 133 (GATT_ERROR) означает внутреннюю ошибку BLE-стека Android. Причины: devicesо отключилось во время discovery, MTU меньше минимального (23 bytesа), или BLE-стек перегружен. Решение: повторите discoverServices() с задержкой 500 мс, проверьте RSSI devicesа и убедитесь, что периферия поддерживает GATT discovery в текущем состоянии.
Для записи с подтверждением вызовите writeCharacteristic() с WRITE_TYPE_DEFAULT (API 33+: BluetoothGattCharacteristicWriteRequest). При успехе BLE-devicesо отправляет подтверждение, и Android вызывает onCharacteristicWrite с GATT_SUCCESS. Если devicesо не отвечает в течение 30 секунд (таймаут стека), callback возвращает статус ошибки. Для watchdog используйте Handler с postDelayed.
На Android 13+ (API 33) изменены методы BluetoothGatt: writeCharacteristic() теперь принимает BluetoothGattCharacteristicWriteRequest, readCharacteristic() — BluetoothGattCharacteristicReadRequest. Старые setValue()/writeCharacteristic() deprecated. Также изменён 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 с ошибкой. Для работы с большим количеством devices используйте циклическое подключение или Bluetooth Mesh.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также