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 — API Android ที่ทันสมัย (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() ที่ยอมรับ 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 ms) การใช้พลังงานสูงสุด สำหรับการค้นหาอุปกรณ์แบบแอคทีฟ ใช้ 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 ระยะไกล (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 ระยะไกล สูงสุด 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 บริการ (เครื่องวัดอัตราการเต้นของหัวใจ)
    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 ใช้ ID บริษัท Apple 0x004C)

ScanCallback: การจัดการผลลัพธ์และข้อผิดพลาดการสแกน

ScanCallback เป็นคลาสนามธรรมสำหรับรับผลลัพธ์การสแกน BLE ประกอบด้วยสามเมธอด: onScanResult — ผลลัพธ์เดี่ยว (ประเภท callback, ScanResult), onBatchScanResults — ผลลัพธ์แบบแบตช์สำหรับ reportDelay > 0, onScanFailed — รหัสข้อผิดพลาด เมธอดทั้งหมดถูกเรียกบนเธรดหลักของ Android สำหรับการประมวลผลที่ยาวนานใน onScanResult ให้ใช้ coroutines หรือ HandlerThread

ScanResult ประกอบด้วย: BluetoothDevice device, int rssi (ระดับสัญญาณใน dBm), ScanRecord scanRecord (ข้อมูลโฆษณา), long timestampNanos (เวลาค้นพบตั้งแต่เริ่มต้นระบบ) ScanRecord ให้: getServiceData() — UUID + ข้อมูลที่กำหนดเอง, getManufacturerSpecificData() — ข้อมูลผู้ผลิต, getAdvertiseFlags() — แฟล็ก BLE ประเภท callback บ่งชี้: 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 ที่สำคัญ จำเป็นต้องมีการแจ้งเตือนบริการเบื้องหน้า

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 เต็มรูปแบบกับ coroutines ใน 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 ms การใช้พลังงานสูงสุด SCAN_MODE_BALANCED — การประนีประนอม (~2 วินาทีหน่วง) ใช้ LOW_LATENCY สำหรับการสแกน UI ใช้ 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 ของสภาพแวดล้อม สำนักงานอาจมีอุปกรณ์ BLE ที่ใช้งานอยู่ 20–50 เครื่อง ห้างสรรพสินค้าอาจมีหลายร้อย ใช้ 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
  • Kotlin Flow — callbackFlow ช่วยให้ใช้ BluetoothLeScanner ในสถาปัตยกรรมรีแอคทีฟพร้อมหยุดอัตโนมัติ

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม