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(), који прихвата једноставан 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 стека.
// Добијање 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 мс), максимална потрошња енергије. За активно претрагивање уређаја користите 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 км). 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. Филтер по имену уређаја („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 — апстрактна класа за примање резултата 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 мс, максимална потрошња енергије. 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. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође