BluetoothLeScanner — Android class para sa pag-scan ng Bluetooth Low Energy devices, available mula API 21 (Android 5.0). Pinalitan ng BluetoothLeScanner ang hindi na ginagamit na startLeScan method sa BluetoothAdapter, na nagbibigay ng flexibleng API na may configuration ng scanning (ScanSettings), filtering (ScanFilter) at support para sa background mode (PendingIntent). Nakukuha ang instance sa pamamagitan ng BluetoothAdapter.getBluetoothLeScanner(). Ayon sa Android Developers, 2026, sinusuportahan ng BluetoothLeScanner ang tatlong power consumption mode at pinapayagan ang pag-scan ng BLE advertisement packets na may filtering ayon sa service UUID, device name o MAC address.
Mga Pangunahing
BluetoothLeScanner — system class para sa pamamahala ng BLE scanning sa Android. Hindi tulad ng BluetoothAdapter.startLeScan() na tumatanggap ng simpleng LeScanCallback, ang BluetoothLeScanner ay nagbibigay ng object-oriented API na may mga setting, filter at pinahabang error handling. Lumitaw ang class sa API 21 (Android 5.0) kasama ng support para sa BLE 4.2 at nananatiling pangunahing paraan ng BLE scanning sa lahat ng modernong bersyon ng Android.
Ang pagkuha ng BluetoothLeScanner instance ay ginagawa sa pamamagitan ng BluetoothAdapter.getBluetoothLeScanner(). Ang method ay nagbabalik ng null kung ang Bluetooth adapter ay hindi available (naka-disable ang Bluetooth o hindi sinusuportahan ng device ang BLE). Bago kumuha, suriin ang BluetoothAdapter.isEnabled() at pagkakaroon ng FEATURE_BLUETOOTH_LE sa pamamagitan ng PackageManager. Pagkatapos makuha ang scanner, maaari mong simulan ang scanning sa anumang thread — Android mismo ang nag-iiskedyul ng BLE operations sa internal thread ng Bluetooth stack.
// Pagkuha ng 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 {
// Suriin ang availability ng BLE
if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
return false
}
// Suriin kung naka-enable ang Bluetooth
if (adapter?.isEnabled != true) {
return false
}
// Kunin ang scanner
scanner = adapter?.bluetoothLeScanner
return scanner != null
}
// Suriin ang availability ng scanner
val isAvailable: Boolean
get() = scanner != null
// Magsimula ng basic scan nang walang filter
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}")
}
}
Ang class na BLEScannerManager ay nagpapakita ng ligtas na pagkuha at initialization ng BluetoothLeScanner. Sinusuri ng initScanner ang pagkakaroon ng BLE sa pamamagitan ng hasSystemFeature, naka-enable na Bluetooth at matagumpay na pagkuha ng scanner. Ang startBasicScan ay nagsisimula ng scanning nang walang setting at filter — natutukoy ang lahat ng BLE device sa saklaw. Hinahati ng handleResult ang ScanResult: BluetoothDevice (pangalan, address), RSSI (signal level), scanRecord (advertisement data).
ScanSettings — class para sa configuration ng BLE scanning. Ang pangunahing parameter — scan mode (scanMode), na tumutukoy sa kompromiso sa pagitan ng power consumption at detection delay. Pinapayagan ng ScanSettings.Builder ang configuration ng: scanMode, callbackType (CALLBACK_TYPE_ALL_MATCHES, CALLBACK_TYPE_FIRST_MATCH, CALLBACK_TYPE_MATCH_LOST), matchMode (MATCH_MODE_AGGRESSIVE, MATCH_MODE_STICKY), reportDelay (delay ng batch sending) at phy (PHY_LE_1M, PHY_LE_2M, PHY_LE_CODED).
Tatlong scan mode: SCAN_MODE_LOW_POWER (0) — background scanning na may mababang power consumption, detection delay ilang segundo. SCAN_MODE_BALANCED (1) — balanseng mode para sa karamihan ng mga scenario. SCAN_MODE_LOW_LATENCY (2) — minimal na detection delay (mga 100 ms), maximum na power consumption. Para sa aktibong paghahanap ng device gamitin ang LOW_LATENCY, para sa background monitoring — LOW_POWER.
reportDelay — delay sa milliseconds bago ang group sending ng mga resulta. Kung reportDelay = 0, ang mga resulta ay ipinapadala kaagad pagkatapos ng detection. Kung > 0, iniipon ng Android ang mga resulta at nagpapadala ng batch sa pamamagitan ng onBatchScanResults. Ang batch sending ay nagbabawas ng bilang ng callback calls at nagpapababa ng power consumption, angkop para sa background scanning na may mababang priyoridad.
// Configuration ng ScanSettings para sa iba’t ibang scenario
class ScanSettingsProvider {
// 1. Mabilis na scan (aktibong paghahanap)
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 bawat 2 segundo
.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 lang sa 2M PHY (BLE 5.0+)
fun highSpeedScan(): ScanSettings {
return ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setPhy(ScanSettings.PHY_LE_2M)
.build()
}
}
Ang class na ScanSettingsProvider ay naglalaman ng tipikal na configuration. lowLatencyScan — para sa UI scanning (paghahanap ‘dito at ngayon’). lowPowerScan — para sa background monitoring na may batch bawat 2 segundo at callbackType FIRST_MATCH (gumagana lamang sa unang detection). Ang longRangeScan ay gumagamit ng PHY_LE_CODED (BLE Long Range, hanggang 1 km). highSpeedScan — PHY_LE_2M (2 Mbit/s, BLE 5.0+ devices lang).
ScanFilter — class para sa filtering ng BLE scan results. Kung walang filter, ibinabalik ng BluetoothLeScanner ang lahat ng BLE device sa saklaw — sa siksik na BLE environment, ito ay daan-daang packet bawat minuto. Pinapaliit ng ScanFilter ang mga resulta sa mga kinakailangang device, na nagbabawas ng power consumption at load ng app. Ang mga filter ay inilalapat sa antas ng Bluetooth stack — ang mga hindi angkop na packet ay tinatanggihan bago ihatid sa app.
Mga uri ng filter: setServiceUuid — service UUID (obligado ang buong 128-bit format). setDeviceName — substring ng device name (case-sensitive, eksaktong substring match). setDeviceAddress — eksaktong MAC address. setManufacturerData — data ng manufacturer (company ID + mask). Para sa isang scanning, maraming filter ang maaaring itakda — dapat tumugma ang device sa lahat (AND logic). Para sa OR logic, magpatakbo ng maraming scanning.
// Paglikha ng ScanFilter para sa iba’t ibang scenario
class ScanFilterFactory {
// 1. Filter ayon sa service UUID (Heart Rate Monitor)
fun byHeartRateService(): ScanFilter {
return ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
)
.build()
}
// 2. Filter ayon sa device name (“iBeacon*")
fun byDeviceName(): ScanFilter {
return ScanFilter.Builder()
.setDeviceName("Sensor")
.build()
}
// 3. MAC- (mga device)
fun byMacAddress(mac: String): ScanFilter {
return ScanFilter.Builder()
.setDeviceAddress(mac)
.build()
}
// 4. Pinagsamang filter (UUID + )
fun combinedFilter(): List<ScanFilter> {
return listOf(
ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000A001-0000-1000-8000-00805F9B34FB")
)
.setDeviceName("MyDevice")
.build()
)
}
// 5. data ng manufacturer
fun byManufacturer(companyId: Int, data: ByteArray, mask: ByteArray): ScanFilter {
return ScanFilter.Builder()
.setManufacturerData(companyId, data, mask)
.build()
}
}
Ang class na ScanFilterFactory ay nagpapakita ng lahat ng uri ng filter. Ang byHeartRateService ay nagfi-filter ng mga device na may pulse service 0x180D. Ang byDeviceName ay nakakahanap ng mga device na naglalaman ng ‘Sensor’ sa pangalan (Inirerekomenda ng Apple ang mga natatanging pangalan para sa filtering). byMacAddress — eksaktong paghahanap ng partikular na device. combinedFilter — AND filter ayon sa UUID at pangalan. byManufacturer — filter ayon sa data ng manufacturer (halimbawa, para sa iBeacon ginagamit ang company ID Apple 0x004C).
ScanCallback — abstract class para sa pagtanggap ng BLE scan results. Naglalaman ng tatlong method: onScanResult — solong resulta (callback type, ScanResult), onBatchScanResults — batch ng mga resulta para sa reportDelay > 0, onScanFailed — error code. Lahat ng method ay tinatawag sa main thread ng Android (main thread). Para sa mahabang pagproseso sa onScanResult, gumamit ng coroutines o HandlerThread.
ScanResult ay naglalaman ng: BluetoothDevice device (device), int rssi (signal level sa dBm), ScanRecord scanRecord (advertisement data), long timestampNanos (oras ng detection mula nang mag-boot ang system). Ang ScanRecord ay nagbibigay ng: getServiceData() — UUID + custom data, getManufacturerSpecificData() — data ng manufacturer, getAdvertiseFlags() — BLE flags. Ang callback type (callbackType) ay nagpapahiwatig: CALLBACK_TYPE_ALL_MATCHES — tugma sa filter, CALLBACK_TYPE_FIRST_MATCH — unang detection, CALLBACK_TYPE_MATCH_LOST — pagkawala ng device.
Mga error code ng onScanFailed: SCAN_FAILED_ALREADY_STARTED (1) — scanning ay nagsimula na, SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2) — nabigo ang pagpaparehistro ng app sa Bluetooth stack, SCAN_FAILED_INTERNAL_ERROR (3) — internal na error ng stack, SCAN_FAILED_FEATURE_UNSUPPORTED (4) — BLE scanning ay hindi suportado sa device.
// Buong scan results at error handling
class ScanResultHandler {
private val results = mutableListOf<ScanResult>()
val scanCallback = object : ScanCallback() {
// 1. Isang resulta
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
}
// Idagdag sa listahan (dedup ayon sa address)
val existingIndex = results.indexOfFirst {
it.device.address == result.device.address
}
if (existingIndex >= 0) {
results[existingIndex] = result // i-update ang RSSI
} else {
results.add(result)
}
// Kunin ang data mula sa advertisement 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. Error sa scan
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}")
}
}
Ang class na ScanResultHandler ay nagpoproseso ng lahat ng uri ng callback ng BluetoothLeScanner. Ina-update ng onScanResult ang listahan ng device na may deduplication ayon sa MAC address — ina-update ang RSSI para sa mga nahanap na device. Ang CALLBACK_TYPE_MATCH_LOST ay nagpapahiwatig ng pagkawala ng device (pag-alis mula sa listahan). Ang onBatchScanResults ay nagpoproseso ng batch results para sa reportDelay > 0. Ang onScanFailed ay nagma-map ng error codes sa nababasang mensahe — mahalaga para sa debugging ng BLE scanning.
PendingIntent scanning — mekanismo ng BluetoothLeScanner para sa BLE scanning na gumagana kahit nasa background ang app (may mga limitasyon sa Android 8+). Sa halip na ScanCallback, ginagamit ang PendingIntent na nagpapadala ng Broadcast sa system BroadcastReceiver kapag may nakitang BLE device. Ito ay nagpapahintulot sa app na makatanggap ng mga notification tungkol sa BLE devices nang hindi nasa memorya (gumagawa ang system ng process kapag nakatanggap ng broadcast).
Mga limitasyon ng background scanning: Sa Android 8+ (API 26) ang mga background service ay limitado — nilalampasan ng PendingIntent scanning ang limitasyong ito sa pamamagitan ng BroadcastReceiver na maaaring simulan ng system kapag nakatanggap ng BLE event. Sa Android 10+ (API 29) ang background BLE scanning ay dagdag na limitado ng power saving policies ng mga manufacturer (Xiaomi, Huawei, Samsung ay humaharang sa background BLE operations). Para sa kritikal na BLE scenario, kinakailangan ang notification na may foreground service.
// Background BLE scanning sa pamamagitan ng PendingIntent
class BackgroundBLEScanner(private val context: Context) {
private val scanner: BluetoothLeScanner? by lazy {
val adapter = BluetoothAdapter.getDefaultAdapter()
adapter?.bluetoothLeScanner
}
fun startBackgroundScan() {
// Gumawa ng PendingIntent para sa BroadcastReceiver
val intent = Intent(context, BLEBroadcastReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// Mga setting ng background scan
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
.setMatchMode(ScanSettings.MATCH_MODE_STICKY)
.build()
// Simulan ang background scan
scanner?.startScan(
null, // mga filter
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) {
// Kunin ang mga scan result
val results = BluetoothLeScanner.getPendingIntentScanResults(intent)
results?.let { scanResults ->
for (result in scanResults) {
// Magpadala ng notification sa 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)
}
}
Ang class na BackgroundBLEScanner ay nagsisimula ng background BLE scanning sa pamamagitan ng PendingIntent. Ang startBackgroundScan ay gumagawa ng PendingIntent na kapag may nakitang BLE device ay nagpapadala ng Broadcast sa BLEBroadcastReceiver. Kinukuha ng BroadcastReceiver ang ScanResult sa pamamagitan ng getPendingIntentScanResults() at maaaring magpakita ng notification o magpadala ng data sa server. Ang approach na ito ay gumagana kahit ang app ay tinapos ng system — sinisimulan ng Android ang BroadcastReceiver kapag nakatanggap ng broadcast.
Buong halimbawa ng BLE scanner sa Kotlin, na gumagamit ng BluetoothLeScanner na may ScanSettings, ScanFilter at ScanCallback para sa paghahanap ng Heart Rate Monitor devices. Ipinapakita ng scanner ang listahan ng mga nahanap na device na may RSSI at service UUIDs, na may kakayahang kumonekta sa pamamagitan ng BluetoothGatt.
// Buong BLE scanner na may coroutines sa 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 {
// Suriin ang estado ng Bluetooth
if (adapter?.isEnabled != true) {
close(IllegalStateException("Bluetooth disabled"))
return@callbackFlow
}
// Configuration ng scan
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"))
}
}
// Simulan ang scanning
scanner?.startScan(filters, settings, callback)
// Awtomatikong paghinto pagkatapos ng duration
delay(duration)
scanner?.stopScan(callback)
close()
}.flowOn(Dispatchers.IO)
fun stop() {
scanner?.stopScan(object : ScanCallback() {})
}
}
class BLEException(message: String) : Exception(message)
Ang class na DeviceScanner ay gumagamit ng Kotlin Flow (callbackFlow) para sa reactive BLE scanning. Ang scanning ay sinisimulan na may LOW_LATENCY settings at filter ayon sa Heart Rate Service UUID. Ang mga resulta ay inilalabas sa pamamagitan ng onScanResult papunta sa Flow. Awtomatikong paghinto pagkatapos ng tinukoy na duration (10 segundo default). Ang FlowOn(Dispatchers.IO) ay naglilipat ng BLE operations sa background thread. Ang approach na ito ay nagpapahintulot sa paggamit ng BLE scanning sa MVVM architecture sa pamamagitan ng viewModelScope.launch at collect.
Mga Madalas Itanong
BluetoothLeScanner — Android class (API 21+) para sa BLE scanning. Nakukuha sa pamamagitan ng BluetoothAdapter.getBluetoothLeScanner(). Sinusuportahan ang tatlong scan mode (LOW_POWER, BALANCED, LOW_LATENCY), filtering ayon sa UUID, pangalan at MAC address, batch results at PendingIntent para sa background scanning. Pinapalitan ang hindi na ginagamit na method na BluetoothAdapter.startLeScan().
SCAN_MODE_LOW_POWER — background mode na may detection delay na 5–10 segundo, minimal na power consumption. SCAN_MODE_LOW_LATENCY — aktibong mode na may delay na mga 100 ms, maximum na power consumption. SCAN_MODE_BALANCED — kompromiso (~2 segundo delay). Para sa UI scanning gamitin ang LOW_LATENCY, para sa background monitoring — LOW_POWER na may PendingIntent.
Mga dahilan: Naka-disable ang Bluetooth (suriin ang adapter.isEnabled), hindi nakuha ang mga permission (BLUETOOTH_SCAN sa API 31+, ACCESS_FINE_LOCATION sa API 23–30), scanner = null (hindi available ang adapter), device wala sa saklaw o maling filter ang ginamit. Suriin din ang onScanFailed — ang error code ay magpapahiwatig ng dahilan: SCAN_FAILED_ALREADY_STARTED (1) o SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2).
Gamitin ang PendingIntent na bersyon ng startScan() — ipasa ang PendingIntent sa halip na ScanCallback. Kapag may nakitang BLE device, nagpapadala ang Android ng Broadcast sa BroadcastReceiver na maaaring simulan ng system kahit nasa background ang app. Para sa Android 8+, idagdag ang BroadcastReceiver sa manifest. Sa Android 10+, isaalang-alang ang power saving restrictions ng mga manufacturer.
Walang limitasyon ang BluetoothLeScanner sa bilang ng mga device na maaaring makita — ang limitasyon ay depende sa BLE saturation ng environment. Sa opisina maaaring may 20–50 aktibong BLE device, sa shopping center — daan-daan. Para sa filtering, gamitin ang ScanFilter (ayon sa UUID, pangalan). Kung walang filtering, iproseso ang mga resulta nang asynchronous — ang onScanResult ay maaaring tawagin ng sampu-sampung beses bawat segundo.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din