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() انجام میشود. اگر آداپتر Bluetooth در دسترس نباشد (Bluetooth غیرفعال یا دستگاه از BLE پشتیبانی نکند)، روش null برمیگرداند. قبل از دریافت، BluetoothAdapter.isEnabled() و وجود FEATURE_BLUETOOTH_LE را از طریق PackageManager بررسی کنید. پس از دریافت اسکنر، میتوان اسکن را در هر ثریدی آغاز کرد — Android عملیات BLE را در ثرید داخلی پیشکر خود برنامهریزی میکند.
// دریافت 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 مگابیت در ثانیه، تنها دستگاههای BLE 5.0+).
ScanFilter — کلاسی برای فیلتراسیون نتایج اسکن BLE. بدون فیلتر، BluetoothLeScanner تمامی دستگاههای BLE را در محدوده بازگشت میدهد — در محیط BLE پرتراکم، این میتواند صدها پکت در دقیقه باشد. ScanFilter نتایج را به دستگاههای مورد نیاز محدود میکند و مصرف انرژی و بار برنامه را کاهش میدهد. فیلترها در سطح Bluetooth Stack اعمال میشوند — پکتهای نامناسب قبل از تحویل به برنامه رد میشوند.
انواع فیلترها: 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 استفاده میشود که در صورت تشخیص دستگاه BLE، Broadcast را به BroadcastReceiver سیستم ارسال میکند. این به برنامه اجازه میدهد اعلامهایی درباره دستگاههای 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 در صورت دریافت broadcast، BroadcastReceiver را راهاندازی میکند.
مثال کامل اسکنر 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() استفاده کنید — به جای ScanCallback، PendingIntent را ارسال کنید. در صورت تشخیص دستگاه BLE، Android Broadcast را به BroadcastReceiver ارسال میکند که حتی اگر برنامه در پسزمینه است نیز میتواند توسط سیستم راهاندازی شود. برای Android 8+ BroadcastReceiver را به مانیفست اضافه کنید. در Android 10+ محدودیتهای صرفهجویی سازندگان را در نظر بگیرید.
BluetoothLeScanner محدودیتی برای تعداد دستگاههای قابل تشخیص ندارد — محدودیت به تراکم BLE محیط بستگی دارد. در دفتر ممکن است 20–50 دستگاه BLE فعال و در مرکز خرید صدها وجود داشته باشد. برای فیلتراسیون از ScanFilter (بر اساس UUID، نام) استفاده کنید. بدون فیلتراسیون نتایج را به صورت ناهمگام پردازش کنید — onScanResult ممکن است دهها بار در ثانیه فراخوانده شود.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.