BluetoothLeScanner — шта је, методе и BLE скенирање у Android-у

Аутор: IT Sectr Објављено: 2026-07-16 Време читања: 10 мин

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 — савремено Android API (API 21+) за BLE скенирање, замена застарелог startLeScan
  • ScanSettings — подешавање режима скенирања: LOW_POWER, BALANCED, LOW_LATENCY и тип callback-а
  • ScanFilter — филтрирање резултата по UUID сервиса, имену уређаја, MAC адреси, подацима произвођача
  • ScanCallback — callback резултата onScanResult, onBatchScanResults и onScanFailed са кодовима грешака
  • PendingIntent — позадинско скенирање путем BroadcastReceiver-а, чак када је апликација у позадини

Шта је 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 ономогућен или уређај не подржава BLE). Пре добијања проверите BluetoothAdapter.isEnabled() и присуство FEATURE_BLUETOOTH_LE путем PackageManager-а. Након добијања скенера, можете покренути скенирање у било којој нити — Android сам планира BLE операције на унутрашњој нити Bluetooth стека.

kotlin
// Добијање 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: режими скенирања и тип callback-а

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 мс), максимална потрошња енергије. За активно претрагивање уређаја користите LOW_LATENCY, за позадински надзор — LOW_POWER.

reportDelay — кашњење у милисекундама пре групног слања резултата. Ако је reportDelay = 0, резултати се шаљу одмах након откривања. Ако је > 0, Android акумулира резултате и шаље серију путем onBatchScanResults. Групно слање смањује број позива callback-ова и смањује потрошњу енергије, погодно за позадинско скенирање са ниским приоритетом.

kotlin
// 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 км). highSpeedScan — PHY_LE_2M (2 Mbit/s, само BLE 5.0+ уређаја).

ScanFilter: филтрирање BLE уређаја по UUID-у и имену

ScanFilter — класа за филтрирање резултата BLE скенирања. Без филтера BluetoothLeScanner враћа све BLE уређаје у домету — у густом BLE окружењу то су стотине пакета у минути. ScanFilter сужава резултате на потребне уређаје, смањујући потрошњу енергије и оптерећење апликације. Филтери се примењују на нивоу Bluetooth стека — неодговарајући пакети се одбацују пре испоруке апликацији.

Типови филтера: setServiceUuid — UUID сервиса (обавезно пуни 128-битни формат). setDeviceName — подниз имена уређаја (осетљив на величину слова, тачно подударање подниза). setDeviceAddress — тачна MAC адреса. setManufacturerData — подаци произвођача (ID компаније + маска). За једно скенирање може се поставити више филтера — уређај мора да одговара свима (AND логика). За OR логику покрените више скенирања.

kotlin
// ScanFilter креирање за различите сценарије
class ScanFilterFactory {

    // 1. Филтер по UUID сервиса (Heart Rate Monitor)
    fun byHeartRateService(): ScanFilter {
        return ScanFilter.Builder()
            .setServiceUuid(
                ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
            )
            .build()
    }

    // 2. Филтер по имену уређаја („Sensor”)
    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: обрада резултата и грешака скенирања

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 скенирање није подржано на уређају.

kotlin
// Пуни резултати скенирања и обрада грешака
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: позадинско BLE скенирање путем BroadcastReceiver-а

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-ом.

kotlin
// 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-ом

Пуни пример BLE скенера у Kotlin-у, који користи BluetoothLeScanner са ScanSettings, ScanFilter и ScanCallback за проналажење Heart Rate Monitor уређаја. Скенер приказује листу пронађених уређаја са RSSI и UUID сервиса, са могућношћу повезивања путем BluetoothGatt-а.

kotlin
// Пуни 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-у?

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 не проналази уређаје?

Разлози: 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).

Како скенирати BLE у позадини на Android-у?

Користите PendingIntent верзију startScan() — проследите PendingIntent уместо ScanCallback-а. При откривању BLE уређаја, Android шаље Broadcast у BroadcastReceiver, који може бити покренут од стране система чак ако је апликација у позадини. За Android 8+ додајте BroadcastReceiver у манифест. На Android 10+ узимите у обзир ограничења штедње енергије произвођача.

Колико BLE уређаја се може открити у једном скенирању?

BluetoothLeScanner нема ограничење броја уређаја који могу бити откривени — ограничење зависи од BLE засићености окружења. У канцеларији може бити 20–50 активних BLE уређаја, у тржном центру — стотине. За филтрирање користите ScanFilter (по UUID-у, имену). Без филтрирања обрађујте резултате асинхроно — onScanResult се може позивати десетинама пута у секунди.

Резиме

  • BluetoothLeScanner — савремена Android класа (API 21+) за BLE скенирање са подешавањима и филтрирањем
  • ScanSettings — три режима: LOW_POWER (позадина), BALANCED (избалансирано), LOW_LATENCY (активно)
  • ScanFilter — филтрирање по UUID сервиса, имену уређаја, MAC адреси, подацима произвођача са AND логиком
  • ScanCallback — onScanResult (појединачни), onBatchScanResults (серијски), onScanFailed (кодови грешака)
  • PendingIntent — позадинско скенирање путем BroadcastReceiver-а, ради када је апликација завршена
  • ScanRecord — BLE рекламни подаци: serviceData, manufacturerSpecificData, advertiseFlags, TX power level
  • Kotlin Flow — callbackFlow омогућуће кориштење BluetoothLeScanner-а у реактивној архитектури са аутоматским заустављањем

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође