BluetoothGatt — clasa Android care oferă API pentru funcționarea clientului GATT (Generic Attribute Profile) peste conexiunea BLE. BluetoothGatt încapsulează conexiunea la serverul GATT la distanță (dispozitiv periferic BLE) și gestionează toate operațiunile profilului: descoperirea serviciilor, citirea și scrierea caracteristicilor, abonarea la notificări și indicații. Instanța BluetoothGatt se obține prin BluetoothDevice.connectGatt() cu callback BluetoothGattCallback. Conform Android Developers, 2026, BluetoothGatt este clasa centrală pentru comunicarea BLE bidirecțională, suportând operațiuni GATT de la BLE 4.0 până la BLE 5.4.
Puncte principale
BluetoothGatt — este un obiect proxy care reprezintă conexiunea GATT între dispozitivul Android (central) și periferia BLE (server). Fiecare instanță BluetoothGatt corespunde unei conexiuni BLE active. Prin intermediul său se execută toate operațiunile profilului GATT: descoperire, citire, scriere, notificări. BluetoothGatt nu se creează direct — este returnat de metoda BluetoothDevice.connectGatt().
Crearea BluetoothGatt necesită patru parametri. Context — contextul aplicației (Activity sau Application). autoConnect — dacă este false, Android inițiază imediat conexiunea directă; dacă este true, Android se conectează automat la detectarea dispozitivului (util pentru conexiunea în fundal). BluetoothGattCallback — callback obligatoriu pentru toate evenimentele GATT. transport — BluetoothDevice.TRANSPORT_LE (BLE) sau TRANSPORT_BREDR (Classic). Pe dispozitivele BLE utilizați întotdeauna TRANSPORT_LE.
Ciclul de viață BluetoothGatt constă din cinci stări. DISCONNECTED — starea inițială. CONNECTING — după apelarea connectGatt, până la confirmare. CONNECTED — după onConnectionStateChange cu STATE_CONNECTED. După conectare se apelează discoverServices() pentru obținerea ierarhiei GATT. După finalizarea lucrului — disconnect() și close() pentru eliberarea resurselor sistemului. Fără apelarea close(), aplicația poate epuiza limita de conexiuni BLE Android (de obicei 4–8).
// Crearea conexiunii BluetoothGatt
import android.bluetooth.*
class GattConnector(private val context: Context) {
private var bluetoothGatt: BluetoothGatt? = null
fun connect(device: BluetoothDevice): BluetoothGatt? {
// Închide conexiunea anterioară dacă există
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. Conexiune stabilită → Descoperire
gatt.discoverServices()
}
BluetoothProfile.STATE_DISCONNECTED -> {
// 3. Conexiune pierdută
close()
}
}
}
override fun onServicesDiscovered(
gatt: BluetoothGatt, status: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// 4. Ierarhie GATT primită
onGattReady(gatt)
}
}
},
BluetoothDevice.TRANSPORT_LE
)
return bluetoothGatt
}
private fun onGattReady(gatt: BluetoothGatt) {
// GATT pregătit pentru operații de citire/scriere
}
fun close() {
bluetoothGatt?.disconnect()
bluetoothGatt?.close()
bluetoothGatt = null
}
}
Clasa GattConnector demonstrează crearea corectă a BluetoothGatt. Parametrul autoConnect=false — conexiune directă (pentru dispozitive scanate). La onConnectionStateChange cu STATE_CONNECTED se apelează imediat discoverServices(). onServicesDiscovered semnalează pregătirea GATT. close() apelează secvențial disconnect() și close() — fără close() resursele sistemului nu sunt eliberate, ceea ce duce la scurgeri de conexiuni BLE.
discoverServices() — prima metodă GATT apelată după conectare. Inițiază căutarea asincronă a tuturor serviciilor pe periferia BLE. Rezultatul vine în onServicesDiscovered() cu codul de stare: GATT_SUCCESS (0) — reușit, 133 — GATT_ERROR, 8 — GATT_CONNECTION_TIMEOUT. După descoperirea reușită, BluetoothGatt completează lista serviciilor disponibile prin getServices().
Fiecare BluetoothGattService conține o listă de BluetoothGattCharacteristic. Caracteristica are UUID, proprietăți (PROPERTY_READ, PROPERTY_WRITE, PROPERTY_NOTIFY) și descriptori opționali. Proprietățile determină ce operațiuni sunt permise: dacă caracteristica nu are PROPERTY_READ, apelarea readCharacteristic va returna o eroare. Pentru obținerea descriptorilor caracteristicii se utilizează getDescriptors().
// Descoperirea serviciilor și căutarea caracteristicilor
class GattServiceExplorer {
// Găsește serviciul după UUID
fun findService(gatt: BluetoothGatt, uuid: UUID): BluetoothGattService? {
return gatt.services?.firstOrNull { it.uuid == uuid }
}
// Găsește caracteristica în serviciu
fun findCharacteristic(
service: BluetoothGattService,
uuid: UUID
): BluetoothGattCharacteristic? {
return service.characteristics?.firstOrNull { it.uuid == uuid }
}
// Obține toate operațiunile suportate ale caracteristicii
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
}
// Înregistrează întreaga ierarhie 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}")
}
}
}
}
}
Clasa GattServiceExplorer oferă utilitare pentru navigarea în ierarhia GATT. findService și findCharacteristic caută servicii și caracteristici după UUID. getCharacteristicProperties verifică măștile de biți ale proprietăților prin and(). dumpGattTree afișează ierarhia completă în log — utilă la depanarea dispozitivelor BLE. Toate operațiunile pe BluetoothGatt trebuie executate după onServicesDiscovered reușit, altfel getServices() va returna o listă goală.
readCharacteristic() — metoda asincronă BluetoothGatt pentru citirea valorii caracteristicii de pe dispozitivul BLE la distanță. Rezultatul vine în onCharacteristicRead() al BluetoothGattCallback. Dacă pe dispozitiv există 2+ caracteristici potrivite (puțin probabil, dar posibil), readCharacteristic() poate citi una nețintă — este mai sigur să apelați readCharacteristic() pe instanța BluetoothGattCharacteristic, nu după UUID.
readDescriptor() — metoda pentru citirea valorii descriptorului caracteristicii. Un descriptor tipic — CCCD (Client Characteristic Configuration Descriptor, UUID 0x2902) care determină dacă notificările sunt activate. Rezultatul în onDescriptorRead(). Citirea descriptorilor este rar necesară în practică — CCCD este gestionat de setCharacteristicNotification(), dar pentru descriptori personalizați (User Description 0x2901, Presentation Format 0x2904) readDescriptor() este singura modalitate de a obține metadate.
MTU și citirea datelor mari — dacă valoarea caracteristicii depășește MTU (23 de octeți pentru BLE 4.0), Android fragmentează și reasamblează automat datele printr-o secvență de cereri de citire prin stiva BLE. Pentru BLE 5.0+ cu MTU extins (până la 251 de octeți) fragmentarea nu este necesară — o singură citire returnează datele complete. Înainte de citire se poate apela requestMtu() pentru negocierea MTU maxim.
// Citirea caracteristicii și descriptorului prin BluetoothGatt
class GattReader {
fun readHeartRate(gatt: BluetoothGatt) {
// UUID serviciu Heart Rate = 0x180D
val service = gatt.getService(UUID.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
?: return
// UUID măsurare 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) }
}
// Procesează datele în callback-ul onCharacteristicRead:
fun parseHeartRate(value: ByteArray): Int {
// BLE Heart Rate: octeți = flags, al doilea = 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
}
}
Clasa GattReader demonstrează citirea caracteristicii Heart Rate. Serviciul 0x180D conține caracteristica 0x2A37 (Heart Rate Measurement) — profilul standard BLE Bluetooth SIG. Înainte de citire se verifică proprietatea PROPERTY_READ prin hasProperty. parseHeartRate parsează formatul BLE al pulsului: primul octet — flags (formatul datelor), al doilea — valoarea bpm. Descriptorul CCCD (0x2902) este citit pentru verificarea stării notificărilor.
writeCharacteristic() — metoda BluetoothGatt pentru scrierea datelor pe periferia BLE. Pe Android API 33+, writeCharacteristic() a fost înlocuit cu writeCharacteristic(request), unde BluetoothGattCharacteristicWriteRequest este un obiect de cerere care conține caracteristica, un array de octeți și WriteType. Metoda veche writeCharacteristic(characteristic) cu setValue() este deprecated. WriteType determină comportamentul cererii: WRITE_TYPE_DEFAULT (depinde de proprietățile caracteristicii), WRITE_TYPE_NO_RESPONSE (withoutResponse) și WRITE_TYPE_SIGNED (autorizare).
Alegerea WriteType influențează viteza și fiabilitatea. WRITE_TYPE_DEFAULT corespunde de obicei withResponse (dacă caracteristica are PROPERTY_WRITE) sau withoutResponse (dacă are PROPERTY_WRITE_NO_RESPONSE). Pentru date în flux (actualizări OTA, loguri) utilizați WRITE_TYPE_NO_RESPONSE — lățime de bandă maximă. Pentru comenzi cu garanție de livrare (activare, configurare) — WRITE_TYPE_DEFAULT cu confirmare prin onCharacteristicWrite.
// Scrierea caracteristicii BLE pe Android API 33+
class GattWriter {
// Scriere cu răspuns (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)
}
}
// Scriere fără răspuns (viteză maximă)
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")
}
}
}
}
Clasa GattWriter suportă ambele WriteType pentru diferite niveluri API. writeWithResponse utilizează WRITE_TYPE_DEFAULT — dispozitivul BLE confirmă scrierea prin onCharacteristicWrite. writeWithoutResponse utilizează WRITE_TYPE_NO_RESPONSE — datele sunt trimise fără confirmare, lățime de bandă maximă. Pe API 33+ se utilizează noul writeCharacteristic(request) cu BluetoothGattCharacteristicWriteRequest. Pe API < 33 — vechiul setValue() + writeCharacteristic().
setCharacteristicNotification() — metoda BluetoothGatt pentru abonarea la notificări privind modificarea caracteristicii pe periferie. După activarea abonamentului, dispozitivul BLE trimite valori noi prin onCharacteristicChanged(). Cu toate acestea, setCharacteristicNotification() activează doar notificarea locală Android — pentru a activa notificările pe dispozitivul BLE în sine, este necesar să scrieți și valoarea 0x0100 în descriptorul CCCD (0x2902).
CCCD (Client Characteristic Configuration Descriptor) — descriptorul care gestionează trimiterea notificărilor de pe periferia BLE. Valoarea 0x0000 — notificări dezactivate, 0x0100 — notificări activate (notifications), 0x0200 — indicații activate (indications). Scrierea în CCCD se execută prin writeDescriptor() pe BluetoothGatt după apelarea setCharacteristicNotification(). Android nu scrie CCCD automat — această responsabilitate revine dezvoltatorului.
// Abonarea corectă la notificări BLE
class GattNotificationManager {
// 1. Activează notificările
fun enableNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
// Pasul 1: abonarea locală Android
val success = gatt.setCharacteristicNotification(characteristic, true)
if (!success) {
print("Abonare eșuată")
return
}
// Pasul 2: scrie CCCD (0x2902) pe dispozitivul BLE
val cccdDescriptor = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
) ?: return
// 0x0100 = notificare, 0x0200 = indicație
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. Descoperirea caracteristicii
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 notificare
private val notificationCallback = object : BluetoothGattCallback() {
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
callbackType: Int
) {
// Valoare nouă de la periferia BLE
print("Notification: ${value.size} bytes")
}
}
}
Clasa GattNotificationManager implementează protocolul corect în doi pași pentru abonarea la notificări BLE. enableNotification apelează mai întâi setCharacteristicNotification(true) pe Android, apoi scrie 0x0100 în descriptorul CCCD prin writeDescriptor. disableNotification execută operațiile inverse. Fără scrierea CCCD, dispozitivul BLE nu trimite notificări — aceasta este cea mai frecventă greșeală a dezvoltatorilor BLE pe Android.
Exemplu complet de client GATT în Kotlin, care combină crearea BluetoothGatt, descoperirea, citirea și abonarea la notificări într-un singur manager folosind corutine pentru procesarea asincronă.
// Client GATT complet cu corutine în Kotlin
class GattClient(context: Context) {
private val context = context.applicationContext
private var gatt: BluetoothGatt? = null
// Conectare cu corutină
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
)
}
// Citește caracteristica prin corutină
suspend fun readCharacteristicValue(char: BluetoothGattCharacteristic): ByteArray? =
suspendCoroutine { continuation ->
gatt?.let { gatt ->
// Salvează eticheta caracteristicii pentru identificarea callback-ului
gatt.setCharacteristic(char, null) // pentru API < 33
gatt.readCharacteristic(char)
}
}
// Închide conexiunea
fun release() {
gatt?.disconnect()
gatt?.close()
gatt = null
}
}
Clientul GATT GattClient utilizează corutine (suspendCoroutine) pentru a transforma API-ul bazat pe callback al BluetoothGatt în apeluri secvențiale. connect() așteaptă onServicesDiscovered, după care ierarhia GATT este disponibilă. readCharacteristicValue() așteaptă onCharacteristicRead. Această abordare elimină imbricarea callback-urilor și face codul BLE liniar. release() garantează eliberarea resurselor — apel obligatoriu în onDestroy al Activity sau ViewModel.onCleared.
Întrebări frecvente
BluetoothGatt — clasa pentru clientul GATT Android care gestionează conexiunea BLE cu dispozitivul periferic. Se creează prin BluetoothDevice.connectGatt(), oferă metodele discoverServices(), readCharacteristic(), writeCharacteristic(), setCharacteristicNotification(). Rezultatele tuturor operațiunilor vin asincron prin BluetoothGattCallback. Fără BluetoothGatt, comunicarea BLE bidirecțională pe Android este imposibilă.
Statusul 133 (GATT_ERROR) înseamnă o eroare internă a stivei BLE Android. Cauze: dispozitivul s-a deconectat în timpul descoperirii, MTU este mai mic decât minimul (23 de octeți) sau stiva BLE este suprasolicitată. Soluție: repetați discoverServices() cu o întârziere de 500 ms, verificați RSSI-ul dispozitivului și asigurați-vă că periferia suportă descoperirea GATT în starea curentă.
Pentru scrierea cu confirmare, apelați writeCharacteristic() cu WRITE_TYPE_DEFAULT (API 33+: BluetoothGattCharacteristicWriteRequest). La succes, dispozitivul BLE trimite o confirmare, iar Android apelează onCharacteristicWrite cu GATT_SUCCESS. Dacă dispozitivul nu răspunde în 30 de secunde (timeout-ul stivei), callback-ul returnează un status de eroare. Pentru watchdog, utilizați Handler cu postDelayed.
Pe Android 13+ (API 33), metodele BluetoothGatt s-au schimbat: writeCharacteristic() acceptă acum BluetoothGattCharacteristicWriteRequest, readCharacteristic() — BluetoothGattCharacteristicReadRequest. Vechile setValue()/writeCharacteristic() sunt deprecated. De asemenea, BluetoothGattCallback s-a schimbat: onCharacteristicRead(), onCharacteristicWrite(), onCharacteristicChanged() primesc ByteArray value și callbackType. Utilizați Build.VERSION.SDK_INT pentru ramificare.
Android suportă 4–8 conexiuni BLE-GATT simultane (depinde de producător și versiunea Android). Pixel/Google: până la 7, Samsung: până la 5, Xiaomi: până la 4. La depășirea limitei, connectGatt returnează null sau onConnectionStateChange cu eroare. Pentru lucrul cu un număr mare de dispozitive, utilizați conexiunea ciclică sau Bluetooth Mesh.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și