BluetoothGatt — шта је то, методе и GATT протокол BLE-а у Android-у

Аутор: IT Sectr Објављено: 2026-07-16 Време читања: 10 мин

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

Шта је BluetoothGatt: суштина и креирање везе

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

kotlin
// Креирање 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 веза.

Откривање сервиса и карактеристика кроз BluetoothGatt

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

kotlin
// Откривање сервиса и претрага карактеристика
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() враћа празну листу.

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

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

kotlin
// Читање карактеристике и дескриптора кроз 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 callback-у:
    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 са WriteType

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.

kotlin
// 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 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 нивое. writeWithResponse користи WRITE_TYPE_DEFAULT — BLE уређај потврђује упис кроз onCharacteristicWrite. writeWithoutResponse користи WRITE_TYPE_NO_RESPONSE — подаци се шаљу без потврде, максимални проток. На API 33+ користи се нови writeCharacteristic(request) са BluetoothGattCharacteristicWriteRequest. На API < 33 — стари setValue() + writeCharacteristic().

Претплата на обавештења: setCharacteristicNotification и CCCD

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 аутоматски — ова обавеза лежи на програмеру.

kotlin
// Исправна претплата на 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-у са BluetoothGattCallback

Комплетан пример GATT клијента на Kotlin-у, који обједињује креирање BluetoothGatt, откривање, читање и претплату на обавештења у јединственом менаџеру користећи корутине за асинхрону обраду.

kotlin
// Потпуни 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-базираног API-ја BluetoothGatt у секвенцијалне позиве. connect() чека onServicesDiscovered, након чега је GATT хијерархија доступна. readCharacteristicValue() чека onCharacteristicRead. Овај приступ елиминише угњежђавање callback-ова и чини BLE код линеарним. release() гарантује ослобађање ресурса — обавезни позив у onDestroy Activity или ViewModel.onCleared.

Често постављана питања

Шта је BluetoothGatt у Android-у?

BluetoothGatt — класа за GATT клијента Android-а која управља BLE везом са периферним уређајем. Креира се кроз BluetoothDevice.connectGatt(), пружа методе discoverServices(), readCharacteristic(), writeCharacteristic(), setCharacteristicNotification(). Резултати свих операција стижу асинхроно кроз BluetoothGattCallback. Без BluetoothGatt-а двосмерна BLE комуникација на Android-у је немогућа.

Зашто onServicesDiscovered враћа статус 133?

Статус 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 секунди (тајмаут стека), callback враћа статус грешке. За watchdog користите Handler са postDelayed.

Како радити са BLE на Android 33+?

На Android 13+ (API 33) измењени су методи BluetoothGatt: writeCharacteristic() сада прима BluetoothGattCharacteristicWriteRequest, readCharacteristic() — BluetoothGattCharacteristicReadRequest. Стари setValue()/writeCharacteristic() су застарели. Такође је измењен 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 са грешком. За рад са великим бројем уређаја користите циклично повезивање или Bluetooth Mesh.

Резиме

  • BluetoothGatt — GATT клијент Android-а за BLE везу, креиран кроз BluetoothDevice.connectGatt() са BluetoothGattCallback
  • discoverServices() — обавезни корак након повезивања за добијање сервиса, карактеристика и дескриптора BLE уређаја
  • Читање — readCharacteristic() са асинхроним резултатом у onCharacteristicRead(); за велике податке потребно је усаглашавање MTU-а
  • Писање — writeCharacteristic() са WriteType: DEFAULT (withResponse) или NO_RESPONSE (без потврде)
  • Обавештења — двостепена активација: setCharacteristicNotification() + упис CCCD (0x2902) вредношћу 0x0100
  • API 33+ — нови методи writeCharacteristic(request) и readCharacteristic(request) са објектима захтева
  • Ослобађање ресурса — обавезни позив disconnect() и close() за спречавање цурења BLE веза

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође