BluetoothLeScanner — Android-klasse voor het scannen van Bluetooth Low Energy-apparaten, beschikbaar vanaf API 21 (Android 5.0). BluetoothLeScanner heeft de verouderde startLeScan-methode op BluetoothAdapter vervangen en biedt een flexibele API met scanconfiguratie (ScanSettings), filtering (ScanFilter) en ondersteuning voor de achtergrondmodus (PendingIntent). De instantie wordt verkregen via BluetoothAdapter.getBluetoothLeScanner(). Volgens Android Developers, 2026 ondersteunt BluetoothLeScanner drie energieverbruikmodi en kunt u BLE-advertentiepakketten scannen met filtering op service-UUID, apparaatnaam of MAC-adres.
Belangrijkste
BluetoothLeScanner — systeemklasse voor het beheren van BLE-scannen op Android. In tegenstelling tot BluetoothAdapter.startLeScan(), die een eenvoudige LeScanCallback accepteert, biedt BluetoothLeScanner een objectgeoriënteerde API met instellingen, filters en uitgebreide foutafhandeling. De klasse verscheen in API 21 (Android 5.0) samen met ondersteuning voor BLE 4.2 en blijft de belangrijkste methode voor BLE-scannen op alle moderne versies van Android.
Het verkrijgen van een BluetoothLeScanner-instantie gebeurt via BluetoothAdapter.getBluetoothLeScanner(). De methode retourneert null als de Bluetooth-adapter niet beschikbaar is (Bluetooth uitgeschakeld of apparaat ondersteunt geen BLE). Controleer vóór het verkrijgen BluetoothAdapter.isEnabled() en de aanwezigheid van FEATURE_BLUETOOTH_LE via PackageManager. Nadat de scanner is verkregen, kunt u het scannen in elke thread starten — Android plant BLE-bewerkingen zelf op de interne thread van de Bluetooth-stack.
// BluetoothLeScanner verkrijgen
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 {
// Controleer BLE-beschikbaarheid
if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
return false
}
// Controleer of Bluetooth is ingeschakeld
if (adapter?.isEnabled != true) {
return false
}
// Scanner verkrijgen
scanner = adapter?.bluetoothLeScanner
return scanner != null
}
// Controleer scanner beschikbaarheid
val isAvailable: Boolean
get() = scanner != null
// Standaard scan starten zonder filters
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}")
}
}
De klasse BLEScannerManager demonstreert veilige verkrijging en initialisatie van BluetoothLeScanner. initScanner controleert de aanwezigheid van BLE via hasSystemFeature, ingeschakelde Bluetooth en succesvolle verkrijging van de scanner. startBasicScan start het scannen zonder instellingen en filters — detecteert alle BLE-apparaten binnen bereik. handleResult analyseert ScanResult: BluetoothDevice (naam, adres), RSSI (signaalniveau), scanRecord (advertentiegegevens).
ScanSettings — klasse voor BLE-scanconfiguratie. De belangrijkste parameter is de scanmodus (scanMode), die de afweging tussen energieverbruik en detectievertraging bepaalt. ScanSettings.Builder maakt configuratie mogelijk van: scanMode, callbackType (CALLBACK_TYPE_ALL_MATCHES, CALLBACK_TYPE_FIRST_MATCH, CALLBACK_TYPE_MATCH_LOST), matchMode (MATCH_MODE_AGGRESSIVE, MATCH_MODE_STICKY), reportDelay (vertraging voor batchverzending) en phy (PHY_LE_1M, PHY_LE_2M, PHY_LE_CODED).
Drie scanmodi: SCAN_MODE_LOW_POWER (0) — achtergrondscannen met laag energieverbruik, detectievertraging enkele seconden. SCAN_MODE_BALANCED (1) — gebalanceerde modus voor de meeste scenario’s. SCAN_MODE_LOW_LATENCY (2) — minimale detectievertraging (ongeveer 100 ms), maximaal energieverbruik. Gebruik LOW_LATENCY voor actief zoeken naar apparaten, LOW_POWER voor achtergrondmonitoring.
reportDelay — vertraging in milliseconden vóór het groepsgewijs verzenden van resultaten. Als reportDelay = 0, worden resultaten onmiddellijk na detectie verzonden. Indien > 0, accumuleert Android de resultaten en verzendt een batch via onBatchScanResults. Batchverzending vermindert het aantal callback-aanroepen en verlaagt het energieverbruik, geschikt voor achtergrondscannen met lage prioriteit.
// ScanSettings-configuratie voor verschillende scenario’s
class ScanSettingsProvider {
// 1. Snelle scan (actief zoeken)
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. Energiezuinige scan (achtergrondmonitoring)
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) // batch elke 2 seconden
.build()
}
// 3. BLE Long Range-scan (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. Alleen scannen op 2M PHY (BLE 5.0+)
fun highSpeedScan(): ScanSettings {
return ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setPhy(ScanSettings.PHY_LE_2M)
.build()
}
}
De klasse ScanSettingsProvider bevat typische configuraties. lowLatencyScan — voor UI-scannen (zoeken ‘hier en nu’). lowPowerScan — voor achtergrondmonitoring met een batch elke 2 seconden en callbackType FIRST_MATCH (wordt alleen bij de eerste detectie geactiveerd). longRangeScan gebruikt PHY_LE_CODED (BLE Long Range, tot 1 km). highSpeedScan — PHY_LE_2M (2 Mbit/s, alleen BLE 5.0+-apparaten).
ScanFilter — klasse voor het filteren van BLE-scanresultaten. Zonder filter retourneert BluetoothLeScanner alle BLE-apparaten binnen bereik — in een dichte BLE-omgeving zijn dat honderden pakketten per minuut. ScanFilter beperkt de resultaten tot de benodigde apparaten, waardoor energieverbruik en belasting van de app worden verminderd. Filters worden toegepast op het niveau van de Bluetooth-stack — ongepaste pakketten worden afgewezen voordat ze aan de app worden geleverd.
Filtertypen: setServiceUuid — service-UUID (verplicht volledig 128-bit formaat). setDeviceName — subtekenreeks van de apparaatnaam (hoofdlettergevoelig, exacte overeenkomst van subtekenreeks). setDeviceAddress — exact MAC-adres. setManufacturerData — fabrikantgegevens (bedrijfs-ID + masker). Voor één scan kunnen meerdere filters worden ingesteld — het apparaat moet aan alle filters voldoen (AND-logica). Voor OR-logica start u meerdere scans.
// ScanFilter maken voor verschillende scenario’s
class ScanFilterFactory {
// 1. Filteren op service-UUID (Heart Rate Monitor)
fun byHeartRateService(): ScanFilter {
return ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
)
.build()
}
// 2. Filteren op apparaatnaam („iBeacon*")
fun byDeviceName(): ScanFilter {
return ScanFilter.Builder()
.setDeviceName("Sensor")
.build()
}
// 3. MAC- (apparaten)
fun byMacAddress(mac: String): ScanFilter {
return ScanFilter.Builder()
.setDeviceAddress(mac)
.build()
}
// 4. Gecombineerd filter (UUID + )
fun combinedFilter(): List<ScanFilter> {
return listOf(
ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000A001-0000-1000-8000-00805F9B34FB")
)
.setDeviceName("MyDevice")
.build()
)
}
// 5. fabrikantgegevens
fun byManufacturer(companyId: Int, data: ByteArray, mask: ByteArray): ScanFilter {
return ScanFilter.Builder()
.setManufacturerData(companyId, data, mask)
.build()
}
}
De klasse ScanFilterFactory toont alle filtertypen. byHeartRateService filtert apparaten met pulsservice 0x180D. byDeviceName vindt apparaten die ‘Sensor’ in de naam bevatten (Apple adviseert unieke namen voor filtering). byMacAddress — exact zoeken naar een specifiek apparaat. combinedFilter — AND-filter op UUID en naam. byManufacturer — filter op fabrikantgegevens (bijvoorbeeld voor iBeacon wordt company ID Apple 0x004C gebruikt).
ScanCallback — abstracte klasse voor het ontvangen van BLE-scanresultaten. Bevat drie methoden: onScanResult — enkel resultaat (callback-type, ScanResult), onBatchScanResults — batch resultaten voor reportDelay > 0, onScanFailed — foutcode. Alle methoden worden aangeroepen op de hoofdthread van Android (main thread). Voor langdurige verwerking in onScanResult gebruikt u coroutines of HandlerThread.
ScanResult bevat: BluetoothDevice device (apparaat), int rssi (signaalniveau in dBm), ScanRecord scanRecord (advertentiegegevens), long timestampNanos (detectietijd sinds opstarten van het systeem). ScanRecord biedt: getServiceData() — UUID + aangepaste gegevens, getManufacturerSpecificData() — fabrikantgegevens, getAdvertiseFlags() — BLE-vlaggen. Het callback-type (callbackType) geeft aan: CALLBACK_TYPE_ALL_MATCHES — overeenkomst met filter, CALLBACK_TYPE_FIRST_MATCH — eerste detectie, CALLBACK_TYPE_MATCH_LOST — verlies van apparaat.
Foutcodes van onScanFailed: SCAN_FAILED_ALREADY_STARTED (1) — scannen is al gestart, SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2) — registratie van de app in de Bluetooth-stack is mislukt, SCAN_FAILED_INTERNAL_ERROR (3) — interne fout van de stack, SCAN_FAILED_FEATURE_UNSUPPORTED (4) — BLE-scannen wordt niet ondersteund op het apparaat.
// Volledige scanresultaten en foutafhandeling
class ScanResultHandler {
private val results = mutableListOf<ScanResult>()
val scanCallback = object : ScanCallback() {
// 1. Enkel resultaat
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
}
// Toevoegen aan lijst (deduplicatie op adres)
val existingIndex = results.indexOfFirst {
it.device.address == result.device.address
}
if (existingIndex >= 0) {
results[existingIndex] = result // RSSI bijwerken
} else {
results.add(result)
}
// Gegevens extraheren uit advertentiepakket
val record = result.scanRecord
val serviceData = record?.serviceData
val manufacturerData = record?.manufacturerSpecificData
print("Found: ${result.device.name ?: "N/A"}, RSSI: ${result.rssi}")
}
// 2. Batchresultaten (reportDelay > 0)
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
results?.let { batch ->
print("Batch: ${batch.size} devices")
}
}
// 3. Scanfout
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}")
}
}
De klasse ScanResultHandler verwerkt alle callback-typen van BluetoothLeScanner. onScanResult werkt de lijst met apparaten bij met deduplicatie op MAC-adres — RSSI wordt bijgewerkt voor reeds gevonden apparaten. CALLBACK_TYPE_MATCH_LOST signaleert verlies van apparaat (verwijdering uit lijst). onBatchScanResults verwerkt batchresultaten voor reportDelay > 0. onScanFailed kent foutcodes toe aan leesbare meldingen — essentieel voor het debuggen van BLE-scannen.
PendingIntent-scannen — het mechanisme van BluetoothLeScanner voor BLE-scannen dat werkt, zelfs wanneer de app op de achtergrond is (met beperkingen van Android 8+). In plaats van ScanCallback wordt PendingIntent gebruikt, die bij detectie van een BLE-apparaat een Broadcast naar de systeem-BroadcastReceiver stuurt. Hierdoor kan de app meldingen over BLE-apparaten ontvangen zonder in het geheugen te zijn (het systeem creëert een proces bij ontvangst van de broadcast).
Beperkingen van achtergrondscannen: Op Android 8+ (API 26) zijn achtergrondservices beperkt — PendingIntent-scannen omzeilt deze beperking via BroadcastReceiver, die het systeem kan starten bij ontvangst van een BLE-gebeurtenis. Op Android 10+ (API 29) wordt BLE-achtergrondscannen verder beperkt door energiebesparingsbeleid van fabrikanten (Xiaomi, Huawei, Samsung blokkeren BLE-achtergrondbewerkingen). Voor kritieke BLE-scenario’s is een melding met foreground service vereist.
// BLE-achtergrondscannen via PendingIntent
class BackgroundBLEScanner(private val context: Context) {
private val scanner: BluetoothLeScanner? by lazy {
val adapter = BluetoothAdapter.getDefaultAdapter()
adapter?.bluetoothLeScanner
}
fun startBackgroundScan() {
// Maak PendingIntent voor BroadcastReceiver
val intent = Intent(context, BLEBroadcastReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// Achtergrondscaninstellingen
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
.setMatchMode(ScanSettings.MATCH_MODE_STICKY)
.build()
// Start achtergrondscan
scanner?.startScan(
null, // filters
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) {
// Scanresultaten ophalen
val results = BluetoothLeScanner.getPendingIntentScanResults(intent)
results?.let { scanResults ->
for (result in scanResults) {
// Melding naar gebruiker sturen
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)
}
}
De klasse BackgroundBLEScanner start BLE-achtergrondscannen via PendingIntent. startBackgroundScan maakt een PendingIntent die bij detectie van een BLE-apparaat een Broadcast naar BLEBroadcastReceiver stuurt. BroadcastReceiver extraheert ScanResult via getPendingIntentScanResults() en kan een melding tonen of gegevens naar de server sturen. Deze aanpak werkt zelfs als de app door het systeem is beëindigd — Android start BroadcastReceiver bij ontvangst van de broadcast.
Volledig voorbeeld van een BLE-scanner in Kotlin, die BluetoothLeScanner met ScanSettings, ScanFilter en ScanCallback gebruikt om Heart Rate Monitor-apparaten te vinden. De scanner toont een lijst van gevonden apparaten met RSSI en service-UUID’s, met de mogelijkheid om verbinding te maken via BluetoothGatt.
// Volledige BLE-scanner met coroutines in 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 {
// Controleer Bluetooth-status
if (adapter?.isEnabled != true) {
close(IllegalStateException("Bluetooth disabled"))
return@callbackFlow
}
// Scanconfiguratie
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"))
}
}
// Scannen starten
scanner?.startScan(filters, settings, callback)
// Automatisch stoppen na tijdsduur
delay(duration)
scanner?.stopScan(callback)
close()
}.flowOn(Dispatchers.IO)
fun stop() {
scanner?.stopScan(object : ScanCallback() {})
}
}
class BLEException(message: String) : Exception(message)
De klasse DeviceScanner gebruikt Kotlin Flow (callbackFlow) voor reactief BLE-scannen. Het scannen wordt gestart met LOW_LATENCY-instellingen en een filter op Heart Rate Service-UUID. Resultaten worden via onScanResult naar de Flow geëmit. Automatisch stoppen na de opgegeven duur (10 seconden standaard). FlowOn(Dispatchers.IO) verplaatst BLE-bewerkingen naar de achtergrondthread. Deze aanpak maakt het mogelijk BLE-scannen te gebruiken in MVVM-architectuur via viewModelScope.launch en collect.
Veelgestelde vragen
BluetoothLeScanner — Android-klasse (API 21+) voor BLE-scannen. Wordt verkregen via BluetoothAdapter.getBluetoothLeScanner(). Ondersteunt drie scanmodi (LOW_POWER, BALANCED, LOW_LATENCY), filtering op UUID, naam en MAC-adres, batchresultaten en PendingIntent voor achtergrondscannen. Vervangt de verouderde methode BluetoothAdapter.startLeScan().
SCAN_MODE_LOW_POWER — achtergrondmodus met detectievertraging van 5–10 seconden, minimaal energieverbruik. SCAN_MODE_LOW_LATENCY — actieve modus met een vertraging van ongeveer 100 ms, maximaal energieverbruik. SCAN_MODE_BALANCED — compromis (~2 seconden vertraging). Gebruik LOW_LATENCY voor UI-scannen en LOW_POWER met PendingIntent voor achtergrondmonitoring.
Oorzaken: Bluetooth uitgeschakeld (controleer adapter.isEnabled), geen machtigingen (BLUETOOTH_SCAN op API 31+, ACCESS_FINE_LOCATION op API 23–30), scanner = null (adapter niet beschikbaar), apparaat buiten bereik of onjuist filter gebruikt. Controleer ook onScanFailed — de foutcode geeft de oorzaak aan: SCAN_FAILED_ALREADY_STARTED (1) of SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2).
Gebruik de PendingIntent-versie van startScan() — geef PendingIntent door in plaats van ScanCallback. Bij detectie van een BLE-apparaat stuurt Android een Broadcast naar BroadcastReceiver, die door het systeem kan worden gestart, zelfs als de app op de achtergrond is. Voeg voor Android 8+ BroadcastReceiver toe aan het manifest. Houd op Android 10+ rekening met energiebesparingsbeperkingen van fabrikanten.
BluetoothLeScanner heeft geen limiet op het aantal detecteerbare apparaten — de beperking hangt af van de BLE-verzadiging van de omgeving. In een kantoor kunnen 20–50 actieve BLE-apparaten zijn, in een winkelcentrum honderden. Gebruik ScanFilter (op UUID, naam) voor filtering. Zonder filtering verwerkt u resultaten asynchroon — onScanResult kan tientallen keren per seconde worden aangeroepen.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook