BluetoothLeScanner é uma classe Android para escanear dispositivos Bluetooth Low Energy, disponível desde API 21 (Android 5.0). BluetoothLeScanner substituiu o método obsoleto startLeScan no BluetoothAdapter, fornecendo uma API flexível com configuração de escaneamento (ScanSettings), filtragem (ScanFilter) e suporte a modo de fundo (PendingIntent). A instância é obtida através de BluetoothAdapter.getBluetoothLeScanner(). De acordo com Android Developers, 2026, BluetoothLeScanner suporta três modos de energia e permite escanear pacotes de publicidade BLE com filtragem por UUID de serviço, nome do dispositivo ou endereço MAC.
Pontos Principais
BluetoothLeScanner é uma classe de sistema para gerenciar o escaneamento BLE no Android. Ao contrário de BluetoothAdapter.startLeScan(), que aceita um simples LeScanCallback, BluetoothLeScanner fornece uma API orientada a objetos com configurações, filtros e tratamento avançado de erros. Esta classe foi introduzida na API 21 (Android 5.0) juntamente com o suporte a BLE 4.2 e continua sendo o principal método de escaneamento BLE em todas as versões modernas do Android.
A obtenção de uma instância BluetoothLeScanner é feita através de BluetoothAdapter.getBluetoothLeScanner(). O método retorna null se o adaptador Bluetooth estiver indisponível (Bluetooth desativado ou dispositivo não suporta BLE). Antes de obtê-la, verifique BluetoothAdapter.isEnabled() e a presença de FEATURE_BLUETOOTH_LE via PackageManager. Uma vez obtido o scanner, o escaneamento pode ser iniciado em qualquer thread — o Android agenda as operações BLE em uma thread interna da pilha Bluetooth.
// Obtendo BluetoothLeScanner
class BLEScannerManager(context: Context) {
private val bluetoothManager: BluetoothManager =
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
private val adapter: BluetoothAdapter? = bluetoothManager.adapter
private var scanner: BluetoothLeScanner? = null
fun initScanner(): Boolean {
// Verificar disponibilidade de BLE
if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
return false
}
// Verificar Bluetooth ativado
if (adapter?.isEnabled != true) {
return false
}
// Obter scanner
scanner = adapter?.bluetoothLeScanner
return scanner != null
}
// Verificar disponibilidade do scanner
val isAvailable: Boolean
get() = scanner != null
// Iniciar varredura básica sem filtros
fun startBasicScan() {
scanner?.startScan(object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
handleResult(result)
}
})
}
private fun handleResult(result: ScanResult) {
val device = result.device
print("Device: ${device.name ?: "Unnamed"}, RSSI: ${result.rssi}, address: ${device.address}")
}
}
A classe BLEScannerManager demonstra a obtenção e inicialização segura do BluetoothLeScanner. initScanner verifica a disponibilidade de BLE via hasSystemFeature, Bluetooth ativado e obtenção bem-sucedida do scanner. startBasicScan inicia o escaneamento sem configurações ou filtros — descobre todos os dispositivos BLE no alcance. handleResult analisa ScanResult: BluetoothDevice (nome, endereço), RSSI (intensidade do sinal), scanRecord (dados de publicidade).
ScanSettings é uma classe para configuração do escaneamento BLE. O parâmetro principal é o modo de escaneamento (scanMode), que determina o equilíbrio entre consumo de energia e latência de descoberta. ScanSettings.Builder permite configurar: scanMode, callbackType (CALLBACK_TYPE_ALL_MATCHES, CALLBACK_TYPE_FIRST_MATCH, CALLBACK_TYPE_MATCH_LOST), matchMode (MATCH_MODE_AGGRESSIVE, MATCH_MODE_STICKY), reportDelay (atraso de entrega em lote) e phy (PHY_LE_1M, PHY_LE_2M, PHY_LE_CODED).
Três modos de escaneamento: SCAN_MODE_LOW_POWER (0) — escaneamento em segundo plano com baixo consumo, atraso de descoberta de vários segundos. SCAN_MODE_BALANCED (1) — modo equilibrado para a maioria dos cenários. SCAN_MODE_LOW_LATENCY (2) — latência mínima de descoberta (cerca de 100 ms), consumo máximo de energia. Para descoberta ativa de dispositivos use LOW_LATENCY, para monitoramento em segundo plano use LOW_POWER.
reportDelay — atraso em milissegundos antes da entrega em lote dos resultados. Se reportDelay = 0, os resultados são enviados imediatamente ao serem descobertos. Se > 0, o Android acumula resultados e envia um lote via onBatchScanResults. A entrega em lote reduz as invocações de callbacks e o consumo de energia, adequada para escaneamento em segundo plano de baixa prioridade.
// Configuração de ScanSettings para diferentes cenários
class ScanSettingsProvider {
// 1. Varredura rápida (busca ativa)
fun lowLatencyScan(): ScanSettings {
return ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.setReportDelay(0)
.setPhy(ScanSettings.PHY_LE_ALL_SUPPORTED)
.build()
}
// 2. Varredura eficiente em energia (monitoramento em fundo)
fun lowPowerScan(): ScanSettings {
return ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
.setMatchMode(ScanSettings.MATCH_MODE_STICKY)
.setReportDelay(2000) // lote a cada 2 segundos
.build()
}
// 3. Varredura BLE Long Range (Coded PHY)
fun longRangeScan(): ScanSettings {
return ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setPhy(ScanSettings.PHY_LE_CODED)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.build()
}
// 4. Escanear apenas em 2M PHY (BLE 5.0+)
fun highSpeedScan(): ScanSettings {
return ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setPhy(ScanSettings.PHY_LE_2M)
.build()
}
}
A classe ScanSettingsProvider contém configurações típicas. lowLatencyScan — para escaneamento de UI (busca “aqui e agora”). lowPowerScan — para monitoramento em segundo plano com lote a cada 2 segundos e callbackType FIRST_MATCH (aciona apenas na primeira descoberta). longRangeScan usa PHY_LE_CODED (BLE Long Range, até 1 km). highSpeedScan — PHY_LE_2M (2 Mbit/s, apenas dispositivos BLE 5.0+).
ScanFilter é uma classe para filtrar resultados de escaneamento BLE. Sem filtro, BluetoothLeScanner retorna todos os dispositivos BLE no alcance — em um ambiente BLE denso podem ser centenas de pacotes por minuto. ScanFilter reduz os resultados aos dispositivos desejados, diminuindo o consumo de energia e a carga do aplicativo. Os filtros são aplicados no nível da pilha Bluetooth — pacotes inadequados são descartados antes de chegar ao app.
Tipos de filtros: setServiceUuid — UUID do serviço (formato completo de 128 bits necessário). setDeviceName — substring do nome do dispositivo (sensível a maiúsculas, correspondência exata de substring). setDeviceAddress — endereço MAC exato. setManufacturerData — dados do fabricante (ID da empresa + máscara). Vários filtros podem ser definidos para uma única varredura — o dispositivo deve corresponder a todos (lógica AND). Para lógica OR, inicie várias varreduras.
// Criação de ScanFilter para diferentes cenários
class ScanFilterFactory {
// 1. Filtrar por UUID de serviço (Monitor de Frequência Cardíaca)
fun byHeartRateService(): ScanFilter {
return ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
)
.build()
}
// 2. Filtrar por nome do dispositivo ( "iBeacon*")
fun byDeviceName(): ScanFilter {
return ScanFilter.Builder()
.setDeviceName("Sensor")
.build()
}
// 3. MAC- ( dispositivos)
fun byMacAddress(mac: String): ScanFilter {
return ScanFilter.Builder()
.setDeviceAddress(mac)
.build()
}
// 4. Filtro combinado (UUID + )
fun combinedFilter(): List<ScanFilter> {
return listOf(
ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000A001-0000-1000-8000-00805F9B34FB")
)
.setDeviceName("MyDevice")
.build()
)
}
// 5. dados do fabricante
fun byManufacturer(companyId: Int, data: ByteArray, mask: ByteArray): ScanFilter {
return ScanFilter.Builder()
.setManufacturerData(companyId, data, mask)
.build()
}
}
A classe ScanFilterFactory mostra todos os tipos de filtros. byHeartRateService filtra dispositivos com o serviço de frequência cardíaca 0x180D. byDeviceName encontra dispositivos contendo “Sensor” no nome (Apple recomenda nomes exclusivos para filtragem). byMacAddress — busca exata de um dispositivo específico. combinedFilter — filtro AND por UUID e nome. byManufacturer — filtro por dados do fabricante (por exemplo, para iBeacon usa-se o ID da empresa Apple 0x004C).
ScanCallback é uma classe abstrata para receber resultados de escaneamento BLE. Contém três métodos: onScanResult — resultado individual (tipo de callback, ScanResult), onBatchScanResults — resultados em lote para reportDelay > 0, onScanFailed — código de erro. Todos os métodos são chamados na thread principal do Android. Para processamento longo em onScanResult, use corrotinas ou HandlerThread.
ScanResult contém: BluetoothDevice device, int rssi (nível de sinal em dBm), ScanRecord scanRecord (dados de publicidade), long timestampNanos (tempo de descoberta desde a inicialização do sistema). ScanRecord fornece: getServiceData() — UUID + dados personalizados, getManufacturerSpecificData() — dados do fabricante, getAdvertiseFlags() — flags BLE. O tipo de callback indica: CALLBACK_TYPE_ALL_MATCHES — correspondência com o filtro, CALLBACK_TYPE_FIRST_MATCH — primeira descoberta, CALLBACK_TYPE_MATCH_LOST — dispositivo perdido.
Códigos de erro onScanFailed: SCAN_FAILED_ALREADY_STARTED (1) — escaneamento já iniciado, SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2) — falha no registro do app na pilha Bluetooth, SCAN_FAILED_INTERNAL_ERROR (3) — erro interno da pilha, SCAN_FAILED_FEATURE_UNSUPPORTED (4) — escaneamento BLE não suportado no dispositivo.
// Resultados completos e tratamento de erros
class ScanResultHandler {
private val results = mutableListOf<ScanResult>()
val scanCallback = object : ScanCallback() {
// 1. Resultado individual
override fun onScanResult(callbackType: Int, result: ScanResult) {
// callbackType: 1 = ALL_MATCHES, 2 = FIRST_MATCH, 4 = MATCH_LOST
if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) {
onDeviceLost(result)
return
}
// Adicionar à lista (desduplicar por endereço)
val existingIndex = results.indexOfFirst {
it.device.address == result.device.address
}
if (existingIndex >= 0) {
results[existingIndex] = result // atualizar RSSI
} else {
results.add(result)
}
// Extrair dados do pacote de publicidade
val record = result.scanRecord
val serviceData = record?.serviceData
val manufacturerData = record?.manufacturerSpecificData
print("Found: ${result.device.name ?: "N/A"}, RSSI: ${result.rssi}")
}
// 2. Resultados em lote (reportDelay > 0)
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
results?.let { batch ->
print("Batch: ${batch.size} devices")
}
}
// 3. Erro de varredura
override fun onScanFailed(errorCode: Int) {
val error = when (errorCode) {
ScanCallback.SCAN_FAILED_ALREADY_STARTED -> "Already scanning"
ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED -> "Registration failed"
ScanCallback.SCAN_FAILED_INTERNAL_ERROR -> "Internal error"
ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED -> "BLE not supported"
else -> "Unknown error: $errorCode"
}
print("Error: $error")
}
}
private fun onDeviceLost(result: ScanResult) {
results.removeAll { it.device.address == result.device.address }
print("Device lost: ${result.device.address}")
}
}
A classe ScanResultHandler lida com todos os tipos de callbacks do BluetoothLeScanner. onScanResult atualiza a lista de dispositivos com desduplicação por endereço MAC — RSSI é atualizado para dispositivos já encontrados. CALLBACK_TYPE_MATCH_LOST sinaliza a perda do dispositivo (remoção da lista). onBatchScanResults processa resultados em lote para reportDelay > 0. onScanFailed mapeia códigos de erro para mensagens legíveis — crítico para depuração de escaneamento BLE.
Escaneamento com PendingIntent é um mecanismo do BluetoothLeScanner para escaneamento BLE que funciona mesmo quando o app está em segundo plano (com restrições do Android 8+). Em vez de ScanCallback, usa-se um PendingIntent que envia um Broadcast ao BroadcastReceiver do sistema quando um dispositivo BLE é descoberto. Isso permite que o app receba notificações de dispositivos BLE sem permanecer na memória (o sistema cria o processo ao receber o broadcast).
Limitações do escaneamento em segundo plano: No Android 8+ (API 26), serviços em segundo plano são restritos — o escaneamento com PendingIntent contorna essa limitação através do BroadcastReceiver, que o sistema pode iniciar ao receber um evento BLE. No Android 10+ (API 29), o escaneamento BLE em segundo plano é adicionalmente restrito por políticas de economia de energia dos fabricantes (Xiaomi, Huawei, Samsung bloqueiam operações BLE em segundo plano). Para cenários BLE críticos, é necessária uma notificação de foreground service.
// Escaneamento BLE em fundo via PendingIntent
class BackgroundBLEScanner(private val context: Context) {
private val scanner: BluetoothLeScanner? by lazy {
val adapter = BluetoothAdapter.getDefaultAdapter()
adapter?.bluetoothLeScanner
}
fun startBackgroundScan() {
// Criar PendingIntent para BroadcastReceiver
val intent = Intent(context, BLEBroadcastReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// Configurações de escaneamento em fundo
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
.setMatchMode(ScanSettings.MATCH_MODE_STICKY)
.build()
// Iniciar escaneamento em fundo
scanner?.startScan(
null, // filtros
settings,
pendingIntent
)
}
fun stopBackgroundScan() {
val intent = Intent(context, BLEBroadcastReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
scanner?.stopScan(pendingIntent)
}
}
// BroadcastReceiver BLE-
class BLEBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// Obter resultados do escaneamento
val results = BluetoothLeScanner.getPendingIntentScanResults(intent)
results?.let { scanResults ->
for (result in scanResults) {
// Enviar notificação ao usuário
showNotification(context, result.device.name ?: " ")
}
}
}
private fun showNotification(context: Context, name: String) {
val notification = Notification.Builder(context, "ble_channel")
.setSmallIcon(android.R.drawable.ic_dialog_info)
.setContentTitle("BLE devices")
.setContentText("Found: $name")
.setAutoCancel(true)
.build()
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE)
as NotificationManager
manager.notify(System.currentTimeMillis().toInt(), notification)
}
}
A classe BackgroundBLEScanner inicia o escaneamento BLE em segundo plano via PendingIntent. startBackgroundScan cria um PendingIntent que, ao descobrir um dispositivo BLE, envia um Broadcast para BLEBroadcastReceiver. O BroadcastReceiver extrai ScanResult via getPendingIntentScanResults() e pode mostrar uma notificação ou enviar dados ao servidor. Essa abordagem funciona mesmo se o app foi encerrado pelo sistema — o Android reinicia o BroadcastReceiver ao receber o broadcast.
Exemplo completo de um scanner BLE em Kotlin usando BluetoothLeScanner com ScanSettings, ScanFilter e ScanCallback para encontrar dispositivos Heart Rate Monitor. O scanner mostra uma lista de dispositivos encontrados com RSSI e UUIDs de serviço, com capacidade de conectar via BluetoothGatt.
// Scanner BLE completo com corrotinas em Kotlin
class DeviceScanner(private val context: Context) {
private val adapter: BluetoothAdapter? by lazy {
val manager = context.getSystemService(Context.BLUETOOTH_SERVICE)
as BluetoothManager
manager.adapter
}
private val scanner: BluetoothLeScanner? by lazy {
adapter?.bluetoothLeScanner
}
fun startScan(duration: Long = 10000): Flow<ScanResult> = callbackFlow {
// Verificar estado do Bluetooth
if (adapter?.isEnabled != true) {
close(IllegalStateException("Bluetooth disabled"))
return@callbackFlow
}
// Configuração de escaneamento
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()
val filters = listOf(
ScanFilter.Builder()
.setServiceUuid(ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
.build()
)
val callback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
trySend(result)
}
override fun onScanFailed(errorCode: Int) {
close(BLEException("Scan failed: $errorCode"))
}
}
// Iniciar escaneamento
scanner?.startScan(filters, settings, callback)
// Parada automática após duração
delay(duration)
scanner?.stopScan(callback)
close()
}.flowOn(Dispatchers.IO)
fun stop() {
scanner?.stopScan(object : ScanCallback() {})
}
}
class BLEException(message: String) : Exception(message)
A classe DeviceScanner usa Kotlin Flow (callbackFlow) para escaneamento BLE reativo. O escaneamento é iniciado com configurações LOW_LATENCY e um filtro UUID do Heart Rate Service. Os resultados são emitidos via onScanResult para um Flow. Parada automática após uma duração determinada (10 segundos por padrão). FlowOn(Dispatchers.IO) transfere as operações BLE para uma thread em segundo plano. Essa abordagem permite o escaneamento BLE na arquitetura MVVM via viewModelScope.launch e collect.
Perguntas Frequentes
BluetoothLeScanner é uma classe Android (API 21+) para escaneamento BLE. É obtida via BluetoothAdapter.getBluetoothLeScanner(). Suporta três modos de escaneamento (LOW_POWER, BALANCED, LOW_LATENCY), filtragem por UUID, nome e endereço MAC, resultados em lote e PendingIntent para escaneamento em segundo plano. Substitui o método obsoleto BluetoothAdapter.startLeScan().
SCAN_MODE_LOW_POWER — modo fundo com atraso de descoberta de 5–10 segundos, consumo mínimo. SCAN_MODE_LOW_LATENCY — modo ativo com atraso de cerca de 100 ms, consumo máximo. SCAN_MODE_BALANCED — compromisso (~2 segundos de atraso). Use LOW_LATENCY para escaneamento de UI, LOW_POWER com PendingIntent para monitoramento em segundo plano.
Razões: Bluetooth desativado (verifique adapter.isEnabled), permissões ausentes (BLUETOOTH_SCAN na API 31+, ACCESS_FINE_LOCATION na API 23–30), scanner = null (adaptador indisponível), dispositivo fora do alcance, ou filtro incorreto. Verifique também onScanFailed — o código de erro indica a causa: SCAN_FAILED_ALREADY_STARTED (1) ou SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2).
Use a versão PendingIntent de startScan() — passe um PendingIntent em vez de ScanCallback. Quando um dispositivo BLE é descoberto, o Android envia um Broadcast ao BroadcastReceiver, que pode ser iniciado pelo sistema mesmo se o app estiver em segundo plano. Para Android 8+, adicione o BroadcastReceiver ao manifesto. No Android 10+, considere as restrições de economia de energia dos fabricantes.
BluetoothLeScanner não tem limite no número de dispositivos descobertos — a limitação depende da saturação BLE do ambiente. Um escritório pode ter 20–50 dispositivos BLE ativos, um shopping center centenas. Use ScanFilter (por UUID, nome) para filtrar. Sem filtragem, processe os resultados de forma assíncrona — onScanResult pode ser chamado dezenas de vezes por segundo.
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