BluetoothGatt — что это, методы и GATT-протокол BLE в Android

Автор: IT Sectr Опубликовано: 2026-07-16 Время чтения: 10 мин

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 — класс Android для GATT-клиента, управляющий BLE-соединением с периферийным devicesом
  • connectGatt() — метод BluetoothDevice для создания BluetoothGatt; принимает контекст, autoConnect, callback и transport
  • discoverServices() — метод для получения GATT-иерархии: сервисы (BluetoothGattService), характеристики (BluetoothGattCharacteristic)
  • readCharacteristic/writeCharacteristic — методы чтения и записи с асинхронным результатом через BluetoothGattCallback
  • setCharacteristicNotification — метод подписки на уведомления BLE с обязательной записью CCCD-дескриптора

Что такое BluetoothGatt: суть и создание соединения

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).

kotlin
// 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-соединений.

Discovery сервисов и характеристик через BluetoothGatt

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().

kotlin
// 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() вернёт пустой список.

Чтение характеристик BLE: readCharacteristic и readDescriptor

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.

kotlin
// 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 с WriteType

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.

kotlin
// 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 и CCCD

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 автоматически — эта обязанность лежит на разработчике.

kotlin
// 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 с BluetoothGattCallback

Полный пример GATT-клиента на Kotlin, объединяющий создание BluetoothGatt, discovery, чтение и подписку на уведомления в едином менеджере с использованием корутин для асинхронной обработки.

kotlin
// 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 в Android?

BluetoothGatt — класс для GATT-клиента Android, управляющий BLE-соединением с периферийным devicesом. Создаётся через BluetoothDevice.connectGatt(), предоставляет методы discoverServices(), readCharacteristic(), writeCharacteristic(), setCharacteristicNotification(). Результаты всех операций асинхронно приходят через BluetoothGattCallback. Без BluetoothGatt невозможна двусторонняя BLE-коммуникация на Android.

Почему onServicesDiscovered возвращает статус 133?

Статус 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.

Как работать с BLE на Android 33+?

На Android 13+ (API 33) изменены методы BluetoothGatt: writeCharacteristic() теперь принимает BluetoothGattCharacteristicWriteRequest, readCharacteristic() — BluetoothGattCharacteristicReadRequest. Старые setValue()/writeCharacteristic() deprecated. Также изменён BluetoothGattCallback: onCharacteristicRead(), onCharacteristicWrite(), onCharacteristicChanged() получают ByteArray value и callbackType. Используйте Build.VERSION.SDK_INT для ветвления.

Сколько BLE-соединений поддерживает Android?

Android поддерживает 4–8 одновременных BLE-GATT соединений (зависит от производителя и версии Android). Pixel/Google: до 7, Samsung: до 5, Xiaomi: до 4. При превышении лимита connectGatt возвращает null или onConnectionStateChange с ошибкой. Для работы с большим количеством devices используйте циклическое подключение или Bluetooth Mesh.

Итоги

  • BluetoothGatt — GATT-клиент Android для BLE-соединения, создаётся через BluetoothDevice.connectGatt() с BluetoothGattCallback
  • discoverServices() — обязательный шаг после подключения для получения сервисов, характеристик и дескрипторов BLE-devicesа
  • Чтение — readCharacteristic() с асинхронным результатом в onCharacteristicRead(); для больших данных требуется согласование MTU
  • Запись — writeCharacteristic() с WriteType: DEFAULT (withResponse) или NO_RESPONSE (без подтверждения)
  • Уведомления — двухшаговая активация: setCharacteristicNotification() + запись CCCD (0x2902) значением 0x0100
  • API 33+ — новые методы writeCharacteristic(request) и readCharacteristic(request) с объектами запросов
  • Release resources — обязательный вызов disconnect() и close() для предотвращения утечки BLE-соединений

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также