BluetoothLeScanner — třída Androidu pro skenování Bluetooth Low Energy zařízení, dostupná od API 21 (Android 5.0). BluetoothLeScanner nahradil zastaralou metodu startLeScan na BluetoothAdapter, poskytující flexibilní API s konfigurací skenování (ScanSettings), filtrováním (ScanFilter) a podporou režimu na pozadí (PendingIntent). Instance se získává přes BluetoothAdapter.getBluetoothLeScanner(). Podle Android Developers, 2026, BluetoothLeScanner podporuje tři režimy spotřeby energie a umožňuje skenovat BLE reklamní pakety s filtrováním podle UUID služby, názvu zařízení nebo MAC adresy.
Hlavní body
BluetoothLeScanner — systémová třída pro správu BLE skenování na Androidu. Na rozdíl od BluetoothAdapter.startLeScan(), který přijímá jednoduchý LeScanCallback, BluetoothLeScanner poskytuje objektově orientované API s nastaveními, filtry a rozšířeným zpracováním chyb. Třída se objevila v API 21 (Android 5.0) spolu s podporou BLE 4.2 a zůstává hlavním způsobem BLE skenování na všech moderních verzích Androidu.
Získání instance BluetoothLeScanner se provádí přes BluetoothAdapter.getBluetoothLeScanner(). Metoda vrací null, pokud Bluetooth adaptér není dostupný (Bluetooth vypnutý nebo zařízení nepodporuje BLE). Před získáním zkontrolujte BluetoothAdapter.isEnabled() a přítomnost FEATURE_BLUETOOTH_LE přes PackageManager. Po získání skeneru můžete spustit skenování v jakémkoli vlákně — Android sám plánuje BLE operace na interním vlákně Bluetooth stacku.
// Získání 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 {
// Zkontrolujte dostupnost BLE
if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
return false
}
// Zkontrolujte zapnutý Bluetooth
if (adapter?.isEnabled != true) {
return false
}
// Získejte skener
scanner = adapter?.bluetoothLeScanner
return scanner != null
}
// Zkontrolujte dostupnost skeneru
val isAvailable: Boolean
get() = scanner != null
// Spusťte základní skenování bez filtrů
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}")
}
}
Třída BLEScannerManager demonstruje bezpečné získání a inicializaci BluetoothLeScanner. initScanner kontroluje přítomnost BLE přes hasSystemFeature, zapnutý Bluetooth a úspěšné získání skeneru. startBasicScan spouští skenování bez nastavení a filtrů — detekuje všechna BLE zařízení v dosahu. handleResult analyzuje ScanResult: BluetoothDevice (název, adresa), RSSI (úroveň signálu), scanRecord (reklamní data).
ScanSettings — třída pro konfiguraci BLE skenování. Hlavní parametr — režim skenování (scanMode), který určuje kompromis mezi spotřebou energie a zpožděním detekce. ScanSettings.Builder umožňuje konfigurovat: scanMode, callbackType (CALLBACK_TYPE_ALL_MATCHES, CALLBACK_TYPE_FIRST_MATCH, CALLBACK_TYPE_MATCH_LOST), matchMode (MATCH_MODE_AGGRESSIVE, MATCH_MODE_STICKY), reportDelay (zpoždění dávkového odesílání) a phy (PHY_LE_1M, PHY_LE_2M, PHY_LE_CODED).
Tři režimy skenování: SCAN_MODE_LOW_POWER (0) — skenování na pozadí s nízkou spotřebou energie, zpoždění detekce několik sekund. SCAN_MODE_BALANCED (1) — vyvážený režim pro většinu scénářů. SCAN_MODE_LOW_LATENCY (2) — minimální zpoždění detekce (přibližně 100 ms), maximální spotřeba energie. Pro aktivní vyhledávání zařízení použijte LOW_LATENCY, pro monitorování na pozadí — LOW_POWER.
reportDelay — zpoždění v milisekundách před hromadným odesláním výsledků. Pokud reportDelay = 0, výsledky se odesílají ihned po detekci. Pokud > 0, Android hromadí výsledky a odesílá dávku přes onBatchScanResults. Dávkové odesílání snižuje počet callback volání a snižuje spotřebu energie, vhodné pro skenování na pozadí s nízkou prioritou.
// Konfigurace ScanSettings pro různé scénáře
class ScanSettingsProvider {
// 1. Rychlé skenování (aktivní vyhledávání)
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. Energeticky účinné skenování (monitorování na pozadí)
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) // dávka každé 2 sekundy
.build()
}
// 3. BLE Long Range skenování (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. Skenování pouze na 2M PHY (BLE 5.0+)
fun highSpeedScan(): ScanSettings {
return ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setPhy(ScanSettings.PHY_LE_2M)
.build()
}
}
Třída ScanSettingsProvider obsahuje typické konfigurace. lowLatencyScan — pro UI skenování (vyhledávání "tady a teď"). lowPowerScan — pro monitorování na pozadí s dávkou každé 2 sekundy a callbackType FIRST_MATCH (aktivuje se pouze při první detekci). longRangeScan používá PHY_LE_CODED (BLE Long Range, až 1 km). highSpeedScan — PHY_LE_2M (2 Mbit/s, pouze BLE 5.0+ zařízení).
ScanFilter — třída pro filtrování výsledků BLE skenování. Bez filtru BluetoothLeScanner vrací všechna BLE zařízení v dosahu — v hustém BLE prostředí to jsou stovky paketů za minutu. ScanFilter zužuje výsledky na potřebná zařízení, snižuje spotřebu energie a zatížení aplikace. Filtry se aplikují na úrovni Bluetooth stacku — nevhodné pakety jsou odmítnuty před doručením aplikaci.
Typy filtrů: setServiceUuid — UUID služby (povinně plný 128-bitový formát). setDeviceName — podřetězec názvu zařízení (rozlišuje malá a velká písmena, přesná shoda podřetězce). setDeviceAddress — přesná MAC adresa. setManufacturerData — data výrobce (ID společnosti + maska). Pro jedno skenování lze nastavit několik filtrů — zařízení musí odpovídat všem (AND logika). Pro OR logiku spusťte několik skenování.
// Vytvoření ScanFilter pro různé scénáře
class ScanFilterFactory {
// 1. Filtrování podle UUID služby (Heart Rate Monitor)
fun byHeartRateService(): ScanFilter {
return ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
)
.build()
}
// 2. Filtrování podle názvu zařízení ("iBeacon*")
fun byDeviceName(): ScanFilter {
return ScanFilter.Builder()
.setDeviceName("Sensor")
.build()
}
// 3. MAC- (zařízení)
fun byMacAddress(mac: String): ScanFilter {
return ScanFilter.Builder()
.setDeviceAddress(mac)
.build()
}
// 4. Kombinovaný filtr (UUID + )
fun combinedFilter(): List<ScanFilter> {
return listOf(
ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000A001-0000-1000-8000-00805F9B34FB")
)
.setDeviceName("MyDevice")
.build()
)
}
// 5. data výrobce
fun byManufacturer(companyId: Int, data: ByteArray, mask: ByteArray): ScanFilter {
return ScanFilter.Builder()
.setManufacturerData(companyId, data, mask)
.build()
}
}
Třída ScanFilterFactory ukazuje všechny typy filtrů. byHeartRateService filtruje zařízení se službou pulsu 0x180D. byDeviceName najde zařízení obsahující "Sensor" v názvu (Apple doporučuje unikátní názvy pro filtrování). byMacAddress — přesné vyhledávání konkrétního zařízení. combinedFilter — AND filtr podle UUID a názvu. byManufacturer — filtr podle dat výrobce (např. pro iBeacon se používá company ID Apple 0x004C).
ScanCallback — abstraktní třída pro příjem výsledků BLE skenování. Obsahuje tři metody: onScanResult — jednotlivý výsledek (typ callbacku, ScanResult), onBatchScanResults — dávka výsledků pro reportDelay > 0, onScanFailed — kód chyby. Všechny metody jsou volány na hlavním vlákně Androidu (main thread). Pro dlouhé zpracování v onScanResult použijte korutiny nebo HandlerThread.
ScanResult obsahuje: BluetoothDevice device (zařízení), int rssi (úroveň signálu v dBm), ScanRecord scanRecord (reklamní data), long timestampNanos (čas detekce od spuštění systému). ScanRecord poskytuje: getServiceData() — UUID + vlastní data, getManufacturerSpecificData() — data výrobce, getAdvertiseFlags() — BLE příznaky. Typ callbacku (callbackType) udává: CALLBACK_TYPE_ALL_MATCHES — shoda s filtrem, CALLBACK_TYPE_FIRST_MATCH — první detekce, CALLBACK_TYPE_MATCH_LOST — ztráta zařízení.
Kódy chyb onScanFailed: SCAN_FAILED_ALREADY_STARTED (1) — skenování již bylo spuštěno, SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2) — registrace aplikace v Bluetooth stacku se nezdařila, SCAN_FAILED_INTERNAL_ERROR (3) — interní chyba stacku, SCAN_FAILED_FEATURE_UNSUPPORTED (4) — BLE skenování není na zařízení podporováno.
// Plné výsledky skenování a zpracování chyb
class ScanResultHandler {
private val results = mutableListOf<ScanResult>()
val scanCallback = object : ScanCallback() {
// 1. Jednotlivý výsledek
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
}
// Přidat do seznamu (deduplikace podle adresy)
val existingIndex = results.indexOfFirst {
it.device.address == result.device.address
}
if (existingIndex >= 0) {
results[existingIndex] = result // aktualizovat RSSI
} else {
results.add(result)
}
// Extrahovat data z reklamního paketu
val record = result.scanRecord
val serviceData = record?.serviceData
val manufacturerData = record?.manufacturerSpecificData
print("Found: ${result.device.name ?: "N/A"}, RSSI: ${result.rssi}")
}
// 2. Dávkové výsledky (reportDelay > 0)
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
results?.let { batch ->
print("Batch: ${batch.size} devices")
}
}
// 3. Chyba skenování
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}")
}
}
Třída ScanResultHandler zpracovává všechny typy callbacků BluetoothLeScanner. onScanResult aktualizuje seznam zařízení s deduplikací podle MAC adresy — RSSI se aktualizuje pro již nalezená zařízení. CALLBACK_TYPE_MATCH_LOST signalizuje ztrátu zařízení (odstranění ze seznamu). onBatchScanResults zpracovává dávkové výsledky pro reportDelay > 0. onScanFailed mapuje kódy chyb na čitelná hlášení — klíčové pro ladění BLE skenování.
Skenování PendingIntent — mechanismus BluetoothLeScanner pro BLE skenování fungující i když je aplikace na pozadí (s omezeními Android 8+). Místo ScanCallback se používá PendingIntent, který při detekci BLE zařízení odesílá Broadcast do systémového BroadcastReceiver. To umožňuje aplikaci přijímat oznámení o BLE zařízeních, aniž by byla v paměti (systém vytvoří proces při přijetí broadcastu).
Omezení skenování na pozadí: Na Android 8+ (API 26) jsou služby na pozadí omezeny — skenování PendingIntent toto omezení obchází přes BroadcastReceiver, který systém může spustit při přijetí BLE události. Na Android 10+ (API 29) je BLE skenování na pozadí dále omezeno politikami úspory energie výrobců (Xiaomi, Huawei, Samsung blokují BLE operace na pozadí). Pro kritické BLE scénáře je vyžadováno oznámení s foreground service.
// BLE skenování na pozadí přes PendingIntent
class BackgroundBLEScanner(private val context: Context) {
private val scanner: BluetoothLeScanner? by lazy {
val adapter = BluetoothAdapter.getDefaultAdapter()
adapter?.bluetoothLeScanner
}
fun startBackgroundScan() {
// Vytvořte PendingIntent pro BroadcastReceiver
val intent = Intent(context, BLEBroadcastReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// Nastavení skenování na pozadí
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
.setMatchMode(ScanSettings.MATCH_MODE_STICKY)
.build()
// Spusťte skenování na pozadí
scanner?.startScan(
null, // filtry
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) {
// Získejte výsledky skenování
val results = BluetoothLeScanner.getPendingIntentScanResults(intent)
results?.let { scanResults ->
for (result in scanResults) {
// Odešlete oznámení uživateli
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)
}
}
Třída BackgroundBLEScanner spouští BLE skenování na pozadí přes PendingIntent. startBackgroundScan vytváří PendingIntent, který při detekci BLE zařízení odesílá Broadcast do BLEBroadcastReceiver. BroadcastReceiver extrahuje ScanResult přes getPendingIntentScanResults() a může zobrazit oznámení nebo odeslat data na server. Tento přístup funguje i když aplikace byla ukončena systémem — Android spouští BroadcastReceiver při přijetí broadcastu.
Plný příklad BLE skeneru v Kotlin, který používá BluetoothLeScanner s ScanSettings, ScanFilter a ScanCallback pro hledání Heart Rate Monitor zařízení. Skener zobrazuje seznam nalezených zařízení s RSSI a UUID služeb, s možností připojení přes BluetoothGatt.
// Plný BLE skener s korutinami v 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 {
// Zkontrolujte stav Bluetooth
if (adapter?.isEnabled != true) {
close(IllegalStateException("Bluetooth disabled"))
return@callbackFlow
}
// Konfigurace skenování
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"))
}
}
// Spusťte skenování
scanner?.startScan(filters, settings, callback)
// Automatické zastavení po době
delay(duration)
scanner?.stopScan(callback)
close()
}.flowOn(Dispatchers.IO)
fun stop() {
scanner?.stopScan(object : ScanCallback() {})
}
}
class BLEException(message: String) : Exception(message)
Třída DeviceScanner používá Kotlin Flow (callbackFlow) pro reaktivní BLE skenování. Skenování se spouští s nastavením LOW_LATENCY a filtrem podle UUID Heart Rate Service. Výsledky jsou emitovány přes onScanResult do Flow. Automatické zastavení po zadané době (10 sekund výchozí). FlowOn(Dispatchers.IO) přesouvá BLE operace do vlákna na pozadí. Tento přístup umožňuje použití BLE skenování v MVVM architektuře přes viewModelScope.launch a collect.
Často kladené otázky
BluetoothLeScanner — třída Androidu (API 21+) pro BLE skenování. Získává se přes BluetoothAdapter.getBluetoothLeScanner(). Podporuje tři režimy skenování (LOW_POWER, BALANCED, LOW_LATENCY), filtrování podle UUID, názvu a MAC adresy, dávkové výsledky a PendingIntent pro skenování na pozadí. Nahrazuje zastaralou metodu BluetoothAdapter.startLeScan().
SCAN_MODE_LOW_POWER — režim na pozadí se zpožděním detekce 5–10 sekund, minimální spotřeba energie. SCAN_MODE_LOW_LATENCY — aktivní režim se zpožděním přibližně 100 ms, maximální spotřeba energie. SCAN_MODE_BALANCED — kompromis (~2 sekundy zpoždění). Pro UI skenování použijte LOW_LATENCY, pro monitorování na pozadí — LOW_POWER s PendingIntent.
Příčiny: Bluetooth vypnutý (zkontrolujte adapter.isEnabled), neudělená oprávnění (BLUETOOTH_SCAN na API 31+, ACCESS_FINE_LOCATION na API 23–30), scanner = null (adaptér není dostupný), zařízení mimo dosah nebo použit nesprávný filtr. Zkontrolujte také onScanFailed — kód chyby ukáže příčinu: SCAN_FAILED_ALREADY_STARTED (1) nebo SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2).
Použijte PendingIntent verzi startScan() — předejte PendingIntent místo ScanCallback. Při detekci BLE zařízení Android odešle Broadcast do BroadcastReceiver, který může být spuštěn systémem i když je aplikace na pozadí. Pro Android 8+ přidejte BroadcastReceiver do manifestu. Na Android 10+ zohledněte omezení úspory energie výrobců.
BluetoothLeScanner nemá limit na počet detekovatelných zařízení — omezení závisí na BLE saturaci prostředí. V kanceláři může být 20–50 aktivních BLE zařízení, v obchodním centru stovky. Pro filtrování použijte ScanFilter (podle UUID, názvu). Bez filtrování zpracovávejte výsledky asynchronně — onScanResult může být volán desítkykrát za sekundu.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také