BluetoothGatt — o que é, métodos e protocolo GATT BLE no Android

Autor: IT Sectr Publicado: 2026-07-16 Tempo de leitura: 10 min

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 — classe Android para cliente GATT que gerencia conexão BLE com um dispositivo periférico
  • connectGatt() — método BluetoothDevice para criar BluetoothGatt; aceita contexto, autoConnect, callback e transport
  • discoverServices() — método para obter a hierarquia GATT: serviços (BluetoothGattService), características (BluetoothGattCharacteristic)
  • readCharacteristic/writeCharacteristic — métodos de leitura e escrita com resultado assíncrono via BluetoothGattCallback
  • setCharacteristicNotification — método de inscrição em notificações BLE com escrita obrigatória do descritor CCCD

O que é BluetoothGatt: essência e criação de conexão

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

kotlin
// 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.

Descoberta de serviços e características via BluetoothGatt

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.

kotlin
// 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.

Leitura de características BLE: readCharacteristic e readDescriptor

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.

kotlin
// 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.

Escrita de características: writeCharacteristic com WriteType

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.

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

Inscrição em notificações: setCharacteristicNotification e CCCD

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.

kotlin
// 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 de cliente GATT em Kotlin com BluetoothGattCallback

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.

kotlin
// 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

O que é BluetoothGatt no Android?

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.

Por que onServicesDiscovered retorna status 133?

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.

Como escrever corretamente uma característica com confirmação?

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.

Como trabalhar com BLE no Android 33+?

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.

Quantas conexões BLE o Android suporta?

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

  • BluetoothGatt — cliente GATT Android para conexão BLE, criado via BluetoothDevice.connectGatt() com BluetoothGattCallback
  • discoverServices() — etapa obrigatória após a conexão para obter serviços, características e descritores do dispositivo BLE
  • Leitura — readCharacteristic() com resultado assíncrono em onCharacteristicRead(); para dados grandes é necessária negociação de MTU
  • Escrita — writeCharacteristic() com WriteType: DEFAULT (withResponse) ou NO_RESPONSE (sem confirmação)
  • Notificações — ativação em duas etapas: setCharacteristicNotification() + escrita do CCCD (0x2902) com valor 0x0100
  • API 33+ — novos métodos writeCharacteristic(request) e readCharacteristic(request) com objetos de solicitação
  • Liberar recursos — chamada obrigatória a disconnect() e close() para evitar vazamento de conexões BLE

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.

Discutir o projeto

Leia também