BluetoothLeScanner ist eine Android-Klasse zum Scannen von Bluetooth Low Energy-Geräten, verfügbar ab API 21 (Android 5.0). BluetoothLeScanner ersetzte die veraltete Methode startLeScan auf BluetoothAdapter und bietet eine flexible API mit Scan-Konfiguration (ScanSettings), Filterung (ScanFilter) und Hintergrundmodus-Unterstützung (PendingIntent). Die Instanz wird über BluetoothAdapter.getBluetoothLeScanner() bezogen. Laut Android Developers, 2026 unterstützt BluetoothLeScanner drei Energiemodi und ermöglicht das Scannen von BLE-Werbe paketen mit Filterung nach Dienst-UUID, Gerätename oder MAC-Adresse.
Wichtige Punkte
BluetoothLeScanner ist eine Systemklasse zur Verwaltung des BLE-Scannens auf Android. Im Gegensatz zu BluetoothAdapter.startLeScan(), das einen einfachen LeScanCallback akzeptiert, bietet BluetoothLeScanner eine objektorientierte API mit Einstellungen, Filtern und erweiterter Fehlerbehandlung. Diese Klasse wurde in API 21 (Android 5.0) zusammen mit BLE 4.2-Unterstützung eingeführt und bleibt die primäre BLE-Scan-Methode auf allen modernen Android-Versionen.
Das Erhalten einer BluetoothLeScanner-Instanz erfolgt über BluetoothAdapter.getBluetoothLeScanner(). Die Methode gibt null zurück, wenn der Bluetooth-Adapter nicht verfügbar ist (Bluetooth deaktiviert oder Gerät unterstützt kein BLE). Überprüfen Sie vor dem Erhalt BluetoothAdapter.isEnabled() und das Vorhandensein von FEATURE_BLUETOOTH_LE über PackageManager. Nach Erhalt des Scanners kann das Scannen in jedem Thread gestartet werden — Android plant BLE-Operationen auf einem internen Bluetooth-Stack-Thread.
// BluetoothLeScanner erhalten
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 {
// BLE-Verfügbarkeit prüfen
if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
return false
}
// Bluetooth aktiviert prüfen
if (adapter?.isEnabled != true) {
return false
}
// Scanner erhalten
scanner = adapter?.bluetoothLeScanner
return scanner != null
}
// Scanner-Verfügbarkeit prüfen
val isAvailable: Boolean
get() = scanner != null
// Basis-Scan ohne Filter starten
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}")
}
}
Die Klasse BLEScannerManager demonstriert das sichere Erhalten und Initialisieren von BluetoothLeScanner. initScanner prüft die BLE-Verfügbarkeit über hasSystemFeature, aktiviertes Bluetooth und erfolgreichen Scanner-Erhalt. startBasicScan startet das Scannen ohne Einstellungen und Filter — erkennt alle BLE-Geräte in Reichweite. handleResult analysiert ScanResult: BluetoothDevice (Name, Adresse), RSSI (Signalstärke), scanRecord (Werbedaten).
ScanSettings ist eine Klasse zur Konfiguration des BLE-Scannens. Der Hauptparameter ist der Scan-Modus (scanMode), der den Kompromiss zwischen Energieverbrauch und Erkennungslatenz bestimmt. ScanSettings.Builder ermöglicht die Konfiguration von: scanMode, callbackType (CALLBACK_TYPE_ALL_MATCHES, CALLBACK_TYPE_FIRST_MATCH, CALLBACK_TYPE_MATCH_LOST), matchMode (MATCH_MODE_AGGRESSIVE, MATCH_MODE_STICKY), reportDelay (Stapel lieferverzögerung) und phy (PHY_LE_1M, PHY_LE_2M, PHY_LE_CODED).
Drei Scan-Modi: SCAN_MODE_LOW_POWER (0) — Hintergrundscannen mit niedrigem Energieverbrauch, Erkennungsverzögerung von mehreren Sekunden. SCAN_MODE_BALANCED (1) — Ausgeglichener Modus für die meisten Szenarien. SCAN_MODE_LOW_LATENCY (2) — Minimale Erkennungsverzögerung (ca. 100 ms), maximaler Energieverbrauch. Für aktive Gerätesuche verwenden Sie LOW_LATENCY, für Hintergrundüberwachung LOW_POWER.
reportDelay — Verzögerung in Millisekunden vor der Stapelauslieferung von Ergebnissen. Bei reportDelay = 0 werden Ergebnisse sofort bei Erkennung gesendet. Bei > 0 sammelt Android Ergebnisse und sendet einen Stapel über onBatchScanResults. Die Stapelauslieferung reduziert Callback-Aufrufe und Energieverbrauch, geeignet für Hintergrundscannen mit niedriger Priorität.
// ScanSettings-Konfiguration für verschiedene Szenarien
class ScanSettingsProvider {
// 1. Schneller Scan (aktive Suche)
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. Energieeffizienter Scan (Hintergrundüberwachung)
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) // Stapel alle 2 Sekunden
.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. Nur auf 2M PHY scannen (BLE 5.0+)
fun highSpeedScan(): ScanSettings {
return ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setPhy(ScanSettings.PHY_LE_2M)
.build()
}
}
Die Klasse ScanSettingsProvider enthält typische Konfigurationen. lowLatencyScan — für UI-Scannen (Suche „jetzt und hier“). lowPowerScan — für Hintergrundüberwachung mit Stapel alle 2 Sekunden und callbackType FIRST_MATCH (nur bei erster Erkennung ausgelöst). longRangeScan verwendet PHY_LE_CODED (BLE Long Range, bis 1 km). highSpeedScan — PHY_LE_2M (2 Mbit/s, nur BLE 5.0+-Geräte).
ScanFilter ist eine Klasse zum Filtern von BLE-Scan-Ergebnissen. Ohne Filter gibt BluetoothLeScanner alle BLE-Geräte in Reichweite zurück — in einer dichten BLE-Umgebung können das hunderte Pakete pro Minute sein. ScanFilter grenzt die Ergebnisse auf die gewünschten Geräte ein und reduziert so Energieverbrauch und App-Last. Filter werden auf Bluetooth-Stack-Ebene angewendet — ungeeignete Pakete werden verworfen, bevor sie die App erreichen.
Filtertypen: setServiceUuid — Dienst-UUID (vollständiges 128-Bit-Format erforderlich). setDeviceName — Teilzeichenfolge des Gerätenamens (Groß-/Kleinschreibung, genaue Teilzeichenfolgenübereinstimmung). setDeviceAddress — genaue MAC-Adresse. setManufacturerData — Herstellerdaten (Unternehmens-ID + Maske). Für einen einzelnen Scan können mehrere Filter gesetzt werden — das Gerät muss allen entsprechen (UND-Logik). Für ODER-Logik starten Sie mehrere Scans.
// ScanFilter-Erstellung für verschiedene Szenarien
class ScanFilterFactory {
// 1. Nach Dienst-UUID filtern (Herzfrequenzmonitor)
fun byHeartRateService(): ScanFilter {
return ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
)
.build()
}
// 2. Nach Gerätenamen filtern ( "iBeacon*")
fun byDeviceName(): ScanFilter {
return ScanFilter.Builder()
.setDeviceName("Sensor")
.build()
}
// 3. MAC- ( Geräte)
fun byMacAddress(mac: String): ScanFilter {
return ScanFilter.Builder()
.setDeviceAddress(mac)
.build()
}
// 4. Kombinierter Filter (UUID + )
fun combinedFilter(): List<ScanFilter> {
return listOf(
ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000A001-0000-1000-8000-00805F9B34FB")
)
.setDeviceName("MyDevice")
.build()
)
}
// 5. Herstellerdaten
fun byManufacturer(companyId: Int, data: ByteArray, mask: ByteArray): ScanFilter {
return ScanFilter.Builder()
.setManufacturerData(companyId, data, mask)
.build()
}
}
Die Klasse ScanFilterFactory zeigt alle Filtertypen. byHeartRateService filtert Geräte mit dem Herzfrequenzdienst 0x180D. byDeviceName findet Geräte, die „Sensor“ im Namen enthalten (Apple empfiehlt eindeutige Namen für die Filterung). byMacAddress — genaue Suche nach einem bestimmten Gerät. combinedFilter — UND-Filter nach UUID und Name. byManufacturer — Filter nach Herstellerdaten (z.B. für iBeacon wird die Apple-Unternehmens-ID 0x004C verwendet).
ScanCallback ist eine abstrakte Klasse zum Empfangen von BLE-Scan-Ergebnissen. Sie enthält drei Methoden: onScanResult — Einzelergebnis (Callback-Typ, ScanResult), onBatchScanResults — Stapelergebnisse für reportDelay > 0, onScanFailed — Fehlercode. Alle Methoden werden auf dem Android-Hauptthread aufgerufen. Für lange Verarbeitung in onScanResult verwenden Sie Coroutinen oder HandlerThread.
ScanResult enthält: BluetoothDevice device, int rssi (Signalpegel in dBm), ScanRecord scanRecord (Werbedaten), long timestampNanos (Erkennungszeit seit Systemstart). ScanRecord bietet: getServiceData() — UUID + benutzerdefinierte Daten, getManufacturerSpecificData() — Herstellerdaten, getAdvertiseFlags() — BLE-Flags. Der Callback-Typ gibt an: CALLBACK_TYPE_ALL_MATCHES — Übereinstimmung mit Filter, CALLBACK_TYPE_FIRST_MATCH — erste Erkennung, CALLBACK_TYPE_MATCH_LOST — Gerät verloren.
Fehlercodes von onScanFailed: SCAN_FAILED_ALREADY_STARTED (1) — Scannen bereits gestartet, SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2) — App-Registrierung im Bluetooth-Stack fehlgeschlagen, SCAN_FAILED_INTERNAL_ERROR (3) — interner Stack-Fehler, SCAN_FAILED_FEATURE_UNSUPPORTED (4) — BLE-Scannen auf diesem Gerät nicht unterstützt.
// Vollständige Scan-Ergebnisse und Fehlerbehandlung
class ScanResultHandler {
private val results = mutableListOf<ScanResult>()
val scanCallback = object : ScanCallback() {
// 1. Einzelergebnis
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
}
// Zur Liste hinzufügen (nach Adresse deduplizieren)
val existingIndex = results.indexOfFirst {
it.device.address == result.device.address
}
if (existingIndex >= 0) {
results[existingIndex] = result // RSSI aktualisieren
} else {
results.add(result)
}
// Daten aus Werbepaket extrahieren
val record = result.scanRecord
val serviceData = record?.serviceData
val manufacturerData = record?.manufacturerSpecificData
print("Found: ${result.device.name ?: "N/A"}, RSSI: ${result.rssi}")
}
// 2. Stapelergebnisse (reportDelay > 0)
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
results?.let { batch ->
print("Batch: ${batch.size} devices")
}
}
// 3. Scan-Fehler
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}")
}
}
Die Klasse ScanResultHandler behandelt alle BluetoothLeScanner-Callback-Typen. onScanResult aktualisiert die Geräteliste mit Deduplizierung nach MAC-Adresse — RSSI wird für bereits gefundene Geräte aktualisiert. CALLBACK_TYPE_MATCH_LOST signalisiert Geräteverlust (Entfernung aus der Liste). onBatchScanResults verarbeitet Stapelergebnisse für reportDelay > 0. onScanFailed ordnet Fehlercodes lesbaren Nachrichten zu — kritisch für das Debuggen von BLE-Scans.
PendingIntent-Scannen ist ein BluetoothLeScanner-Mechanismus für BLE-Scannen, das auch funktioniert, wenn die App im Hintergrund ist (mit Android 8+-Einschränkungen). Statt ScanCallback wird ein PendingIntent verwendet, der bei Erkennung eines BLE-Geräts einen Broadcast an den systemeigenen BroadcastReceiver sendet. Dadurch kann die App BLE-Gerätebenachrichtigungen empfangen, ohne im Speicher zu bleiben (das System erstellt den Prozess beim Empfang des Broadcasts).
Einschränkungen des Hintergrundscannens: Auf Android 8+ (API 26) sind Hintergrunddienste eingeschränkt — PendingIntent-Scannen umgeht diese Einschränkung über BroadcastReceiver, den das System beim Empfang eines BLE-Ereignisses starten kann. Auf Android 10+ (API 29) ist das BLE-Hintergrundscannen zusätzlich durch Hersteller-Energiesparrichtlinien eingeschränkt (Xiaomi, Huawei, Samsung blockieren Hintergrund-BLE-Operationen). Für kritische BLE-Szenarien ist eine Vordergrunddienst-Benachrichtigung erforderlich.
// Hintergrund-BLE-Scannen über PendingIntent
class BackgroundBLEScanner(private val context: Context) {
private val scanner: BluetoothLeScanner? by lazy {
val adapter = BluetoothAdapter.getDefaultAdapter()
adapter?.bluetoothLeScanner
}
fun startBackgroundScan() {
// PendingIntent für BroadcastReceiver erstellen
val intent = Intent(context, BLEBroadcastReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// Hintergrund-Scan-Einstellungen
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
.setMatchMode(ScanSettings.MATCH_MODE_STICKY)
.build()
// Hintergrund-Scan starten
scanner?.startScan(
null, // Filter
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) {
// Scan-Ergebnisse abrufen
val results = BluetoothLeScanner.getPendingIntentScanResults(intent)
results?.let { scanResults ->
for (result in scanResults) {
// Benachrichtigung an Benutzer senden
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)
}
}
Die Klasse BackgroundBLEScanner startet das Hintergrund-BLE-Scannen über PendingIntent. startBackgroundScan erstellt einen PendingIntent, der bei Erkennung eines BLE-Geräts einen Broadcast an BLEBroadcastReceiver sendet. Der BroadcastReceiver extrahiert ScanResult über getPendingIntentScanResults() und kann eine Benachrichtigung anzeigen oder Daten an den Server senden. Dieser Ansatz funktioniert auch, wenn die App vom System beendet wurde — Android startet den BroadcastReceiver beim Empfang des Broadcasts neu.
Vollständiges Beispiel eines BLE-Scanners in Kotlin, der BluetoothLeScanner mit ScanSettings, ScanFilter und ScanCallback verwendet, um Heart Rate Monitor-Geräte zu finden. Der Scanner zeigt eine Liste der gefundenen Geräte mit RSSI und Dienst-UUIDs an, mit der Möglichkeit, über BluetoothGatt zu verbinden.
// Vollständiger BLE-Scanner mit Coroutinen 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 {
// Bluetooth-Status prüfen
if (adapter?.isEnabled != true) {
close(IllegalStateException("Bluetooth disabled"))
return@callbackFlow
}
// Scan-Konfiguration
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)
// Automatischer Stopp nach Zeit
delay(duration)
scanner?.stopScan(callback)
close()
}.flowOn(Dispatchers.IO)
fun stop() {
scanner?.stopScan(object : ScanCallback() {})
}
}
class BLEException(message: String) : Exception(message)
Die Klasse DeviceScanner verwendet Kotlin Flow (callbackFlow) für reaktives BLE-Scannen. Das Scannen wird mit LOW_LATENCY-Einstellungen und einem Heart Rate Service UUID-Filter gestartet. Ergebnisse werden über onScanResult in einen Flow ausgegeben. Automatischer Stopp nach einer bestimmten Dauer (Standard 10 Sekunden). FlowOn(Dispatchers.IO) lagert BLE-Operationen auf einen Hintergrundthread aus. Dieser Ansatz ermöglicht BLE-Scannen in der MVVM-Architektur über viewModelScope.launch und collect.
Häufig gestellte Fragen
BluetoothLeScanner ist eine Android-Klasse (API 21+) für BLE-Scannen. Sie wird über BluetoothAdapter.getBluetoothLeScanner() bezogen. Unterstützt drei Scan-Modi (LOW_POWER, BALANCED, LOW_LATENCY), Filterung nach UUID, Name und MAC-Adresse, Stapelergebnisse und PendingIntent für Hintergrundscannen. Ersetzt die veraltete Methode BluetoothAdapter.startLeScan().
SCAN_MODE_LOW_POWER — Hintergrundmodus mit 5–10 Sekunden Erkennungsverzögerung, minimaler Energieverbrauch. SCAN_MODE_LOW_LATENCY — aktiver Modus mit etwa 100 ms Verzögerung, maximaler Energieverbrauch. SCAN_MODE_BALANCED — Kompromiss (~2 Sekunden Verzögerung). Verwenden Sie LOW_LATENCY für UI-Scannen, LOW_POWER mit PendingIntent für Hintergrundüberwachung.
Ursachen: Bluetooth deaktiviert (prüfen Sie adapter.isEnabled), fehlende Berechtigungen (BLUETOOTH_SCAN ab API 31+, ACCESS_FINE_LOCATION auf API 23–30), scanner = null (Adapter nicht verfügbar), Gerät außerhalb der Reichweite oder falscher Filter. Prüfen Sie auch onScanFailed — der Fehlercode gibt die Ursache an: SCAN_FAILED_ALREADY_STARTED (1) oder SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2).
Verwenden Sie die PendingIntent-Version von startScan() — übergeben Sie einen PendingIntent statt ScanCallback. Wenn ein BLE-Gerät erkannt wird, sendet Android einen Broadcast an den BroadcastReceiver, der vom System gestartet werden kann, auch wenn die App im Hintergrund ist. Für Android 8+ fügen Sie den BroadcastReceiver im Manifest hinzu. Auf Android 10+ beachten Sie die Hersteller-Energiespar einschränkungen.
BluetoothLeScanner hat keine Begrenzung der Anzahl erkennbarer Geräte — die Begrenzung hängt von der BLE-Sättigung der Umgebung ab. Ein Büro kann 20–50 aktive BLE-Geräte haben, ein Einkaufszentrum hunderte. Verwenden Sie ScanFilter (nach UUID, Name) zum Filtern. Ohne Filterung verarbeiten Sie die Ergebnisse asynchron — onScanResult kann dutzende Male pro Sekunde aufgerufen werden.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch