BluetoothGatt — Android-klass som tillhandahåller API för GATT-klienten (Generic Attribute Profile) över BLE-anslutning. BluetoothGatt kapslar in anslutningen till en fjärr-GATT-server (perifer BLE-enhet) och hanterar alla profiloperationer: upptäckt av tjänster, läsning och skrivning av egenskaper, prenumeration på meddelanden och indikationer. En BluetoothGatt-instans erhålls via BluetoothDevice.connectGatt() med callback BluetoothGattCallback. Enligt Android Developers, 2026 är BluetoothGatt den centrala klassen för tvåvägs BLE-kommunikation som stöder GATT-operationer från BLE 4.0 till BLE 5.4.
Huvudpunkter
BluetoothGatt — är ett proxyobjekt som representerar GATT-anslutningen mellan en Android-enhet (central) och BLE-periferi (server). Varje BluetoothGatt-instans motsvarar en aktiv BLE-anslutning. Genom den utförs alla GATT-profiloperationer: upptäckt, läsning, skrivning, meddelanden. BluetoothGatt skapas inte direkt — det returneras av metoden BluetoothDevice.connectGatt().
Att skapa BluetoothGatt kräver fyra parametrar. Context — applikationskontext (Activity eller Application). autoConnect — om false, initierar Android omedelbart direkt anslutning; om true, ansluter Android automatiskt vid upptäckt av enheten (användbart för bakgrundsanslutning). BluetoothGattCallback — obligatorisk callback för alla GATT-händelser. transport — BluetoothDevice.TRANSPORT_LE (BLE) eller TRANSPORT_BREDR (Classic). Använd alltid TRANSPORT_LE på BLE-enheter.
BluetoothGatts livscykel består av fem tillstånd. DISCONNECTED — initialt tillstånd. CONNECTING — efter anrop av connectGatt, fram till bekräftelse. CONNECTED — efter onConnectionStateChange med STATE_CONNECTED. Efter anslutning anropas discoverServices() för att erhålla GATT-hierarkin. Efter avslutat arbete — disconnect() och close() för att frigöra systemresurser. Utan close() kan appen förbruka Android BLE-anslutningsgränsen (vanligtvis 4–8).
// Skapa BluetoothGatt-anslutning
import android.bluetooth.*
class GattConnector(private val context: Context) {
private var bluetoothGatt: BluetoothGatt? = null
fun connect(device: BluetoothDevice): BluetoothGatt? {
// Stäng föregående anslutning om den finns
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. Anslutning upprättad → Upptäckt
gatt.discoverServices()
}
BluetoothProfile.STATE_DISCONNECTED -> {
// 3. Anslutning förlorad
close()
}
}
}
override fun onServicesDiscovered(
gatt: BluetoothGatt, status: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// 4. GATT-hierarki mottagen
onGattReady(gatt)
}
}
},
BluetoothDevice.TRANSPORT_LE
)
return bluetoothGatt
}
private fun onGattReady(gatt: BluetoothGatt) {
// GATT redo för läs-/skrivoperationer
}
fun close() {
bluetoothGatt?.disconnect()
bluetoothGatt?.close()
bluetoothGatt = null
}
}
Klassen GattConnector demonstrerar korrekt skapande av BluetoothGatt. Parametern autoConnect=false — direkt anslutning (för skannade enheter). Vid onConnectionStateChange med STATE_CONNECTED anropas omedelbart discoverServices(). onServicesDiscovered signalerar GATT-beredskap. close() anropar sekventiellt disconnect() och close() — utan close() frigörs inte systemresurser, vilket leder till läckage av BLE-anslutningar.
discoverServices() — första GATT-metoden som anropas efter anslutning. Initierar asynkron sökning efter alla tjänster på BLE-periferin. Resultatet kommer i onServicesDiscovered() med statuskod: GATT_SUCCESS (0) — lyckad, 133 — GATT_ERROR, 8 — GATT_CONNECTION_TIMEOUT. Efter lyckad upptäckt fyller BluetoothGatt listan över tjänster som är tillgängliga via getServices().
Varje BluetoothGattService innehåller en lista av BluetoothGattCharacteristic. En egenskap har UUID, egenskaper (PROPERTY_READ, PROPERTY_WRITE, PROPERTY_NOTIFY) och valfria deskriptorer. Egenskaperna bestämmer vilka operationer som är tillåtna: om en egenskap saknar PROPERTY_READ kommer anrop av readCharacteristic att returnera ett fel. För att erhålla deskriptorer för en egenskap används getDescriptors().
// Tjänstupptäckt och egenskapssökning
class GattServiceExplorer {
// Hitta tjänst efter UUID
fun findService(gatt: BluetoothGatt, uuid: UUID): BluetoothGattService? {
return gatt.services?.firstOrNull { it.uuid == uuid }
}
// Hitta egenskap i tjänst
fun findCharacteristic(
service: BluetoothGattService,
uuid: UUID
): BluetoothGattCharacteristic? {
return service.characteristics?.firstOrNull { it.uuid == uuid }
}
// Hämta alla stödda egenskapsoperationer
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
}
// Logga hela GATT-hierarkin
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}")
}
}
}
}
}
Klassen GattServiceExplorer tillhandahåller verktyg för navigering i GATT-hierarkin. findService och findCharacteristic söker efter tjänster och egenskaper enligt UUID. getCharacteristicProperties kontrollerar bitmasker för egenskaper via and(). dumpGattTree skriver ut hela hierarkin i loggen — användbar vid felsökning av BLE-enheter. Alla operationer på BluetoothGatt måste utföras efter lyckad onServicesDiscovered, annars returnerar getServices() en tom lista.
readCharacteristic() — asynkron BluetoothGatt-metod för att läsa värdet av en egenskap från en fjärr-BLE-enhet. Resultatet kommer i onCharacteristicRead() för BluetoothGattCallback. Om det finns 2+ matchande egenskaper på enheten (osannolikt men möjligt), kan readCharacteristic() läsa en icke-mål-egenskap — säkrare är att anropa readCharacteristic() på BluetoothGattCharacteristic-instansen, inte enligt UUID.
readDescriptor() — metod för att läsa värdet av en deskriptor för en egenskap. En typisk deskriptor — CCCD (Client Characteristic Configuration Descriptor, UUID 0x2902) som bestämmer om meddelanden är aktiverade. Resultat i onDescriptorRead(). Läsning av deskriptorer krävs sällan i praktiken — CCCD hanteras av setCharacteristicNotification(), men för anpassade deskriptorer (User Description 0x2901, Presentation Format 0x2904) är readDescriptor() det enda sättet att erhålla metadata.
MTU och läsning av stora data — om egenskapens värde överstiger MTU (23 byte för BLE 4.0) fragmenterar och assemblerar Android automatiskt data genom en sekvens av läsförfrågningar via BLE-stacken. För BLE 5.0+ med utökad MTU (upp till 251 byte) krävs ingen fragmentering — en läsning returnerar fullständiga data. Före läsning kan requestMtu() anropas för att förhandla om maximal MTU.
// Läsning av egenskap och deskriptor via BluetoothGatt
class GattReader {
fun readHeartRate(gatt: BluetoothGatt) {
// Heart Rate-tjänst UUID = 0x180D
val service = gatt.getService(UUID.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
?: return
// Heart Rate-mätning 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) }
}
// Bearbeta data i onCharacteristicRead-callback:
fun parseHeartRate(value: ByteArray): Int {
// BLE Heart Rate: byte = flags, andra = 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
}
}
Klassen GattReader demonstrerar läsning av Heart Rate-egenskapen. Tjänsten 0x180D innehåller egenskapen 0x2A37 (Heart Rate Measurement) — standard BLE-profil för Bluetooth SIG. Före läsning kontrolleras egenskapen PROPERTY_READ via hasProperty. parseHeartRate parses BLE-pulsformatet: första byten — flags (dataformat), andra — bpm-värde. CCCD-deskriptorn (0x2902) läses för att kontrollera meddelandestatus.
writeCharacteristic() — BluetoothGatt-metod för att skriva data till BLE-periferi. På Android API 33+ har writeCharacteristic() ersatts av writeCharacteristic(request), där BluetoothGattCharacteristicWriteRequest är ett förfrågningsobjekt som innehåller egenskapen, en byte-array och WriteType. Den gamla metoden writeCharacteristic(characteristic) med setValue() är föråldrad. WriteType bestämmer förfrågans beteende: WRITE_TYPE_DEFAULT (beror på egenskapens egenskaper), WRITE_TYPE_NO_RESPONSE (withoutResponse) och WRITE_TYPE_SIGNED (autorisering).
Val av WriteType påverkar hastighet och tillförlitlighet. WRITE_TYPE_DEFAULT motsvarar vanligtvis withResponse (om egenskapen har PROPERTY_WRITE) eller withoutResponse (om den har PROPERTY_WRITE_NO_RESPONSE). För strömmande data (OTA-uppdateringar, loggar) använd WRITE_TYPE_NO_RESPONSE — maximal bandbredd. För kommandon med leveransgaranti (aktivering, konfiguration) — WRITE_TYPE_DEFAULT med bekräftelse via onCharacteristicWrite.
// BLE-egenskapsskrivning på Android API 33+
class GattWriter {
// Skrivning med svar (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)
}
}
// Skrivning utan svar (maximal hastighet)
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")
}
}
}
}
Klassen GattWriter stöder båda WriteType för olika API-nivåer. writeWithResponse använder WRITE_TYPE_DEFAULT — BLE-enheten bekräftar skrivning via onCharacteristicWrite. writeWithoutResponse använder WRITE_TYPE_NO_RESPONSE — data skickas utan bekräftelse, maximal bandbredd. På API 33+ används den nya writeCharacteristic(request) med BluetoothGattCharacteristicWriteRequest. På API < 33 — gamla setValue() + writeCharacteristic().
setCharacteristicNotification() — BluetoothGatt-metod för prenumeration på meddelanden om ändring av egenskap på periferin. Efter aktivering av prenumeration skickar BLE-enheten nya värden via onCharacteristicChanged(). Men setCharacteristicNotification() aktiverar endast den lokala Android-meddelandet — för att aktivera meddelanden på själva BLE-enheten måste även värdet 0x0100 skrivas till CCCD-deskriptorn (0x2902).
CCCD (Client Characteristic Configuration Descriptor) — deskriptor som hanterar sändning av meddelanden från BLE-periferin. Värde 0x0000 — meddelanden avaktiverade, 0x0100 — meddelanden aktiverade (notifications), 0x0200 — indikationer aktiverade (indications). Skrivning till CCCD utförs via writeDescriptor() på BluetoothGatt efter anrop av setCharacteristicNotification(). Android skriver inte CCCD automatiskt — detta ansvar ligger på utvecklaren.
// Korrekt BLE-meddelandeprenumeration
class GattNotificationManager {
// 1. Aktivera meddelanden
fun enableNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
// Steg 1: lokal Android-prenumeration
val success = gatt.setCharacteristicNotification(characteristic, true)
if (!success) {
print("Prenumeration misslyckades")
return
}
// Steg 2: skriv CCCD (0x2902) på BLE-enheten
val cccdDescriptor = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
) ?: return
// 0x0100 = meddelande, 0x0200 = indikation
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. Egenskaper upptäckt
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. Meddelandecallback
private val notificationCallback = object : BluetoothGattCallback() {
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
callbackType: Int
) {
// Nytt värde från BLE-periferi
print("Notification: ${value.size} bytes")
}
}
}
Klassen GattNotificationManager implementerar korrekt tvåstegsprotokoll för prenumeration på BLE-meddelanden. enableNotification anropar först setCharacteristicNotification(true) på Android, skriver sedan 0x0100 till CCCD-deskriptorn via writeDescriptor. disableNotification utför motsatta operationer. Utan CCCD-skrivning skickar BLE-enheten inga meddelanden — detta är det vanligaste felet bland BLE-utvecklare på Android.
Fullständigt exempel på GATT-klient i Kotlin som kombinerar skapande av BluetoothGatt, upptäckt, läsning och prenumeration på meddelanden i en enda hanterare med hjälp av korutiner för asynkron bearbetning.
// Fullständig GATT-klient med korutiner i Kotlin
class GattClient(context: Context) {
private val context = context.applicationContext
private var gatt: BluetoothGatt? = null
// Anslut med korutin
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
)
}
// Läs egenskap via korutin
suspend fun readCharacteristicValue(char: BluetoothGattCharacteristic): ByteArray? =
suspendCoroutine { continuation ->
gatt?.let { gatt ->
// Spara egenskapstagg för callback-identifiering
gatt.setCharacteristic(char, null) // för API < 33
gatt.readCharacteristic(char)
}
}
// Stäng anslutning
fun release() {
gatt?.disconnect()
gatt?.close()
gatt = null
}
}
GATT-klienten GattClient använder korutiner (suspendCoroutine) för att omvandla det callback-baserade BluetoothGatt API till sekventiella anrop. connect() väntar på onServicesDiscovered, varefter GATT-hierarkin är tillgänglig. readCharacteristicValue() väntar på onCharacteristicRead. Detta tillvägagångssätt eliminerar nästlade callbacks och gör BLE-koden linjär. release() garanterar frigöring av resurser — obligatoriskt anrop i onDestroy för Activity eller ViewModel.onCleared.
Vanliga frågor
BluetoothGatt — klass för Android GATT-klient som hanterar BLE-anslutning med perifer enhet. Skapas via BluetoothDevice.connectGatt(), tillhandahåller metoder discoverServices(), readCharacteristic(), writeCharacteristic(), setCharacteristicNotification(). Resultat av alla operationer kommer asynkront via BluetoothGattCallback. Utan BluetoothGatt är tvåvägs BLE-kommunikation på Android omöjlig.
Status 133 (GATT_ERROR) betyder ett internt fel i Android BLE-stacken. Orsaker: enheten kopplades bort under upptäckt, MTU är mindre än minimum (23 byte) eller BLE-stacken är överbelastad. Lösning: upprepa discoverServices() med en fördröjning på 500 ms, kontrollera enhetens RSSI och säkerställ att periferin stöder GATT-upptäckt i aktuellt tillstånd.
För skrivning med bekräftelse, anropa writeCharacteristic() med WRITE_TYPE_DEFAULT (API 33+: BluetoothGattCharacteristicWriteRequest). Vid framgång skickar BLE-enheten en bekräftelse och Android anropar onCharacteristicWrite med GATT_SUCCESS. Om enheten inte svarar inom 30 sekunder (stack-timeout) returnerar callbacken en felstatus. För watchdog, använd Handler med postDelayed.
På Android 13+ (API 33) har BluetoothGatt-metoder ändrats: writeCharacteristic() accepterar nu BluetoothGattCharacteristicWriteRequest, readCharacteristic() — BluetoothGattCharacteristicReadRequest. Gamla setValue()/writeCharacteristic() är föråldrade. BluetoothGattCallback har också ändrats: onCharacteristicRead(), onCharacteristicWrite(), onCharacteristicChanged() får ByteArray value och callbackType. Använd Build.VERSION.SDK_INT för förgrening.
Android stöder 4–8 samtidiga BLE-GATT-anslutningar (beroende på tillverkare och Android-version). Pixel/Google: upp till 7, Samsung: upp till 5, Xiaomi: upp till 4. Vid överskridande av gränsen returnerar connectGatt null eller onConnectionStateChange med fel. För arbete med många enheter, använd cyklisk anslutning eller Bluetooth Mesh.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också