BluetoothLeScanner — что это, методы и BLE-сканирование в Android

Автор: IT Sectr Опубликовано: 2026-07-16 Время чтения: 10 мин

BluetoothLeScanner — класс Android для сканирования Bluetooth Low Energy devices, доступный с API 21 (Android 5.0). BluetoothLeScanner заменил устаревший метод startLeScan на BluetoothAdapter, предоставляя гибкое API с настройкой сканирования (ScanSettings), фильтрацией (ScanFilter) и поддержкой фонового режима (PendingIntent). Экземпляр получается через BluetoothAdapter.getBluetoothLeScanner(). По данным Android Developers, 2026, BluetoothLeScanner поддерживает три режима энергопотребления и позволяет сканировать рекламные пакеты BLE с фильтрацией по UUID сервиса, имени devicesа или MAC-адресу.

Главное

  • BluetoothLeScanner — современное API Android (API 21+) для BLE-сканирования, замена устаревшего startLeScan
  • ScanSettings — настройка режима сканирования: LOW_POWER, BALANCED, LOW_LATENCY и callback type
  • ScanFilter — фильтрация результатов по UUID сервиса, имени devicesа, MAC-адресу, manufacturer data
  • ScanCallback — callback результатов onScanResult, onBatchScanResults и onScanFailed с кодами ошибок
  • PendingIntent — фоновое сканирование через BroadcastReceiver, даже когда приложение в background

Что такое BluetoothLeScanner: суть и получение экземпляра

BluetoothLeScanner — системный класс для управления BLE-сканированием на Android. В отличие от BluetoothAdapter.startLeScan(), который принимает простой callback LeScanCallback, BluetoothLeScanner предоставляет объектный API с настройками, фильтрами и расширенной обработкой ошибок. Класс появился в API 21 (Android 5.0) вместе с поддержкой BLE 4.2 и остаётся основным способом BLE-сканирования на всех современных версиях Android.

Получение экземпляра BluetoothLeScanner выполняется через BluetoothAdapter.getBluetoothLeScanner(). Метод возвращает null, если Bluetooth-адаптер недоступен (Bluetooth disabled или devicesо не поддерживает BLE). Перед получением проверьте BluetoothAdapter.isEnabled() и наличие FEATURE_BLUETOOTH_LE через PackageManager. После получения сканера можно запускать сканирование в любом потоке — Android сам планирует BLE-операции на внутреннем потоке Bluetooth-стека.

kotlin
// Getting 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 {
        // Check BLE availability
        if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            return false
        }

        // Check Bluetooth enabled
        if (adapter?.isEnabled != true) {
            return false
        }

        // Get scanner
        scanner = adapter?.bluetoothLeScanner
        return scanner != null
    }

    // Check scanner availability
    val isAvailable: Boolean
        get() = scanner != null

    // Start basic scan without 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}")
    }
}

Класс BLEScannerManager демонстрирует безопасное получение и инициализацию BluetoothLeScanner. initScanner проверяет наличие BLE через hasSystemFeature, включённый Bluetooth и успешное получение сканера. startBasicScan запускает сканирование без настроек и фильтров — обнаруживает все BLE-devicesа в зоне действия. handleResult разбирает ScanResult: BluetoothDevice (имя, адрес), RSSI (уровень сигнала), scanRecord (рекламные данные).

ScanSettings: режимы сканирования и callback type

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) — balanced режим для большинства сценариев. SCAN_MODE_LOW_LATENCY (2) — минимальная задержка обнаружения (около 100 мс), максимальное энергопотребление. Для активного поиска devices используйте LOW_LATENCY, для фонового мониторинга — LOW_POWER.

reportDelay — задержка в миллисекундах перед групповой отправкой результатов. Если reportDelay = 0, результаты отправляются немедленно при обнаружении. Если > 0, Android накапливает результаты и отправляет батч через onBatchScanResults. Пакетная отправка уменьшает количество вызовов callbackов и снижает энергопотребление, подходит для сканирования в фоне с низким приоритетом.

kotlin
// ScanSettings configuration for different scenarios
class ScanSettingsProvider {

    // 1. Fast scan (active search)
    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. Power efficient scan (background monitoring)
    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 every 2 seconds
            .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. Scan only on 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 км). highSpeedScan — PHY_LE_2M (2 Мбит/с, только BLE 5.0+ devicesа).

ScanFilter: фильтрация BLE-devices по UUID и имени

ScanFilter — класс для фильтрации результатов BLE-сканирования. Без фильтра BluetoothLeScanner возвращает все BLE-devicesа в зоне действия — для плотной BLE-среды это сотни пакетов в минуту. ScanFilter сужает результаты до нужных devices, уменьшая энергопотребление и нагрузку на приложение. Фильтры применяются на уровне Bluetooth-стека — неподходящие пакеты отбрасываются до доставки в приложение.

Типы фильтров: setServiceUuid — UUID сервиса (обязательно полный 128-bit формат). setDeviceName — подстрока имени devicesа (регистрозависимо, точное совпадение подстроки). setDeviceAddress — точный MAC-адрес. setManufacturerData — данные производителя (ID компании + маска). Для одного сканирования можно установить несколько фильтров — devicesо должно соответствовать всем (AND-логика). Для OR-логики запустите несколько сканирований.

kotlin
// ScanFilter creation for different scenarios
class ScanFilterFactory {

    // 1. Filter by service UUID (Heart Rate Monitor)
    fun byHeartRateService(): ScanFilter {
        return ScanFilter.Builder()
            .setServiceUuid(
                ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
            )
            .build()
    }

    // 2. Filter by device name ( "iBeacon*")
    fun byDeviceName(): ScanFilter {
        return ScanFilter.Builder()
            .setDeviceName("Sensor")
            .build()
    }

    // 3.   MAC- ( devices)
    fun byMacAddress(mac: String): ScanFilter {
        return ScanFilter.Builder()
            .setDeviceAddress(mac)
            .build()
    }

    // 4. Combined filter (UUID + )
    fun combinedFilter(): List<ScanFilter> {
        return listOf(
            ScanFilter.Builder()
                .setServiceUuid(
                    ParcelUuid.fromString("0000A001-0000-1000-8000-00805F9B34FB")
                )
                .setDeviceName("MyDevice")
                .build()
        )
    }

    // 5.   manufacturer data
    fun byManufacturer(companyId: Int, data: ByteArray, mask: ByteArray): ScanFilter {
        return ScanFilter.Builder()
            .setManufacturerData(companyId, data, mask)
            .build()
    }
}

Класс ScanFilterFactory показывает все типы фильтров. byHeartRateService отфильтровывает devicesа с сервисом пульса 0x180D. byDeviceName находит devicesа, содержащие "Sensor" в имени (Apple рекомендует уникальные имена для фильтрации). byMacAddress — точный поиск конкретного девайса. combinedFilter — AND-фильтр по UUID и имени. byManufacturer — фильтр по данным производителя (например, для iBeacon используется company ID Apple 0x004C).

ScanCallback: обработка результатов и ошибок сканирования

ScanCallback — абстрактный класс для получения результатов BLE-сканирования. Содержит три метода: onScanResult — единичный результат (тип callbackа, ScanResult), onBatchScanResults — пакет результатов для reportDelay > 0, onScanFailed — код ошибки. Все методы вызываются на главном потоке Android (main thread). Для длительной обработки в onScanResult используйте корутины или HandlerThread.

ScanResult содержит: BluetoothDevice device (devicesо), int rssi (уровень сигнала в dBm), ScanRecord scanRecord (рекламные данные), long timestampNanos (время обнаружения с момента загрузки системы). ScanRecord предоставляет: getServiceData() — UUID + кастомные данные, getManufacturerSpecificData() — данные производителя, getAdvertiseFlags() — BLE-флаги. Тип callbackа (callbackType) указывает: CALLBACK_TYPE_ALL_MATCHES — совпадение with filter, CALLBACK_TYPE_FIRST_MATCH — первое обнаружение, CALLBACK_TYPE_MATCH_LOST — потеря devicesа.

Коды ошибок onScanFailed: SCAN_FAILED_ALREADY_STARTED (1) — сканирование уже запущено, SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2) — регистрация приложения в Bluetooth-стеке не удалась, SCAN_FAILED_INTERNAL_ERROR (3) — внутренняя ошибка стека, SCAN_FAILED_FEATURE_UNSUPPORTED (4) — BLE-сканирование не поддерживается на devicesе.

kotlin
// Full scan results and error handling
class ScanResultHandler {

    private val results = mutableListOf<ScanResult>()

    val scanCallback = object : ScanCallback() {

        // 1. Single result
        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
            }

            // Add to list (dedup by address)
            val existingIndex = results.indexOfFirst {
                it.device.address == result.device.address
            }
            if (existingIndex >= 0) {
                results[existingIndex] = result  // update RSSI
            } else {
                results.add(result)
            }

            // Extract data from advertising packet
            val record = result.scanRecord
            val serviceData = record?.serviceData
            val manufacturerData = record?.manufacturerSpecificData

            print("Found: ${result.device.name ?: "N/A"}, RSSI: ${result.rssi}")
        }

        // 2. Batch results (reportDelay > 0)
        override fun onBatchScanResults(results: MutableList<ScanResult>?) {
            results?.let { batch ->
                print("Batch: ${batch.size} devices")
            }
        }

        // 3. Scan error
        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 обновляет список devices с дедупликацией по MAC-адресу — RSSI обновляется для уже найденных devices. CALLBACK_TYPE_MATCH_LOST сигнализирует о потере devicesа (удаление из списка). onBatchScanResults обрабатывает пакетные результаты для reportDelay > 0. onScanFailed маппит коды ошибок в человекочитаемые сообщения — критично для отладки BLE-сканирования.

PendingIntent: фоновое BLE-сканирование через BroadcastReceiver

PendingIntent-сканирование — механизм BluetoothLeScanner для BLE-сканирования, работающего даже когда приложение в background (с ограничениями Android 8+). Вместо ScanCallback используется PendingIntent, который отправляет Broadcast в системный BroadcastReceiver при обнаружении BLE-devicesа. Это позволяет приложению получать уведомления о BLE-devicesах, не находясь в памяти (система создаёт процесс при получении broadcast).

Ограничения фонового сканирования: На Android 8+ (API 26) фоновые сервисы ограничены — PendingIntent-сканирование обходит это ограничение через BroadcastReceiver, который система может запустить при получении BLE-события. На Android 10+ (API 29) фоновое BLE-сканирование дополнительно ограничено политиками энергосбережения производителей (Xiaomi, Huawei, Samsung блокируют фоновые BLE-операции). Для критических BLE-сценариев требуется уведомление с foreground service.

kotlin
// Background BLE scanning via PendingIntent
class BackgroundBLEScanner(private val context: Context) {

    private val scanner: BluetoothLeScanner? by lazy {
        val adapter = BluetoothAdapter.getDefaultAdapter()
        adapter?.bluetoothLeScanner
    }

    fun startBackgroundScan() {
        // Create PendingIntent for BroadcastReceiver
        val intent = Intent(context, BLEBroadcastReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(
            context,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        // Background scan settings
        val settings = ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
            .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
            .setMatchMode(ScanSettings.MATCH_MODE_STICKY)
            .build()

        // Start background scan
        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) {
        // Get scan results
        val results = BluetoothLeScanner.getPendingIntentScanResults(intent)
        results?.let { scanResults ->
            for (result in scanResults) {
                // Send notification to user
                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-devicesа отправляет Broadcast в BLEBroadcastReceiver. BroadcastReceiver извлекает ScanResult через getPendingIntentScanResults() и может показывать уведомление или отправлять данные на сервер. Такой подход работает даже если приложение было завершено системой — Android перезапускает BroadcastReceiver при получении broadcast.

Пример BLE-сканера на Kotlin с BluetoothLeScanner

Полный пример BLE-сканера на Kotlin, использующего BluetoothLeScanner с ScanSettings, ScanFilter и ScanCallback для поиска devices Heart Rate Monitor. Сканер показывает список найденных devices с RSSI и UUID сервисов, с возможностью подключения через BluetoothGatt.

kotlin
// Full BLE scanner with 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 {
        // Check Bluetooth state
        if (adapter?.isEnabled != true) {
            close(IllegalStateException("Bluetooth disabled"))
            return@callbackFlow
        }

        // Scan configuration
        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"))
            }
        }

        // Start scanning
        scanner?.startScan(filters, settings, callback)

        // Auto-stop after duration
        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. Автоостановка через заданный duration (10 секунд по умолчанию). FlowOn(Dispatchers.IO) выносит BLE-операции в фоновый поток. Такой подход позволяет использовать BLE-сканирование в MVVM-архитектуре через viewModelScope.launch и collect.

Часто задаваемые вопросы

Что такое BluetoothLeScanner в Android?

BluetoothLeScanner — класс Android (API 21+) для BLE-сканирования. Получается через BluetoothAdapter.getBluetoothLeScanner(). Поддерживает три режима сканирования (LOW_POWER, BALANCED, LOW_LATENCY), фильтрацию по UUID, имени и MAC-адресу, пакетные результаты и PendingIntent для фонового сканирования. Заменяет устаревший метод BluetoothAdapter.startLeScan().

Чем отличается LOW_POWER от LOW_LATENCY?

SCAN_MODE_LOW_POWER — фоновый режим с задержкой обнаружения 5–10 секунд, минимальное энергопотребление. SCAN_MODE_LOW_LATENCY — активный режим с задержкой около 100 мс, максимальное энергопотребление. SCAN_MODE_BALANCED — компромисс (~2 секунды задержки). Для UI-сканирования используйте LOW_LATENCY, для фонового мониторинга — LOW_POWER с PendingIntent.

Почему BluetoothLeScanner не находит devicesа?

Причины: Bluetooth disabled (проверьте adapter.isEnabled), не получены разрешения (BLUETOOTH_SCAN на API 31+, ACCESS_FINE_LOCATION на API 23–30), scanner = null (адаптер недоступен), devicesо вне зоны действия, или используется неправильный фильтр. Также проверьте onScanFailed — код ошибки укажет на причину: SCAN_FAILED_ALREADY_STARTED (1) или SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2).

Как сканировать BLE в фоне на Android?

Используйте PendingIntent версию startScan() — передайте PendingIntent вместо ScanCallback. При обнаружении BLE-devicesа Android отправляет Broadcast в BroadcastReceiver, который может быть запущен системой даже если приложение в background. Для Android 8+ добавьте BroadcastReceiver в манифест. На Android 10+ учитывайте ограничения энергосбережения производителей.

Сколько BLE-devices можно обнаружить за одно сканирование?

BluetoothLeScanner не имеет лимита на количество обнаруженных devices — ограничение зависит от BLE-насыщенности среды. В офисе может быть 20–50 активных BLE-devices, в торговом центре — сотни. Для фильтрации используйте ScanFilter (по UUID, имени). Без фильтрации обрабатывайте результаты асинхронно — onScanResult может вызываться десятки раз в секунду.

Итоги

  • BluetoothLeScanner — современный класс Android (API 21+) для BLE-сканирования с настройками и фильтрацией
  • ScanSettings — три режима: LOW_POWER (фоновый), BALANCED (balanced), LOW_LATENCY (активный)
  • ScanFilter — фильтрация по UUID сервиса, имени devicesа, MAC-адресу, manufacturer data с AND-логикой
  • ScanCallback — onScanResult (одиночные), onBatchScanResults (пакетные), onScanFailed (коды ошибок)
  • PendingIntent — фоновое сканирование через BroadcastReceiver, работающее при завершённом приложении
  • ScanRecord — рекламные данные BLE: serviceData, manufacturerSpecificData, advertiseFlags, TX power level
  • Kotlin Flow — callbackFlow позволяет использовать BluetoothLeScanner в реактивной архитектуре с автоостановкой

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также