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("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 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() — метод 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 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() — метод 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-базираног 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 ms, проверите 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. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође