BluetoothLeScanner — clasa Android pentru scanarea dispozitivelor Bluetooth Low Energy, disponibilă începând cu API 21 (Android 5.0). BluetoothLeScanner a înlocuit metoda învechită startLeScan de pe BluetoothAdapter, oferind o API flexibilă cu configurare a scanării (ScanSettings), filtrare (ScanFilter) și suport pentru modul fundal (PendingIntent). Instanța se obține prin BluetoothAdapter.getBluetoothLeScanner(). Potrivit Android Developers, 2026, BluetoothLeScanner suportă trei moduri de consum energetic și permite scanarea pachetelor publicitare BLE cu filtrare după UUID serviciu, nume dispozitiv sau adresă MAC.
Principalele
BluetoothLeScanner — clasa de sistem pentru gestionarea scanării BLE pe Android. Spre deosebire de BluetoothAdapter.startLeScan(), care primește un simplu callback LeScanCallback, BluetoothLeScanner oferă o API orientată pe obiecte cu setări, filtre și gestionare extinsă a erorilor. Clasa a apărut în API 21 (Android 5.0) odată cu suportul BLE 4.2 și rămâne principala metodă de scanare BLE pe toate versiunile moderne de Android.
Obținerea instanței BluetoothLeScanner se realizează prin BluetoothAdapter.getBluetoothLeScanner(). Metoda returnează null dacă adaptorul Bluetooth este indisponibil (Bluetooth dezactivat sau dispozitivul nu suportă BLE). Înainte de obținere, verificați BluetoothAdapter.isEnabled() și prezența FEATURE_BLUETOOTH_LE prin PackageManager. După obținerea scanerului, puteți porni scanarea în orice thread — Android planifică el însuși operațiile BLE pe threadul intern al stivei Bluetooth.
// Obținerea 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 {
// Verificați disponibilitatea BLE
if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
return false
}
// Verificați Bluetooth activat
if (adapter?.isEnabled != true) {
return false
}
// Obțineți scanerul
scanner = adapter?.bluetoothLeScanner
return scanner != null
}
// Verificați disponibilitatea scanerului
val isAvailable: Boolean
get() = scanner != null
// Porniți scanarea de bază fără filtre
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}")
}
}
Clasa BLEScannerManager demonstrează obținerea și inițializarea sigură a BluetoothLeScanner. initScanner verifică prezența BLE prin hasSystemFeature, Bluetooth activat și obținerea cu succes a scanerului. startBasicScan pornește scanarea fără setări și filtre — detectează toate dispozitivele BLE în raza de acțiune. handleResult analizează ScanResult: BluetoothDevice (nume, adresă), RSSI (nivel semnal), scanRecord (date publicitare).
ScanSettings — clasa pentru configurarea scanării BLE. Parametrul principal — modul de scanare (scanMode), care determină compromisul între consumul de energie și întârzierea detectării. ScanSettings.Builder permite configurarea: scanMode, callbackType (CALLBACK_TYPE_ALL_MATCHES, CALLBACK_TYPE_FIRST_MATCH, CALLBACK_TYPE_MATCH_LOST), matchMode (MATCH_MODE_AGGRESSIVE, MATCH_MODE_STICKY), reportDelay (întârziere trimitere lot) și phy (PHY_LE_1M, PHY_LE_2M, PHY_LE_CODED).
Trei moduri de scanare: SCAN_MODE_LOW_POWER (0) — scanare în fundal cu consum redus de energie, întârziere detectare câteva secunde. SCAN_MODE_BALANCED (1) — mod echilibrat pentru majoritatea scenariilor. SCAN_MODE_LOW_LATENCY (2) — întârziere minimă de detectare (aproximativ 100 ms), consum maxim de energie. Pentru căutarea activă a dispozitivelor utilizați LOW_LATENCY, pentru monitorizare în fundal — LOW_POWER.
reportDelay — întârzierea în milisecunde înainte de trimiterea în grup a rezultatelor. Dacă reportDelay = 0, rezultatele sunt trimise imediat după detectare. Dacă > 0, Android acumulează rezultatele și trimite lotul prin onBatchScanResults. Trimiterea în loturi reduce numărul de apeluri callback și consumul de energie, fiind potrivită pentru scanarea în fundal cu prioritate scăzută.
// Configurarea ScanSettings pentru diferite scenarii
class ScanSettingsProvider {
// 1. Scanare rapidă (căutare activă)
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. Scanare eficientă energetic (monitorizare fundal)
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) // lot la fiecare 2 secunde
.build()
}
// 3. Scanare 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. Scanare doar pe 2M PHY (BLE 5.0+)
fun highSpeedScan(): ScanSettings {
return ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setPhy(ScanSettings.PHY_LE_2M)
.build()
}
}
Clasa ScanSettingsProvider conține configurații tipice. lowLatencyScan — pentru scanare UI (căutare „aici și acum”). lowPowerScan — pentru monitorizare în fundal cu lot la fiecare 2 secunde și callbackType FIRST_MATCH (se declanșează doar la prima detectare). longRangeScan folosește PHY_LE_CODED (BLE Long Range, până la 1 km). highSpeedScan — PHY_LE_2M (2 Mbit/s, doar dispozitive BLE 5.0+).
ScanFilter — clasa pentru filtrarea rezultatelor scanării BLE. Fără filtru, BluetoothLeScanner returnează toate dispozitivele BLE în raza de acțiune — într-un mediu BLE dens, aceasta înseamnă sute de pachete pe minut. ScanFilter restrânge rezultatele la dispozitivele necesare, reducând consumul de energie și încărcarea aplicației. Filtrele sunt aplicate la nivelul stivei Bluetooth — pachetele nepotrivite sunt respinse înainte de livrarea către aplicație.
Tipuri de filtre: setServiceUuid — UUID serviciu (obligatoriu format 128-bit complet). setDeviceName — subșir al numelui dispozitivului (sensibil la majuscule, potrivire exactă a subșirului). setDeviceAddress — adresă MAC exactă. setManufacturerData — date producător (ID companie + mască). Pentru o singură scanare se pot seta mai multe filtre — dispozitivul trebuie să corespundă tuturor (logică AND). Pentru logică OR, porniți mai multe scanări.
// Crearea ScanFilter pentru diferite scenarii
class ScanFilterFactory {
// 1. Filtrare după UUID serviciu (Heart Rate Monitor)
fun byHeartRateService(): ScanFilter {
return ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
)
.build()
}
// 2. Filtrare după nume dispozitiv („iBeacon*")
fun byDeviceName(): ScanFilter {
return ScanFilter.Builder()
.setDeviceName("Sensor")
.build()
}
// 3. MAC- (dispozitive)
fun byMacAddress(mac: String): ScanFilter {
return ScanFilter.Builder()
.setDeviceAddress(mac)
.build()
}
// 4. Filtru combinat (UUID + )
fun combinedFilter(): List<ScanFilter> {
return listOf(
ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000A001-0000-1000-8000-00805F9B34FB")
)
.setDeviceName("MyDevice")
.build()
)
}
// 5. date producător
fun byManufacturer(companyId: Int, data: ByteArray, mask: ByteArray): ScanFilter {
return ScanFilter.Builder()
.setManufacturerData(companyId, data, mask)
.build()
}
}
Clasa ScanFilterFactory arată toate tipurile de filtre. byHeartRateService filtrează dispozitivele cu serviciu puls 0x180D. byDeviceName găsește dispozitive care conțin „Sensor” în nume (Apple recomandă nume unice pentru filtrare). byMacAddress — căutarea exactă a unui dispozitiv specific. combinedFilter — filtru AND după UUID și nume. byManufacturer — filtru după datele producătorului (de exemplu, pentru iBeacon se folosește company ID Apple 0x004C).
ScanCallback — clasa abstractă pentru primirea rezultatelor scanării BLE. Conține trei metode: onScanResult — rezultat unic (tip callback, ScanResult), onBatchScanResults — lot de rezultate pentru reportDelay > 0, onScanFailed — cod de eroare. Toate metodele sunt apelate pe threadul principal Android (main thread). Pentru procesare lungă în onScanResult, utilizați corutine sau HandlerThread.
ScanResult conține: BluetoothDevice device (dispozitiv), int rssi (nivel semnal în dBm), ScanRecord scanRecord (date publicitare), long timestampNanos (timpul detectării de la pornirea sistemului). ScanRecord oferă: getServiceData() — UUID + date personalizate, getManufacturerSpecificData() — date producător, getAdvertiseFlags() — flaguri BLE. Tipul callback (callbackType) indică: CALLBACK_TYPE_ALL_MATCHES — potrivire cu filtrul, CALLBACK_TYPE_FIRST_MATCH — prima detectare, CALLBACK_TYPE_MATCH_LOST — pierderea dispozitivului.
Coduri de eroare onScanFailed: SCAN_FAILED_ALREADY_STARTED (1) — scanarea este deja pornită, SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2) — înregistrarea aplicației în stiva Bluetooth a eșuat, SCAN_FAILED_INTERNAL_ERROR (3) — eroare internă a stivei, SCAN_FAILED_FEATURE_UNSUPPORTED (4) — scanarea BLE nu este suportată pe dispozitiv.
// Rezultate complete ale scanării și gestionarea erorilor
class ScanResultHandler {
private val results = mutableListOf<ScanResult>()
val scanCallback = object : ScanCallback() {
// 1. Rezultat unic
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
}
// Adăugați la listă (deduplicare după adresă)
val existingIndex = results.indexOfFirst {
it.device.address == result.device.address
}
if (existingIndex >= 0) {
results[existingIndex] = result // actualizați RSSI
} else {
results.add(result)
}
// Extrageți datele din pachetul publicitar
val record = result.scanRecord
val serviceData = record?.serviceData
val manufacturerData = record?.manufacturerSpecificData
print("Found: ${result.device.name ?: "N/A"}, RSSI: ${result.rssi}")
}
// 2. Rezultate în lot (reportDelay > 0)
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
results?.let { batch ->
print("Batch: ${batch.size} devices")
}
}
// 3. Eroare de scanare
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}")
}
}
Clasa ScanResultHandler procesează toate tipurile de callback BluetoothLeScanner. onScanResult actualizează lista dispozitivelor cu deduplicare după adresa MAC — RSSI este actualizat pentru dispozitivele deja găsite. CALLBACK_TYPE_MATCH_LOST semnalează pierderea dispozitivului (ștergere din listă). onBatchScanResults procesează rezultatele în loturi pentru reportDelay > 0. onScanFailed mapează codurile de eroare în mesaje lizibile — esențial pentru depanarea scanării BLE.
Scanarea PendingIntent — mecanismul BluetoothLeScanner pentru scanarea BLE care funcționează chiar și când aplicația este în fundal (cu restricțiile Android 8+). În loc de ScanCallback, se folosește PendingIntent care trimite un Broadcast către BroadcastReceiver-ul sistemului la detectarea unui dispozitiv BLE. Aceasta permite aplicației să primească notificări despre dispozitivele BLE fără a fi în memorie (sistemul creează procesul la primirea broadcast-ului).
Restricții ale scanării în fundal: Pe Android 8+ (API 26) serviciile în fundal sunt limitate — scanarea PendingIntent ocolește această restricție prin BroadcastReceiver, pe care sistemul îl poate porni la primirea unui eveniment BLE. Pe Android 10+ (API 29) scanarea BLE în fundal este suplimentar limitată de politicile de economisire a energiei ale producătorilor (Xiaomi, Huawei, Samsung blochează operațiile BLE în fundal). Pentru scenarii critice BLE, este necesară o notificare cu foreground service.
// Scanare BLE în fundal prin PendingIntent
class BackgroundBLEScanner(private val context: Context) {
private val scanner: BluetoothLeScanner? by lazy {
val adapter = BluetoothAdapter.getDefaultAdapter()
adapter?.bluetoothLeScanner
}
fun startBackgroundScan() {
// Creați PendingIntent pentru BroadcastReceiver
val intent = Intent(context, BLEBroadcastReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// Setări scanare în fundal
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
.setMatchMode(ScanSettings.MATCH_MODE_STICKY)
.build()
// Porniți scanarea în fundal
scanner?.startScan(
null, // filtre
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) {
// Obțineți rezultatele scanării
val results = BluetoothLeScanner.getPendingIntentScanResults(intent)
results?.let { scanResults ->
for (result in scanResults) {
// Trimiteți notificare utilizatorului
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)
}
}
Clasa BackgroundBLEScanner pornește scanarea BLE în fundal prin PendingIntent. startBackgroundScan creează un PendingIntent care, la detectarea unui dispozitiv BLE, trimite un Broadcast către BLEBroadcastReceiver. BroadcastReceiver extrage ScanResult prin getPendingIntentScanResults() și poate afișa o notificare sau trimite date pe server. Această abordare funcționează chiar dacă aplicația a fost încheiată de sistem — Android pornește BroadcastReceiver la primirea broadcast-ului.
Exemplu complet de scanner BLE în Kotlin, care folosește BluetoothLeScanner cu ScanSettings, ScanFilter și ScanCallback pentru a găsi dispozitive Heart Rate Monitor. Scannerul arată o listă a dispozitivelor găsite cu RSSI și UUID-uri servicii, cu posibilitatea de conectare prin BluetoothGatt.
// Scanner BLE complet cu corutine în 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 {
// Verificați starea Bluetooth
if (adapter?.isEnabled != true) {
close(IllegalStateException("Bluetooth disabled"))
return@callbackFlow
}
// Configurarea scanării
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"))
}
}
// Porniți scanarea
scanner?.startScan(filters, settings, callback)
// Oprire automată după durată
delay(duration)
scanner?.stopScan(callback)
close()
}.flowOn(Dispatchers.IO)
fun stop() {
scanner?.stopScan(object : ScanCallback() {})
}
}
class BLEException(message: String) : Exception(message)
Clasa DeviceScanner folosește Kotlin Flow (callbackFlow) pentru scanarea reactivă BLE. Scanarea este pornită cu setările LOW_LATENCY și filtrul după UUID Heart Rate Service. Rezultatele sunt emise prin onScanResult în Flow. Oprire automată după durata specificată (10 secunde implicit). FlowOn(Dispatchers.IO) mută operațiile BLE în threadul de fundal. Această abordare permite utilizarea scanării BLE în arhitectura MVVM prin viewModelScope.launch și collect.
Întrebări frecvente
BluetoothLeScanner — clasa Android (API 21+) pentru scanare BLE. Se obține prin BluetoothAdapter.getBluetoothLeScanner(). Suportă trei moduri de scanare (LOW_POWER, BALANCED, LOW_LATENCY), filtrare după UUID, nume și adresă MAC, rezultate în loturi și PendingIntent pentru scanare în fundal. Înlocuiește metoda învechită BluetoothAdapter.startLeScan().
SCAN_MODE_LOW_POWER — mod fundal cu întârziere de detectare 5–10 secunde, consum minim de energie. SCAN_MODE_LOW_LATENCY — mod activ cu întârziere de aproximativ 100 ms, consum maxim de energie. SCAN_MODE_BALANCED — compromis (~2 secunde întârziere). Pentru scanare UI utilizați LOW_LATENCY, pentru monitorizare în fundal — LOW_POWER cu PendingIntent.
Cauze: Bluetooth dezactivat (verificați adapter.isEnabled), permisiuni neacordate (BLUETOOTH_SCAN pe API 31+, ACCESS_FINE_LOCATION pe API 23–30), scanner = null (adaptor indisponibil), dispozitiv în afara razei sau filtru incorect utilizat. Verificați și onScanFailed — codul de eroare va indica cauza: SCAN_FAILED_ALREADY_STARTED (1) sau SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2).
Utilizați versiunea PendingIntent a startScan() — transmiteți PendingIntent în loc de ScanCallback. La detectarea unui dispozitiv BLE, Android trimite un Broadcast către BroadcastReceiver, care poate fi pornit de sistem chiar dacă aplicația este în fundal. Pentru Android 8+, adăugați BroadcastReceiver în manifest. Pe Android 10+, luați în considerare restricțiile de economisire a energiei ale producătorilor.
BluetoothLeScanner nu are o limită pentru numărul de dispozitive detectabile — limitarea depinde de saturația BLE a mediului. Într-un birou pot fi 20–50 de dispozitive BLE active, într-un centru comercial — sute. Pentru filtrare, utilizați ScanFilter (după UUID, nume). Fără filtrare, procesați rezultatele asincron — onScanResult poate fi apelat de zeci de ori pe secundă.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și