BluetoothGatt — uma classe Android que fornece API para operações de cliente GATT (Generic Attribute Profile) sobre uma conexão BLE. BluetoothGatt encapsula a conexão a um servidor GATT remoto (dispositivo periférico BLE) e gerencia todas as operações do perfil: descoberta de serviços, leitura e escrita de características, inscrição em notificações e indicações. Uma instância de BluetoothGatt é obtida via BluetoothDevice.connectGatt() com um BluetoothGattCallback. De acordo com Android Developers, 2026, BluetoothGatt é a classe central para comunicação BLE bidirecional, suportando operações GATT de BLE 4.0 a BLE 5.4.
Pontos Principais
BluetoothGatt — um objeto proxy que representa uma conexão GATT entre um dispositivo Android (central) e um periférico BLE (servidor). Cada instância BluetoothGatt corresponde a uma conexão BLE ativa. Todas as operações do perfil GATT são realizadas através dele: descoberta, leitura, escrita, notificações. BluetoothGatt não é criado diretamente — ele é retornado pelo método BluetoothDevice.connectGatt().
Criar um BluetoothGatt requer quatro parâmetros. Context — o contexto da aplicação (Activity ou Application). autoConnect — se false, o Android inicia imediatamente uma conexão direta; se true, o Android conecta automaticamente quando o dispositivo é detectado (útil para conexão em segundo plano). BluetoothGattCallback — callback obrigatório para todos os eventos GATT. transport — BluetoothDevice.TRANSPORT_LE (BLE) ou TRANSPORT_BREDR (Classic). Em dispositivos BLE, use sempre TRANSPORT_LE.
Ciclo de vida do BluetoothGatt consiste em cinco estados. DISCONNECTED — estado inicial. CONNECTING — após chamar connectGatt, antes da confirmação. CONNECTED — após onConnectionStateChange com STATE_CONNECTED. Após a conexão, discoverServices() é chamado para obter a hierarquia GATT. Após finalizar o trabalho — disconnect() e close() para liberar recursos do sistema. Sem close(), a aplicação pode esgotar o limite de conexões BLE do Android (geralmente 4–8).
// Criando conexão BluetoothGatt
import android.bluetooth.*
class GattConnector(private val context: Context) {
private var bluetoothGatt: BluetoothGatt? = null
fun connect(device: BluetoothDevice): BluetoothGatt? {
// Fechar conexão anterior se existir
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. Conexão estabelecida → Descoberta
gatt.discoverServices()
}
BluetoothProfile.STATE_DISCONNECTED -> {
// 3. Conexão perdida
close()
}
}
}
override fun onServicesDiscovered(
gatt: BluetoothGatt, status: Int
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// 4. Hierarquia GATT recebida
onGattReady(gatt)
}
}
},
BluetoothDevice.TRANSPORT_LE
)
return bluetoothGatt
}
private fun onGattReady(gatt: BluetoothGatt) {
// GATT pronto para operações de leitura/escrita
}
fun close() {
bluetoothGatt?.disconnect()
bluetoothGatt?.close()
bluetoothGatt = null
}
}
A classe GattConnector demonstra a criação correta de BluetoothGatt. O parâmetro autoConnect=false significa conexão direta (para dispositivos escaneados). Ao onConnectionStateChange com STATE_CONNECTED, discoverServices() é chamado imediatamente. onServicesDiscovered sinaliza que o GATT está pronto. close() chama sequencialmente disconnect() e close() — sem close() os recursos do sistema não são liberados, causando vazamento de conexões BLE.
discoverServices() — o primeiro método GATT chamado após a conexão. Inicia uma busca assíncrona de todos os serviços no periférico BLE. O resultado chega em onServicesDiscovered() com um código de status: GATT_SUCCESS (0) — sucesso, 133 — GATT_ERROR, 8 — GATT_CONNECTION_TIMEOUT. Após descoberta bem-sucedida, BluetoothGatt preenche a lista de serviços acessíveis via getServices().
Cada BluetoothGattService contém uma lista de BluetoothGattCharacteristic. Uma característica tem UUID, propriedades (PROPERTY_READ, PROPERTY_WRITE, PROPERTY_NOTIFY) e descritores opcionais. As propriedades determinam quais operações são permitidas: se uma característica não tem PROPERTY_READ, chamar readCharacteristic retornará um erro. Use getDescriptors() para obter os descritores da característica.
// Descoberta de serviços e busca de características
class GattServiceExplorer {
// Encontrar serviço por UUID
fun findService(gatt: BluetoothGatt, uuid: UUID): BluetoothGattService? {
return gatt.services?.firstOrNull { it.uuid == uuid }
}
// Encontrar característica no serviço
fun findCharacteristic(
service: BluetoothGattService,
uuid: UUID
): BluetoothGattCharacteristic? {
return service.characteristics?.firstOrNull { it.uuid == uuid }
}
// Obter todas as operações de característica suportadas
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("ESCREVER")
if (and(BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0) props.add("ESCREVER_SEM_RESPOSTA")
if (and(BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) props.add("NOTIFY")
if (and(BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) props.add("INDICATE")
}
return props
}
// Registrar hierarquia GATT completa
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}")
}
}
}
}
}
A classe GattServiceExplorer fornece utilitários para navegar pela hierarquia GATT. findService e findCharacteristic procuram serviços e características por UUID. getCharacteristicProperties verifica as máscaras de bits das propriedades via and(). dumpGattTree imprime a hierarquia completa no log — útil ao depurar dispositivos BLE. Todas as operações no BluetoothGatt devem ser realizadas após onServicesDiscovered bem-sucedido, caso contrário getServices() retornará uma lista vazia.
readCharacteristic() — um método assíncrono BluetoothGatt para ler o valor de uma característica de um dispositivo BLE remoto. O resultado chega em onCharacteristicRead() do BluetoothGattCallback. Se um dispositivo tiver 2+ características correspondentes (improvável mas possível), readCharacteristic() pode ler uma não alvo — é mais seguro chamar readCharacteristic() em uma instância BluetoothGattCharacteristic em vez de por UUID.
readDescriptor() — um método para ler o valor de um descritor de característica. Um descritor típico é CCCD (Client Characteristic Configuration Descriptor, UUID 0x2902), que determina se as notificações estão ativadas. O resultado está em onDescriptorRead(). A leitura de descritores raramente é necessária na prática — CCCD é gerenciado por setCharacteristicNotification(), mas para descritores personalizados (User Description 0x2901, Presentation Format 0x2904) readDescriptor() é a única maneira de obter metadados.
MTU e leitura de dados grandes — se o valor da característica exceder o MTU (23 bytes para BLE 4.0), o Android fragmenta e remonta automaticamente os dados através de uma sequência de solicitações de leitura via pilha BLE. Para BLE 5.0+ com MTU estendido (até 251 bytes), a fragmentação não é necessária — uma única leitura retorna dados completos. Antes de ler, você pode chamar requestMtu() para negociar o MTU máximo.
// Ler característica e descritor via BluetoothGatt
class GattReader {
fun readHeartRate(gatt: BluetoothGatt) {
// UUID do serviço de frequência cardíaca = 0x180D
val service = gatt.getService(UUID.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
?: return
// UUID de medição de frequência cardíaca = 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) }
}
// Processar dados no callback onCharacteristicRead:
fun parseHeartRate(value: ByteArray): Int {
// Frequência cardíaca BLE: 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
}
}
A classe GattReader demonstra a leitura de uma característica de frequência cardíaca. O serviço 0x180D contém a característica 0x2A37 (Heart Rate Measurement) — um perfil BLE padrão da Bluetooth SIG. Antes da leitura, a propriedade PROPERTY_READ é verificada via hasProperty. parseHeartRate analisa o formato BLE de pulso: o primeiro byte — flags (formato de dados), o segundo — valor bpm. O descritor CCCD (0x2902) é lido para verificar o status das notificações.
writeCharacteristic() — um método BluetoothGatt para escrever dados em um periférico BLE. No Android API 33+, writeCharacteristic() foi substituído por writeCharacteristic(request), onde BluetoothGattCharacteristicWriteRequest é um objeto de solicitação contendo a característica, matriz de bytes e WriteType. O antigo método writeCharacteristic(characteristic) com setValue() está obsoleto. WriteType determina o comportamento da solicitação: WRITE_TYPE_DEFAULT (depende das propriedades da característica), WRITE_TYPE_NO_RESPONSE (sem resposta) e WRITE_TYPE_SIGNED (autorização).
Escolher o WriteType afeta a velocidade e a confiabilidade. WRITE_TYPE_DEFAULT geralmente corresponde a withResponse (se a característica tem PROPERTY_WRITE) ou withoutResponse (se tem PROPERTY_WRITE_NO_RESPONSE). Para dados em streaming (atualizações OTA, logs), use WRITE_TYPE_NO_RESPONSE — máxima taxa de transferência. Para comandos com garantia de entrega (ativar, configurar) — WRITE_TYPE_DEFAULT com confirmação via onCharacteristicWrite.
// Escrita de característica BLE no Android API 33+
class GattWriter {
// Escrever com resposta (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)
}
}
// Escrever sem resposta (velocidade máxima)
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")
}
}
}
}
A classe GattWriter suporta ambos os WriteTypes para diferentes níveis de API. writeWithResponse usa WRITE_TYPE_DEFAULT — o dispositivo BLE confirma a escrita via onCharacteristicWrite. writeWithoutResponse usa WRITE_TYPE_NO_RESPONSE — os dados são enviados sem confirmação, máxima taxa de transferência. Na API 33+, usa-se o novo writeCharacteristic(request) com BluetoothGattCharacteristicWriteRequest. Na API < 33 — o antigo setValue() + writeCharacteristic().
setCharacteristicNotification() — um método BluetoothGatt para se inscrever em notificações sobre mudanças de característica no periférico. Após ativar a inscrição, o dispositivo BLE envia novos valores via onCharacteristicChanged(). No entanto, setCharacteristicNotification() apenas ativa a notificação local do Android — para habilitar notificações no próprio dispositivo BLE, você também deve escrever o valor 0x0100 no descritor CCCD (0x2902).
CCCD (Client Characteristic Configuration Descriptor) — um descritor que controla o envio de notificações do periférico BLE. Valor 0x0000 — notificações desativadas, 0x0100 — notificações ativadas, 0x0200 — indicações ativadas. A escrita no CCCD é feita via writeDescriptor() no BluetoothGatt após chamar setCharacteristicNotification(). O Android não escreve CCCD automaticamente — esta responsabilidade é do desenvolvedor.
// Inscrição correta em notificações BLE
class GattNotificationManager {
// 1. Ativar notificações
fun enableNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
// Etapa 1: inscrição local no Android
val success = gatt.setCharacteristicNotification(characteristic, true)
if (!success) {
print("Falha ao inscrever")
return
}
// Etapa 2: escrever CCCD (0x2902) no dispositivo BLE
val cccdDescriptor = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
) ?: return
// 0x0100 = notificação, 0x0200 = indicação
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. Descoberta de características
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 de notificação
private val notificationCallback = object : BluetoothGattCallback() {
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
callbackType: Int
) {
// Novo valor do periférico BLE
print("Notification: ${value.size} bytes")
}
}
}
A classe GattNotificationManager implementa o protocolo correto de duas etapas para inscrição em notificações BLE. enableNotification primeiro chama setCharacteristicNotification(true) no Android, depois escreve 0x0100 no descritor CCCD via writeDescriptor. disableNotification realiza as operações inversas. Sem a escrita do CCCD, o dispositivo BLE não envia notificações — este é o erro mais comum entre desenvolvedores BLE no Android.
Exemplo completo de um cliente GATT em Kotlin que combina criação de BluetoothGatt, descoberta, leitura e inscrição em notificações em um único gerenciador usando corrotinas para processamento assíncrono.
// Cliente GATT completo com corrotinas em Kotlin
class GattClient(context: Context) {
private val context = context.applicationContext
private var gatt: BluetoothGatt? = null
// Conectar com corrotina
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
)
}
// Ler característica via corrotina
suspend fun readCharacteristicValue(char: BluetoothGattCharacteristic): ByteArray? =
suspendCoroutine { continuation ->
gatt?.let { gatt ->
// Salvar tag da característica para identificação no callback
gatt.setCharacteristic(char, null) // para API < 33
gatt.readCharacteristic(char)
}
}
// Fechar conexão
fun release() {
gatt?.disconnect()
gatt?.close()
gatt = null
}
}
O cliente GATT GattClient usa corrotinas (suspendCoroutine) para transformar a API baseada em callbacks do BluetoothGatt em chamadas sequenciais. connect() aguarda onServicesDiscovered, após o qual a hierarquia GATT está disponível. readCharacteristicValue() aguarda onCharacteristicRead. Esta abordagem elimina o aninhamento de callbacks e torna o código BLE linear. release() garante a liberação de recursos — uma chamada obrigatória em onDestroy da Activity ou ViewModel.onCleared.
Perguntas Frequentes
BluetoothGatt — uma classe Android para cliente GATT que gerencia uma conexão BLE com um dispositivo periférico. É criado via BluetoothDevice.connectGatt(), fornecendo métodos discoverServices(), readCharacteristic(), writeCharacteristic(), setCharacteristicNotification(). Os resultados de todas as operações chegam assincronamente via BluetoothGattCallback. Sem BluetoothGatt, a comunicação BLE bidirecional no Android é impossível.
O status 133 (GATT_ERROR) indica um erro interno da pilha BLE do Android. Causas: o dispositivo desconectou durante a descoberta, MTU abaixo do mínimo (23 bytes) ou a pilha BLE está sobrecarregada. Solução: repita discoverServices() com um atraso de 500 ms, verifique o RSSI do dispositivo e certifique-se de que o periférico suporta descoberta GATT em seu estado atual.
Para escrever com confirmação, chame writeCharacteristic() com WRITE_TYPE_DEFAULT (API 33+: BluetoothGattCharacteristicWriteRequest). Em caso de sucesso, o dispositivo BLE envia uma confirmação e o Android chama onCharacteristicWrite com GATT_SUCCESS. Se o dispositivo não responder em 30 segundos (timeout da pilha), o callback retorna um status de erro. Para watchdog, use Handler com postDelayed.
No Android 13+ (API 33), os métodos BluetoothGatt mudaram: writeCharacteristic() agora aceita BluetoothGattCharacteristicWriteRequest, readCharacteristic() — BluetoothGattCharacteristicReadRequest. Os antigos setValue()/writeCharacteristic() estão obsoletos. BluetoothGattCallback também mudou: onCharacteristicRead(), onCharacteristicWrite(), onCharacteristicChanged() recebem ByteArray value e callbackType. Use Build.VERSION.SDK_INT para ramificação.
O Android suporta 4–8 conexões BLE GATT simultâneas (varia por fabricante e versão do Android). Pixel/Google: até 7, Samsung: até 5, Xiaomi: até 4. Quando o limite é excedido, connectGatt retorna null ou onConnectionStateChange com erro. Para trabalhar com um grande número de dispositivos, use conexão cíclica ou Bluetooth Mesh.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também