BluetoothLeScanner — Android клас за сканиране на Bluetooth Low Energy устройства, достъпен от API 21 (Android 5.0). BluetoothLeScanner замени остарелия метод startLeScan на BluetoothAdapter, предоставяйки гъвкаво API с настройка на сканирането (ScanSettings), филтриране (ScanFilter) и поддръжка на фонов режим (PendingIntent). Инстанцията се получава чрез BluetoothAdapter.getBluetoothLeScanner(). Според Android Developers, 2026, BluetoothLeScanner поддържа три режима на консумация на енергия и позволява сканиране на BLE рекламни пакети с филтриране по UUID на услугата, име на устройството или MAC адрес.
Основни положения
BluetoothLeScanner — системен клас за управление на BLE сканиране на Android. За разлика от BluetoothAdapter.startLeScan(), който приема прост LeScanCallback, BluetoothLeScanner предоставя обектно-ориентирано API с настройки, филтри и разширена обработка на грешки. Класът се появи в API 21 (Android 5.0) заедно с поддръжка на BLE 4.2 и остава основният начин за BLE сканиране на всички съвременни версии на Android.
Получаването на инстанция на BluetoothLeScanner се извършва чрез BluetoothAdapter.getBluetoothLeScanner(). Методът връща null, ако Bluetooth адаптерът не е наличен (Bluetooth е изключен или устройството не поддържа BLE). Преди получаване проверете BluetoothAdapter.isEnabled() и наличието на FEATURE_BLUETOOTH_LE чрез PackageManager. След получаване на скенера можете да стартирате сканиране във всяка нишка — Android сам планира BLE операциите на вътрешната нишка на Bluetooth стека.
// Получаване на 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 {
// Проверете наличността на BLE
if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
return false
}
// Проверете дали Bluetooth е включен
if (adapter?.isEnabled != true) {
return false
}
// Вземете скенер
scanner = adapter?.bluetoothLeScanner
return scanner != null
}
// Проверете наличността на скенер
val isAvailable: Boolean
get() = scanner != null
// Стартирайте основно сканиране без филтри
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}")
}
}
Класът BLEScannerManager демонстрира безопасно получаване и инициализация на BluetoothLeScanner. initScanner проверява наличието на BLE чрез hasSystemFeature, включен Bluetooth и успешно получаване на скенера. startBasicScan стартира сканиране без настройки и филтри — открива всички BLE устройства в обхват. handleResult анализира ScanResult: BluetoothDevice (име, адрес), RSSI (ниво на сигнала), scanRecord (рекламни данни).
ScanSettings — клас за конфигурация на BLE сканиране. Основният параметър е режимът на сканиране (scanMode), който определя компромиса между консумацията на енергия и забавянето на откриване. ScanSettings.Builder позволява конфигуриране на: scanMode, callbackType (CALLBACK_TYPE_ALL_MATCHES, CALLBACK_TYPE_FIRST_MATCH, CALLBACK_TYPE_MATCH_LOST), matchMode (MATCH_MODE_AGGRESSIVE, MATCH_MODE_STICKY), reportDelay (забавяне на групово изпращане) и phy (PHY_LE_1M, PHY_LE_2M, PHY_LE_CODED).
Три режима на сканиране: SCAN_MODE_LOW_POWER (0) — фоново сканиране с ниска консумация на енергия, забавяне на откриване няколко секунди. SCAN_MODE_BALANCED (1) — балансиран режим за повечето сценарии. SCAN_MODE_LOW_LATENCY (2) — минимално забавяне на откриване (около 100 ms), максимална консумация на енергия. За активно търсене на устройства използвайте LOW_LATENCY, за фонов мониторинг — LOW_POWER.
reportDelay — забавяне в милисекунди преди груповото изпращане на резултати. Ако reportDelay = 0, резултатите се изпращат веднага след откриване. Ако > 0, Android натрупва резултатите и изпраща партида чрез onBatchScanResults. Груповото изпращане намалява броя на callback извикванията и намалява консумацията на енергия, подходящо за фоново сканиране с нисък приоритет.
// Конфигурация на ScanSettings за различни сценарии
class ScanSettingsProvider {
// 1. Бързо сканиране (активно търсене)
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. Енергийно ефективно сканиране (фонов мониторинг)
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) // партида на всеки 2 секунди
.build()
}
// 3. 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. Сканиране само на 2M PHY (BLE 5.0+)
fun highSpeedScan(): ScanSettings {
return ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setPhy(ScanSettings.PHY_LE_2M)
.build()
}
}
Класът ScanSettingsProvider съдържа типични конфигурации. lowLatencyScan — за UI сканиране (търсене "тук и сега"). lowPowerScan — за фонов мониторинг с партида на всеки 2 секунди и callbackType FIRST_MATCH (активира се само при първото откриване). longRangeScan използва PHY_LE_CODED (BLE Long Range, до 1 km). highSpeedScan — PHY_LE_2M (2 Mbit/s, само BLE 5.0+ устройства).
ScanFilter — клас за филтриране на резултати от BLE сканиране. Без филтър BluetoothLeScanner връща всички BLE устройства в обхват — в гъста BLE среда това са стотици пакети в минута. ScanFilter стеснява резултатите до необходимите устройства, намалявайки консумацията на енергия и натоварването на приложението. Филтрите се прилагат на ниво Bluetooth стек — неподходящите пакети се отхвърлят преди доставяне до приложението.
Типове филтри: setServiceUuid — UUID на услугата (задължително пълен 128-битов формат). setDeviceName — подниз от името на устройството (чувствителен към главни и малки букви, точно съвпадение на подниз). setDeviceAddress — точен MAC адрес. setManufacturerData — данни на производителя (ID на компания + маска). За едно сканиране могат да се зададат няколко филтъра — устройството трябва да отговаря на всички (AND логика). За OR логика стартирайте няколко сканирания.
// Създаване на ScanFilter за различни сценарии
class ScanFilterFactory {
// 1. Филтър по UUID на услугата (Heart Rate Monitor)
fun byHeartRateService(): ScanFilter {
return ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
)
.build()
}
// 2. Филтър по име на устройство ("iBeacon*")
fun byDeviceName(): ScanFilter {
return ScanFilter.Builder()
.setDeviceName("Sensor")
.build()
}
// 3. MAC- (устройства)
fun byMacAddress(mac: String): ScanFilter {
return ScanFilter.Builder()
.setDeviceAddress(mac)
.build()
}
// 4. Комбиниран филтър (UUID + )
fun combinedFilter(): List<ScanFilter> {
return listOf(
ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000A001-0000-1000-8000-00805F9B34FB")
)
.setDeviceName("MyDevice")
.build()
)
}
// 5. данни на производителя
fun byManufacturer(companyId: Int, data: ByteArray, mask: ByteArray): ScanFilter {
return ScanFilter.Builder()
.setManufacturerData(companyId, data, mask)
.build()
}
}
Класът ScanFilterFactory показва всички типове филтри. byHeartRateService филтрира устройства с услуга пулс 0x180D. byDeviceName намира устройства, съдържащи "Sensor" в името (Apple препоръчва уникални имена за филтриране). byMacAddress — точно търсене на конкретно устройство. combinedFilter — AND филтър по UUID и име. byManufacturer — филтър по данни на производителя (например за iBeacon се използва company ID Apple 0x004C).
ScanCallback — абстрактен клас за получаване на резултати от BLE сканиране. Съдържа три метода: onScanResult — единичен резултат (тип callback, ScanResult), onBatchScanResults — партида резултати за reportDelay > 0, onScanFailed — код за грешка. Всички методи се извикват на основната нишка на Android (main thread). За дълга обработка в onScanResult използвайте корутини или HandlerThread.
ScanResult съдържа: BluetoothDevice device (устройство), int rssi (ниво на сигнала в dBm), ScanRecord scanRecord (рекламни данни), long timestampNanos (време на откриване от стартиране на системата). ScanRecord предоставя: getServiceData() — UUID + персонализирани данни, getManufacturerSpecificData() — данни на производителя, getAdvertiseFlags() — BLE флагове. Типът callback (callbackType) показва: CALLBACK_TYPE_ALL_MATCHES — съвпадение с филтър, CALLBACK_TYPE_FIRST_MATCH — първо откриване, CALLBACK_TYPE_MATCH_LOST — загуба на устройство.
Кодове за грешка на onScanFailed: SCAN_FAILED_ALREADY_STARTED (1) — сканирането вече е стартирано, SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2) — регистрацията на приложението в Bluetooth стека не успя, SCAN_FAILED_INTERNAL_ERROR (3) — вътрешна грешка на стека, SCAN_FAILED_FEATURE_UNSUPPORTED (4) — BLE сканирането не се поддържа на устройството.
// Пълни резултати от сканиране и обработка на грешки
class ScanResultHandler {
private val results = mutableListOf<ScanResult>()
val scanCallback = object : ScanCallback() {
// 1. Единичен резултат
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
}
// Добавяне към списък (дедупликация по адрес)
val existingIndex = results.indexOfFirst {
it.device.address == result.device.address
}
if (existingIndex >= 0) {
results[existingIndex] = result // актуализиране на RSSI
} else {
results.add(result)
}
// Извличане на данни от рекламен пакет
val record = result.scanRecord
val serviceData = record?.serviceData
val manufacturerData = record?.manufacturerSpecificData
print("Found: ${result.device.name ?: "N/A"}, RSSI: ${result.rssi}")
}
// 2. Партидни резултати (reportDelay > 0)
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
results?.let { batch ->
print("Batch: ${batch.size} devices")
}
}
// 3. Грешка при сканиране
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}")
}
}
Класът ScanResultHandler обработва всички типове callback на BluetoothLeScanner. onScanResult актуализира списъка с устройства с дедупликация по MAC адрес — RSSI се актуализира за вече намерени устройства. CALLBACK_TYPE_MATCH_LOST сигнализира за загуба на устройство (премахване от списъка). onBatchScanResults обработва партидни резултати за reportDelay > 0. onScanFailed преобразува кодовете за грешка в четими съобщения — критично за отстраняване на грешки при BLE сканиране.
PendingIntent сканиране — механизъм на BluetoothLeScanner за BLE сканиране, което работи дори когато приложението е на заден план (с ограничения на Android 8+). Вместо ScanCallback се използва PendingIntent, който изпраща Broadcast до системния BroadcastReceiver при откриване на BLE устройство. Това позволява на приложението да получава известия за BLE устройства, без да е в паметта (системата създава процес при получаване на broadcast).
Ограничения на фоновото сканиране: На Android 8+ (API 26) фоновите услуги са ограничени — PendingIntent сканирането заобикаля това ограничение чрез BroadcastReceiver, който системата може да стартира при получаване на BLE събитие. На Android 10+ (API 29) фоновото BLE сканиране е допълнително ограничено от политиките за пестене на енергия на производителите (Xiaomi, Huawei, Samsung блокират фонови BLE операции). За критични BLE сценарии се изисква известие с foreground service.
// Фоново BLE сканиране чрез PendingIntent
class BackgroundBLEScanner(private val context: Context) {
private val scanner: BluetoothLeScanner? by lazy {
val adapter = BluetoothAdapter.getDefaultAdapter()
adapter?.bluetoothLeScanner
}
fun startBackgroundScan() {
// Създайте PendingIntent за BroadcastReceiver
val intent = Intent(context, BLEBroadcastReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// Настройки за фоново сканиране
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
.setMatchMode(ScanSettings.MATCH_MODE_STICKY)
.build()
// Стартирайте фоново сканиране
scanner?.startScan(
null, // филтри
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) {
// Вземете резултати от сканиране
val results = BluetoothLeScanner.getPendingIntentScanResults(intent)
results?.let { scanResults ->
for (result in scanResults) {
// Изпратете известие до потребителя
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)
}
}
Класът BackgroundBLEScanner стартира фоново BLE сканиране чрез PendingIntent. startBackgroundScan създава PendingIntent, който при откриване на BLE устройство изпраща Broadcast до BLEBroadcastReceiver. BroadcastReceiver извлича ScanResult чрез getPendingIntentScanResults() и може да покаже известие или да изпрати данни на сървъра. Този подход работи дори ако приложението е било прекратено от системата — Android стартира BroadcastReceiver при получаване на broadcast.
Пълен пример за BLE скенер на Kotlin, който използва BluetoothLeScanner с ScanSettings, ScanFilter и ScanCallback за намиране на Heart Rate Monitor устройства. Скенерът показва списък с намерени устройства с RSSI и UUID на услугите, с възможност за свързване чрез BluetoothGatt.
// Пълен BLE скенер с корутини на 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
if (adapter?.isEnabled != true) {
close(IllegalStateException("Bluetooth disabled"))
return@callbackFlow
}
// Конфигурация на сканиране
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"))
}
}
// Стартирайте сканиране
scanner?.startScan(filters, settings, callback)
// Автоматично спиране след време
delay(duration)
scanner?.stopScan(callback)
close()
}.flowOn(Dispatchers.IO)
fun stop() {
scanner?.stopScan(object : ScanCallback() {})
}
}
class BLEException(message: String) : Exception(message)
Класът DeviceScanner използва Kotlin Flow (callbackFlow) за реактивно BLE сканиране. Сканирането се стартира с настройки LOW_LATENCY и филтър по UUID Heart Rate Service. Резултатите се изпращат чрез onScanResult към Flow. Автоматично спиране след зададена продължителност (10 секунди по подразбиране). FlowOn(Dispatchers.IO) премества BLE операциите във фонова нишка. Този подход позволява използването на BLE сканиране в MVVM архитектура чрез viewModelScope.launch и collect.
Често задавани въпроси
BluetoothLeScanner — Android клас (API 21+) за BLE сканиране. Получава се чрез BluetoothAdapter.getBluetoothLeScanner(). Поддържа три режима на сканиране (LOW_POWER, BALANCED, LOW_LATENCY), филтриране по UUID, име и MAC адрес, партидни резултати и PendingIntent за фоново сканиране. Заменя остарелия метод BluetoothAdapter.startLeScan().
SCAN_MODE_LOW_POWER — фонов режим със забавяне на откриване 5–10 секунди, минимална консумация на енергия. SCAN_MODE_LOW_LATENCY — активен режим със забавяне около 100 ms, максимална консумация на енергия. SCAN_MODE_BALANCED — компромис (~2 секунди забавяне). За UI сканиране използвайте LOW_LATENCY, за фонов мониторинг — LOW_POWER с PendingIntent.
Причини: Bluetooth е изключен (проверете adapter.isEnabled), не са получени разрешения (BLUETOOTH_SCAN на API 31+, ACCESS_FINE_LOCATION на API 23–30), scanner = null (адаптерът не е наличен), устройството е извън обхват или се използва неправилен филтър. Проверете също onScanFailed — кодът за грешка ще посочи причината: SCAN_FAILED_ALREADY_STARTED (1) или SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2).
Използвайте PendingIntent версията на startScan() — предайте PendingIntent вместо ScanCallback. При откриване на BLE устройство Android изпраща Broadcast до BroadcastReceiver, който може да бъде стартиран от системата дори когато приложението е на заден план. За Android 8+ добавете BroadcastReceiver в манифеста. На Android 10+ вземете предвид ограниченията за пестене на енергия на производителите.
BluetoothLeScanner няма ограничение за броя на откриваемите устройства — ограничението зависи от BLE наситеността на средата. В офис може да има 20–50 активни BLE устройства, в търговски център — стотици. За филтриране използвайте ScanFilter (по UUID, име). Без филтриране обработвайте резултатите асинхронно — onScanResult може да бъде извикван десетки пъти в секунда.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също