Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> ระบบ >> Android

คู่มือเทคนิคครบวงจร: AOSP 16 Bluetooth Scanner - การสแกนแบบ Passive และฟีเจอร์ใหม่ๆ ที่นักพัฒนาต้องรู้

คู่มือเทคนิคครบวงจร: AOSP 16 Bluetooth Scanner - การสแกนแบบ Passive และฟีเจอร์ใหม่ๆ ที่นักพัฒนาต้องรู้

Bluetooth... เทคโนโลยีที่เรารักและเกลียดพร้อมกัน เหมือนเพื่อนคนหนึ่งที่จะเชื่อมต่อได้ทุกครั้ง แต่แล้ว... ไม่ได้เชื่อมต่อจริงๆ

นานหลายปีแล้ว นักพัฒนา Android ต้องต่อสู้กับ Bluetooth อย่างหนัก พยายามให้มันทำงาน ได้รับความทุกข์ทรมานจากการหลุดเชื่อมต่อที่ไม่มีเหตุผล และหยดน้ำตาเงียบๆ กับปัญหาที่ลึกลับ

แต่ถ้าฉันบอกว่าสิ่งต่างๆ จะดียิ่งขึ้น? ถ้าฉันบอกว่ากับ Android 16 เทพBluetooth ได้ยินมโนของเราแล้ว? ไม่ใช่ฝันครับ เพื่อนๆ นี่คือ AOSP 16 Bluetooth Scanner ที่มาพร้อมความหวังใหม่ให้จิตวิญญาณนักพัฒนาที่เหนื่อยล้า

ในคู่มือนี้ เราจะเดินทางไปด้วยกัน สู่ใจกลางของฟีเจอร์ Bluetooth ใหม่ๆ ใน AOSP 16 เราจะหัวเราะ เราจะร้องไห้ (หวังว่าจะเป็นน้ำตาของความสุข) และเราจะเรียนรู้ที่จะใช้พลังใหม่ๆ เหล่านี้ให้เกิดประโยชน์ เราจะสำรวจเวทมนตร์ของการสแกนแบบ Passive, ดราม่าของเหตุผลการสูญเสีย Bond, และความสะดวกสบายในการดึง Service UUID จากโฆษณาโดยไม่ต้องผ่านความยุ่งยากเดิมๆ

จบคู่มือนี้ คุณจะสามารถ:

  • สร้าง Bluetooth Scanner ที่มีประสิทธิภาพมากจนเกือบจะทายใจได้

  • ดีบั๊กปัญหาการเชื่อมต่อเหมือนสืบสวนมืออาชีพ

  • โชว์ทักษะ Bluetooth ใหม่ๆ ให้เพื่อนและเพื่อนร่วมงานชื่นชม

ข้อกำหนดเบื้องต้น:

ก่อนเริ่ม ควรมีความเข้าใจพื้นฐานเกี่ยวกับการพัฒนา Android และ Kotlin หากคุณเคยพยายามให้อุปกรณ์สองเครื่องคุยกัน และสุดท้ายอยากโยนคอมพิวเตอร์ออกหน้าต่าง คุณสมบูรณ์แล้วครับ

เตรียมเครื่องดื่มโปรด สวมเสื้อคลุมนักเขียนโค้ด และพร้อมสำหรับการตื่นตัวของ Bluetooth กันเลย!

สารบัญ

  1. ประวัติสั้นๆ ของ Bluetooth ใน Android

  2. อะไรใหม่ใน AOSP 16: สามยอดนักดาบ

  3. เจาะลึก #1: การสแกนแบบ Passive

  4. ทำความเข้าใจ BluetoothLeScanner

  5. ลงมือทำ: สร้าง Passive Scanner แรกของคุณ

  6. เจาะลึก #2: เหตุผลการสูญเสีย Bluetooth Bond

  7. เจาะลึก #3: Service UUID จาก Advertisements

  8. หัวข้อขั้นสูง: ยกระดับเกมการสแกน

  9. Use Cases จริง: จุดที่ Bluetooth ติดถนนจริง

  10. การตรวจสอบ API Version: ไม่ให้แอป Crash

  11. การทดสอบและดีบั๊ก: ส่วนที่สนุก (ไม่มีใครบอกเลย)

  12. ประสิทธิภาพและ Best Practices: เป็นพลเมือง Bluetooth ที่ดี

  13. สรุป: อนาคตคือ Passive (และมันโอเค)

ประวัติ Bluetooth (หรือ: เราเรียนรู้ที่จะหยุดกังวลและรักคลื่นวิทยุ)

ยุคมืด: Classic Bluetooth

ในตอนแรก มี Classic Bluetooth มันเหมือนแขกงานเลี้ยงที่ดังและ吵鬧 สามารถส่งข้อมูลเยอะ (เพลงไปยังสปีกเกอร์) แต่กินแบตเตอรี่อย่างหนัก เหมาะสำหรับสตรีมเสียง แต่สำหรับการถ่ายโอนข้อมูลเล็กน้อยและไม่บ่อย? เหมือนใช้ท่อน้ำดับเพลิงรดน้ำต้นไม้เล็ก มากเกินไป และค่อนข้างวุ่นวาย

นักพัฒนายุคนั้นใช้ชีวิตกับ BluetoothAdapter, BluetoothDevice, และ BluetoothSocket ที่น่ากลัว การเชื่อมต่อง่ายๆ อาจใช้เวลาหลายวินาที หรือ... ไปทำกาแฟกลับมาก็ยังไม่เสร็จ แบตเตอรี่ของผู้ใช้จะลดลงเร็วกว่าลูกโป่งตะกั่ว

ยุคฟื้นฟู: Bluetooth Low Energy (BLE)

มา Android 4.3 ฮีโร่ใหม่ขึ้น: Bluetooth Low Energy (BLE) ไม่ใช่ Bluetooth ของพ่อคุณ BLE ทันสมัย ประหยัด และลึกลับ ออกแบบสำหรับการส่งข้อมูลแบบสั้นๆ ดื่มพลังงานเหมือนไวน์ดีๆ แทนที่จะกลืนควย

BLE เป็นเด็กหนุ่มโฉมดิ้น เปิดโลกใหม่: นาฬิกาอัจฉริยะ, ตัววัดอัตราชีพจร, และอุปกรณ์ IoT ล้านล้านชนิด ที่วิ่งได้หลายเดือนด้วยแบตเหรียญเพียงอันเดียว มันเปลี่ยนเกมจริงๆ

แต่มาพร้อมกับความซับซ้อน ต้องเรียนภาษาใหม่: GATT, GAP, Services, Characteristics เหมือนจากเขียนสคริปต์ง่ายๆ กลายเป็นเขียนโอเปร่าทั้ง齣 พลังมากแต่ learning curve ชัน

เด็กปัญหา: การสแกน (Scanning)

แล้วก็มาถึงการสแกน การค้นหาอุปกรณ์ประหยัดพลังงานเหล่านี้ ในวันแรกๆ ของ BLE การสแกนยังเป็นoeste ตัวโทรศัพท์จะตะโกน "ใครอยู่ไหน?" แล้วฟังตอบกลับ ใช้ได้แต่กินแบต มากกว่านักพัฒนาต้องเดินเชือก: ต้องค้นหาอุปกรณ์ แต่ไม่อยากเป็นคนทำให้โทรศัพท์ผู้ใช้ตายก่อนเที่ยง

นี่คือโลกที่ AOSP 16 เกิดขึ้น โลกที่ร้องขอวิธีสแกนที่ดีกว่า โลกที่พร้อมสำหรับฮีโร่ และฮีโร่นั้นคือ Passive Scanning

อะไรใหม่ใน AOSP 16? (สปอยล์: จริงๆ แล้วโคตรเยี่ยม)

มาถึงส่วนดีๆ AOSP 16 ให้ของเล่นใหม่อะไรบ้าง? ได้เยอะมาก! แต่ก่อนเปิดกล่อง พูดถึงตารางการปล่อยก่อน เพราะมันต่างออกไป

เรื่องของสองครั้งปล่อย (Two Releases)

พลิกโฉม! Android ปล่อย API ใหญ่ 2 ครั้งในปี 2025 ก่อนอื่น Android 16 (โค้ดเนม "Baklava") ใน Q2 - รุ่นใหญ่แบบเดิม แล้ว Q4 มี minor release อีกครั้ง ที่ฟีเจอร์ Bluetooth ใหม่ๆ อยู่ รุ่นนี้เป็นฟีเจอร์และ API ใหม่โดยไม่มีการเปลี่ยนแปลงที่ทำให้แอพพัง เหมือนได้ของหวานฟรีหลังจ่ายบิล

สามยอดนักดาบของ Bluetooth

Q4 release นำอะไรมาให้ Bluetooth? สามฮีโร่ใหม่ ที่เรียกว่า "สามยอดนักดาบ"

ฟีเจอร์ใจความทำไมต้องสนใจ
Passive Scanningฟังอุปกรณ์ Bluetooth โดยไม่ต้องตะโกนหาแอปกลายเป็นนินจาประหยัดแบต
Bond Loss Reasonsสุดท้ายก็รู้ว่าทำไมการเชื่อมต่อถึงหายหยุดเดาเล่น จริงจังดีบั๊กได้
Service UUID from Adsดึงข้อมูลสำคัญของอุปกรณ์จากโฆษณาโดยตรงSpeed dating สำหรับ Bluetooth เร็วขึ้น ประหยัดกว่า

ไม่ใช่แค่การปรับเล็กน้อย ครับพี่น้อง เป็นการปรับปรุงคุณภาพชีวิตที่จะเปลี่ยนวิธีเราสร้างและดีบั๊กแอป Bluetooth รูปแบบพื้นฐาน เหมือนทีม Android ได้ฟังเสียงร้องทุกข์ของเรา (ผมก็ช็อกเหมือนกัน)

ต่อไปเราจะเจาะลึกแต่ละฟีเจอร์ ดูโค้ด สำรวจ use cases และเรียนรู้การใช้พลัง มาพบยอดนักดาบคนแรก: Passive Scanning ที่เข้มแข็งและเงียบขรึม

เจาะลึก #1: การสแกนแบบ Passive (Passive Scanning)

จินตนาการว่าคุณอยู่ในห้องสมุด อยากหาเพื่อนแต่ไม่รู้อยู่ไหน มี 2 ทางเลือก:

  • Active Scanning: ยืนกลางห้องตะโกน "เฮ้ สตีฟ! อยู่ไง?" 有效果 แต่ดัง รบกวน และจะโดนหัวหน้าห้องสมุดไล่ออก (ซึ่งในอุปมาคือแบตเตอรี่ผู้ใช้)

  • Passive Scanning: เดินหาเงียบๆ ฟังเสียงหัวเราะเฉพาะตัวของเพื่อน ไม่พูดเลย แค่ฟัง ลับลอก ประหยัด และไม่กินสังคม (หรือแบตจริง)

หลายปี Android สแกนแบบตะโกนในห้องสมุด แต่ AOSP 16 เราสามารถเป็นผู้ฟังเงียบๆ ได้ นี่คือเวทมนตร์ของ Passive Scanning

Active vs Passive: เทคนิคชนกัน

ในโลก BLE อุปกรณ์ส่งแพ็กเก็ตเล็กๆ เรียกว่า "Advertisements" ว่าด้วยการบอกว่า "นี่คือฉัน และนี่คือสิ่งที่ฉันทำ!"

  • Active Scanning: โทรศัพท์ได้ยิน advertisement แล้วส่ง SCAN_REQ กลับไป = "บอกมากหน่อย!" Peripheral ตอบด้วย SCAN_RSP ที่มีข้อมูลเพิ่ม

  • Passive Scanning: โทรศัพท์ได้ยิน advertisement... แค่นั้นแหละ ไม่ส่งอะไรกลับ แค่จดจำ advertisement แรกและไปต่อ การสนทนาแบบทางเดียว

ทำไมต้อง Passive? พลังของความเงียบ

สองคำ: การบริโภคพลังงาน ทุกครั้งที่วิทยุโทรศัพท์ต้องส่ง (เช่น SCAN_REQ) ใช้พลังงาน ถ้าแอปสแกนตลอดเวลา การส่งเล็กๆ น้อยๆ สะสมกัน แบตผู้ใช้จ่ายราคา

Passive scanning = บอกวิทยุ "แค่ฟัง" ไม่ต้องพูด ลดพลังงานสแกนอย่างมาก เหมาะสำหรับแอปที่ต้อง monitor อุปกรณ์ใกล้เคียงนานๆ

โค้ด: กลายเป็นนินจา Bluetooth

การ implement อย่างไร? ง่ายมาก อยู่ที่ ScanSettings ที่ใช้เริ่มสแกน

ก่อนหน้านี้อาจทำแบบนี้:

val settings = ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
    .build()

ตอนนี้ AOSP 16 มีตัวเลือกใหม่ เปิด passive scanning ด้วยการตั้ง scan type:

// Magic line นี้แหละ!
.setScanMode(ScanSettings.SCAN_TYPE_PASSIVE)

รอ... เอกสารบอกว่า SCAN_TYPE_PASSIVE เป็น scan type ไม่ใช่ scan mode ถูกต้อง! ขอโทษ ตื่นเต้นเกินไป วิธีที่ถูกต้องคือตั้ง scan mode เป็น passive:

val settings = ScanSettings.Builder()
// Magic line จริงๆ!
.setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC) // ใกล้ passive ที่สุด
.build()

ยังไม่ถูก อีกครั้ง ดูเอกสารจริง... Ah! ScanSettings.Builder มีเมธอดใหม่ใน Android 16 QPR2 ไม่ใช่ setScanMode แต่เป็น setting ใหม่ทั้งหมด

คราวนี้ถูกต้องแล้ว นี่คือวิธีเปิด passive scanning จริง:

// ใช้ได้ Android 16 QPR2 ขึ้นไป
val settings = ScanSettings.Builder()
// นี่คือ Magic line จริงๆ ขอสัตย์!
.setScanType(ScanSettings.SCAN_TYPE_PASSIVE)
.build()

เพียงบรรทัดเดียว คุณเปลี่ยนแอปจากนักท่องเที่ยวดังกินแบต เป็นนินจา Bluetooth เงียบขรึม ประหยัดพลังงาน แบตผู้ใช้จะขอบคุณ

แต่นั่นมี trade-off: ไม่ส่ง SCAN_REQ = ไม่ได้ข้อมูลเพิ่มจาก SCAN_RSP แต่หลาย use cases advertisement แรกพอใจแล้ว การประหยัดพลังงานคุ้มค่า

ตอนนี้เราเก่ง Passive Scanning แล้ว ไปต่อที่ BluetoothLeScanner กัน

ทำความเข้าใจ BluetoothLeScanner (ดาราหลักของเรา)

ก่อน master การสแกน ต้องรู้เครื่องมือหลัก: BluetoothLeScanner คิดว่ามันคือ PKE Meter จาก Ghostbusters เครื่องมือตรวจจับพลังงานล่องลอย (BLE advertisements) แต่มันทำงานยังไง?

สถาปัตยะ: มองเบื้องหลังม่าน

ระดับสูง: แอปตัดสินใจหา BLE devices → ขอ BluetoothLeScanner → "ไปหาให้"

ใต้หลอดมันซับซ้อน BluetoothLeScanner คุยกับ Android Bluetooth stack (โค้ดเนม "Fluoride") → สื่อสารกับ Bluetooth controller (hardware จริง) Classic case ของ "ซับซ้อนกว่าที่คิด"

อักษรย่อ: GATT, GAP และเพื่อนๆ

เข้ามาโลก BLE เจออักษรย่อเยอะ อย่าปânico! ไม่ยากอย่างคิด สองตัวสำคัญคือ GAP และ GATT:

  • GAP (Generic Access Profile): การค้นหาและเชื่อมต่อ คิดว่าเป็นบウンサー 클럽 決定ใครคุยกับใคร จัดการ advertising และ scanning BluetoothLeScanner เป็นตัวหลักใน GAP-verse

  • GATT (Generic Attribute Profile): หลังเชื่อมต่อแล้ว GATT ดูแลการแลกเปลี่ยนข้อมูล คิดว่าเป็นการสนทนาภายใน klub Services, Characteristics, Descriptors อุปกรณ์อาจมี "Heart Rate Service" ที่มี "Heart Rate Measurement Characteristic" แอปอ่าน/เขียน characteristics นี้เพื่อข้อมูล

สำหรับ scanning เราอยู่ในโลก GAP ยืนข้างนอก klub ฟัง advertisements

วัฏจักรการสแกน: ละคร 3 ฉาก

  • ฉาก 1: การเตรียม แอปตัดสินใจสแกน → ขอ BluetoothLeScanner สร้าง ScanFilters (กรองอุปกรณ์เฉพาะ) และ ScanSettings (กำหนดวิธีสแกน เช่น passive mode) และกำหนด ScanCallback

  • ฉาก 2: การสแกน เรียก startScan() วิทยุ Bluetooth ตื่น ฟัง advertisements ตรง filters → รายงานผ่าน onScanResult() ใน ScanCallback

  • ฉาก 3: จบ แอปพอใจ (หรือเจอที่ต้องการ) → เรียก stopScan() วิทยุปิด เงียบสนิท สำคัญ: ต้อง stopScan() เสมอ สแกนลี้ลับ = สาเหตุ #1 ของ complaint "แบตตายใน 1 ชั่วโมง"

นี่คือ BluetoothLeScanner ก้อนเปลือก อันทรงพลัง ซับซ้อน แต่มันฉลาดและมีประสิทธิภาพมากขึ้นทุก release ตอนนี้รู้เครื่องมือแล้ว ไปสร้าง Passive Scanner แรกกัน!

ลงมือทำ: สร้าง Passive Scanner แรกของคุณ

ทฤษฎีดีแต่เรานักพัฒนา เรียนรู้จากการทำ (หรือ copy-paste จาก Stack Overflow) ม้ามือ ขึ้น Android Studio สร้างกัน!

ขั้นตอน 1: พิธีขออนุญาต (Permissions)

ก่อนเขียนโค้ด ต้องขออนุญาต Android permission gods พวกนี้เปลี่ยนไปบ้างตลอดกาล

เปิด AndroidManifest.xml ใส่:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- Android 12 (API 31) ขึ้นไป -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<!-- รุ่นเก่า ต้องการ location permissions -->
<!-- อาจยังต้องการถ้ารองรับอุปกรณ์เก่า -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

ดู permissions ข้างต้น จะเห็นวิวัฒนาการของ permission model ของ Android

BLUETOOTH และ BLUETOOTH_ADMIN เป็น old guard มาตั้งแต่แรก ให้ฟังก์ชันพื้นฐานและค้นหาอุปกรณ์ BLUETOOTH_SCAN มา Android 12 (API 31) เปลี่ยน paradigm เรื่อง privacy

ก่อน Android 12 Google คิดว่าหา Bluetooth device = รู้ location ผู้ใช้ ทำให้แอปหาหูฟังต้องขอ location ผู้ใช้ suspect เป็นธรรมชาติ

Android 12 มา BLUETOOTH_SCAN permission 合理的 กว่า สแกนหา Bluetooth โดยไม่ต้องขอ location access ยังต้อง request runtime permission แต่น้อยกว่าอธิบายว่าไม่อยากให้รู้ว่าอยู่ไหน

แต่ permission location สองตัวสุดท้าย เป็นของเก่า ถ้ารองรับ Android 11 ลงมา ต้องเก็บไว้ใน manifest สำหรับ backward compatibility บนอุปกรณ์ใหม่ BLUETOOTH_SCAN เพียงพอ

ขั้นตอน 2: โค้ดตื่นตัว

มาส่วนสนุก Implement passive scanner ใน Activity หรือ Fragment

ขอ Scanner

ก่อนอื่น ขอ instance ของ BluetoothLeScanner:

private val bluetoothAdapter: BluetoothAdapter? by lazy {
    val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
    bluetoothManager.adapter
}
private val bleScanner: BluetoothLeScanner? by lazy {
    bluetoothAdapter?.bluetoothLeScanner
}

ใช้ Kotlin lazy delegation = "สร้าง object ตอนต้องการจริงๆ" Good practice เพราะขอ Bluetooth adapter ต้อง system calls ไม่ควรทำถ้าไม่ใช้

ขอ BluetoothManager จาก system services = gatekeeper ของ Bluetooth จากนั้นขอ BluetoothAdapter = hardware จริง nullable เพราะไม่ใช่ทุกอุปกรณ์มี Bluetooth แท็บเล็ตบางตัวอาจไม่มี hardware

จาก adapter ขอ BluetoothLeScanner = object ที่拿来สแกน ใช้ safe call (?.) เพราะถ้า adapter null ก็ขอ scanner ไม่ได้ Defensive programming แยกแอปที่ crash ไปเรื่อยๆ กับแอปที่ handle edge cases ได้ดี

กำหนด Callback

นี่คือหูของแอปในโลก Bluetooth ScanCallback ฟังผลลัพธ์ Override สองเมธอด: onScanResult และ onScanFailed

private val scanCallback = object : ScanCallback() {
    override fun onScanResult(callbackType: Int, result: ScanResult) {
        // เจออุปกรณ์!
        // result มี device, RSSI, advertisement data
        Log.d("BleScanner", "Found device: ${result.device.address}, RSSI: ${result.rssi}")
    }
    override fun onScanFailed(errorCode: Int) {
        // จักรวาลบอกให้พัก หรือ เกิดอะไรผิดพล้ายาก
        Log.e("BleScanner", "Scan failed with error code: $errorCode")
    }
}

ScanCallback นี้คือหูของแอป ตอน scanner เจออุปกรณ์ มันไม่แค่เก็บข้อมูล มัน callback ให้แอปผ่าน object นี้ Event-driven programming classic ทำให้แอป responsive ไม่ block main thread

onScanResult เรียกทุกครั้งที่เจออุปกรณ์ตรง filters (หรือทุกตัวถ้าไม่มี filters) result parameter = กองทอง: BluetoothDevice (MAC address, name), RSSI (Received Signal Strength Indicator - ใกล้แค่ไหน ค่าสูง = ใกล้), raw advertisement data

ตัวอย่าง log แค่ MAC และ RSSI แต่ real app จะ update UI, add to list, หรือ trigger connection

callbackType บอกว่าทำไมถึง callback: CALLBACK_TYPE_ALL_MATCHES (default = ทุกตัว), CALLBACK_TYPE_FIRST_MATCH (เจอครั้งแรก), CALLBACK_TYPE_MATCH_LOST (ไม่เจอนานแล้ว อาจจากไป) จะเจาะลึกใน advanced section

onScanFailed = เมธอดที่ไม่อยากให้เรียก แต่ต้อง handle เสมอ เรียกตอน disaster: Bluetooth adapter ปิดระหว่างสแกน, permission ไม่ถูกต้อง, หรือ controller โรคจิต errorCode ให้ hint ควร log และ handle gracefully เช่น show message หรือ restart scan หลัง delay

ตั้งค่าการสแกน (ScanSettings)

สร้าง ScanSettings บอก Android เราอยากเป็นนินจาประหยัดแบต

val scanSettings = ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) // ใจดีกับแบต
    .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
    .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
    .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) // รายงานทุก advertisement
    .setReportDelay(0L) // รายงานทันที
    // ซุปเปอร์สตาร์ของเรา!
    .setScanType(ScanSettings.SCAN_TYPE_PASSIVE)
    .build()

ScanSettings = คู่มือสั่งการสำหรับ scanner แต่ละ method tune พฤติกรรมสแกน การตั้งค่าถูกต้อง = แยกแอปประหยัดแบต กับแอปที่ถูกลบภายในชั่วโมง

มาวิเคราะห์แต่ละ setting:

  • setScanMode(SCAN_MODE_LOW_POWER) = low-power scanning mode สแกนแบบ intervals ไม่ใช่ต่อเนื่อง เหมาะสำหรับส่วนใหญ่ ไม่ต้องการผลทันที ต้องการประหยัดแบต ตื่นสแกน นอน ตื่นสแกน นอน = power naps

  • setCallbackType(CALLBACK_TYPE_ALL_MATCHES) = notify ทุกครั้งที่เจอ default behavior ใช้ส่วนใหญ่ มี FIRST_MATCH และ MATCH_LOST สำหรับ presence detection ขั้นสูง

  • setMatchMode(MATCH_MODE_AGGRESSIVE) = hardware พยายาม match กับ filters อย่าง aggressive = รายงานเร็ว แม่นยำน้อยกว่า STICKY = รอจนมั่นใจ Aggressive = เร็วแต่อาจ false positive

  • setNumOfMatches(MATCH_NUM_ONE_ADVERTISEMENT) = รายงานหลังเจอ advertisement เพียงครั้งเดียว ทางเลือก MATCH_NUM_FEW_ADVERTISEMENT = รอหลายครั้ง หนึ่งครั้ง = ค้นหาเร็ว หลายครั้ง = ลด false positives จากอุปกรณ์ผ่านไป

  • setReportDelay(0L) = รายงานทันที ถ้าตั้ง 5000ms = batch results ส่งทุก 5 วินาที Batching ดีสำหรับ background scanning แต foreground ที่ user รอ = immediate

  • setScanType(SCAN_TYPE_PASSIVE) = สตาร์ของเรา! API ใหม่ Android 16 QPR2 ทำให้ scanner กลายเป็น silent listener แทนที่จะส่ง scan requests ทุกตัว มันแค่ฟัง advertisements ล่องลอย ลดแบต consumption อย่างมาก ฟีเจอร์ที่รอคอย มาแล้ว!

เริ่มและหยุดการสแกน

ฟังก์ชัน start/stop จำ: หยุดสแกนเสมอ! สแกนที่ลืม = รाक्षสกินแบต

private fun startBleScan() {
    // อย่าลืมขอ permissions ก่อน!
    if (bleScanner != null) {
        // เพิ่ม ScanFilters ที่นี่ เพื่อหาอุปกรณ์เฉพาะ
        val scanFilters: List<ScanFilter> = listOf()
        bleScanner.startScan(scanFilters, scanSettings, scanCallback)
        Log.d("BleScanner", "Scan started.")
    } else {
        Log.e("BleScanner", "Bluetooth is not available.")
    }
}
private fun stopBleScan() {
    if (bleScanner != null) {
        bleScanner.stopScan(scanCallback)
        Log.d("BleScanner", "Scan stopped.")
    }
}

สองฟังก์ชันนี้ = สวิตช์ on/off ของ scanner ดูง่ายแต่สำคัญมาก

startBleScan(): เช็ค bleScanner ไม่ null = safety net ถ้าไม่มี hardware หรือ Bluetooth ปิด scanner = null ไม่อยาก crash เรียก startScan() 3 params: ScanFilter list, ScanSettings, ScanCallback

scanFilters ว่าง = หาทุก BLE devices Real app จะเพิ่ม filters เช่น หา heart rate monitors เท่านั้น → filter service UUID สำคัญสำหรับ performance และแบต: ทำไมต้อง喚醒แอปสำหรับแปรงสีฟัน Bluetooth ถ้าอยากแค่ fitness tracker?

startScan() เริ่มสแกน asynchronous ไม่ block รอผลลัพธ์ ผลลัพธ์มาผ่าน callback

stopBleScan() = ฟังก์ชันสำคัญที่สุด เรียก stopScan() = บอกวิทยุ "พอแล้ว นอนไป" หยุดทันที ปล่อยทรัพยากร ถ้าไม่เรียก = สแกนวิ่งต่อเรื่อยๆ กินแบต כמו vampire ที่ all-you-can-eat blood bank

ส่ง scanCallback เดียวกับตอน startScan() Android รู้ว่าหยุด scan ไหน (อาจมีหลาย scans กับ callbacks ต่างกัน) ใช้ callback reference เดียวกันเสมอ

รวมทั้งหมด

ตัวอย่างสมบูรณ์ drop ใน Activity ได้เลย จำได้ว่า: handle runtime permissions!

// ใน Activity class
class MainActivity : AppCompatActivity() {
    // ... (lazy properties adapter และ scanner จากด้านบน)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ... UI setup ...
        // เริ่มสแกนตอนกดปุ่ม
        val startButton = findViewById<Button>(R.id.startButton)
        startButton.setOnClickListener {
            // ต้อง request permissions ก่อนเรียก!
            startBleScan()
        }
        // หยุดสแกนตอนกดปุ่มอีกตัว
        val stopButton = findViewById<Button>(R.id.stopButton)
        stopButton.setOnClickListener {
            stopBleScan()
        }
    }
    // ... (scanCallback, startBleScan, stopBleScan functions)
    override fun onPause() {
        super.onPause()
        // หยุดสแกนเสมอตอน activity ไมermost visible
        stopBleScan()
    }
}

ตัวอย่างข้างบนแสดงว่า pieces ทั้งหมด fit กันยังไงใน Activity จริง Minimal แต่ functional Bluetooth scanner ที่ run ได้จริง เน้นจุดสำคัญ:

  • ผูก scan lifecycle กับ user actions ผ่าน button clicks = UX ดีและแบตดี สแกนตอน user อยากได้
  • onPause() override = safety net สำคัญ Activity ไป background (home button, switch app) → onPause() → หยุดสแกนทันที User ไม่เห็นแอป = ไม่ต้องการผลสแกน = ไม่มีเหตุผลกินแบต Pattern นี้กันสแกนวิ่ง background ตลอดกาลแม้ user ลืมกด Stop
  • onResume() ควร restart scan ไหม? Design decision บางแอป auto-resume บางแอปให้ user กด Start ใหม่ Device-finding app = auto-resume เหมาะสม Monitoring app = explicit control
  • Runtime permission handling ยังไม่ได้โชว์ในตัวอย่าง Permission ใน manifest ไม่พอ Android 6+ ต้อง request runtime ด้วย ก่อน startBleScan() เช็ค permission → request ด้วย ActivityCompat.requestPermissions() ถ้าเริ่มสแกนไม่มี permission = fail เงียบๆ (หรือดังๆ) = หน้ามุมงง

เสร็จแล้ว! สร้าง AOSP 16 Passive Bluetooth Scanner แรกแล้ว เบา เร็ว ประหยัดพลังงานมาก ฟังเงียบๆ รายงานผ่าน callback หยุด graceful ตอนไม่ต้องการ

ต่อไป: ตอนผิดพลาด จัดการ breakups... Bluetooth bond breakups นั่นแหละ

เจาะลึก #2: เหตุผลการสูญเสีย Bluetooth Bond (Bond Loss Reasons)

Bluetooth bond = สิ่งศักดิ์สิทธิ์ เหมือนแลกสร้อยข้อมือมิตรภาพ Bond โทรศัพท์กับหูฟัง = สร้างความสัมพันธ์ระยะยาว ไว้ใจกัน แลก secret keys จดจำกัน เชื่อมต่ออัตโนมัติ ไม่ต้อง pair ใหม่ทุกครั้ง โรแมนติก

จนวันหนึ่ง... ลืมกัน 連接หาย ความไว้วางใจพัง แอปกลายเป็น therapist ที่ไม่รู้เหตุผล ได้แต่ notification bond state = BOND_NONE ไม่มีอธิบาย ไม่มี closure แค่เงียบเย็นของการเชื่อมต่อล้มเหลว

สุดท้ายก็ได้ Closure!

แต่อ-android team ผ่าน breakups ยากมาแล้ว เพราะ AOSP 16 ให้ของขวัญ closure: BluetoothDevice.EXTRA_BOND_LOSS_REASON Extra ใหม่มาพร้อม ACTION_BOND_STATE_CHANGED broadcast บอกว่าทำไม bond ถึงหาย เหมือนข้อความเลิกที่อธิบายเหตุผลจริง!

ตอน bond ทूट คุณได้ reason code เหมือนข้อความเลิกแบบคลาสสิก แต่สำหรับ Bluetooth:

Reason Code (ตัวอย่าง)ความหมายจริง
BOND_LOSS_REASON_BREDR_AUTH_FAILUREBREDR authentication failure
BOND_LOSS_REASON_BREDR_INCOMING_PAIRINGBREDR pairing failure
BOND_LOSS_REASON_LE_ENCRYPT_FAILURELE encryption failure
BOND_LOSS_REASON_LE_INCOMING_PAIRINGLE pairing failure

โค้ด: เล่นนักสืบ

ยังไงได้ข่าวลือนี้? ตั้ง BroadcastReceiver ฟัง bond state changes

// BroadcastReceiver ฟัง bond state changes
private val bondStateReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
            val device: BluetoothDevice? = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
            val bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR)
            val previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.ERROR)
            // เช็ค transition จาก bonded → not bonded
            if (bondState == BluetoothDevice.BOND_NONE && previousBondState == BluetoothDevice.BOND_BONDED) {
                Log.d("BondBreakup", "เราถูกเลิกโดย ${device?.address}!")
                // หาสาเหตุ...
                val reason = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_LOSS_REASON, -1)
                when (reason) {
                    // ค่า constant จริงอยู่ใน Android SDK
                    BluetoothDevice.BOND_LOSS_REASON_REMOTE_DEVICE_REMOVED -> {
                        Log.d("BondBreakup", "Reason: อุปกรณ์ remote ลบ bond")
                        // แสดงข้อความผู้ใช้: "หูฟังลืมคุณแล้ว ลอง pair ใหม่"
                    }
                    // ... handle reasons อื่นๆ ...
                    else -> {
                        Log.d("BondBreakup", "Reason: ซับซ้อน (Unknown reason code: $reason)")
                    }
                }
            }
        }
    }
}
// ใน Activity หรือ Service register receiver
override fun onResume() {
    super.onResume()
    val filter = IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
    registerReceiver(bondStateReceiver, filter)
}
override fun onPause() {
    super.onPause()
    // อย่าลืม unregister!
    unregisterReceiver(bondStateReceiver)
}

โค้ดด้านบน = ระบบสืบสวน bond breakups ซับซ้อนกว่าที่มอง มาดูว่า broadcast receiver pattern ทำงานยังไง และทำไมถึง powerful

สร้าง BroadcastReceiver = Android ให้แอปฟัง system-wide events เหมือน subscribe notification service ทุกครั้งที่มี event น่าสนใจ (bond state change) ระบบ broadcast "intent" ให้ listeners เราเป็นหนึ่งในนั้น

ใน onReceive() เช็ค action = ACTION_BOND_STATE_CHANGED สำคัญ เพราะ receiver อาจได้ intents หลายประเภท เราสนใจแค่ bond state changes ดึงข้อมูลจาก intent ด้วย getParcelableExtra() และ getIntExtra()

device = อุปกรณ์ไหนที่ event เกี่ยวข้อง อาจ bonded หลายตัว (หูฟัง, นาฬิกา, รถ) ต้องรู้ว่าใครเลิกเรา bondState = state ปัจจุบัน previousBondState = state ก่อนเปลี่ยน

logic สำคัญ: if (bondState == BluetoothDevice.BOND_NONE && previousBondState == BluetoothDevice.BOND_BONDED) เช็ค transition bonded → not bonded = breakup digital ไสนใจ bonding process (none → bonding → bonded) สนใจตอน bond ที่มีอยู่หาย

ดึง EXTRA_BOND_LOSS_REASON ใหม่จาก intent = สตาร์ฟีเจอร์ AOSP 16 ให้ closure reason code บอกเหตุผลเฉพาะ: remote device เริ่มเลิก? user ลบ manual? auth fail? แต่ละ code = scenario ต่างกัน handle ตามสถานการณ์

when expression handle reason codes ตัวอย่าง BOND_LOSS_REASON_BREDR_INCOMING_PAIRING = อุปกรณ์อีกฝ่ายเริ่มเลิก → show message helpful "หูฟังลืมคุณ ลอง pair ใหม่" reasons อื่นๆ add branches เพิ่ม

Lifecycle management: register ใน onResume() unregister ใน onPause() สำคัญมาก: ลืม unregister = receiver รับ broadcasts ต่อหลัง activity destroyed = memory leaks และ crashes Pattern register onResume/unregister onPause = ฟัง bond changes เฉพาะตอน Activity visible และ active

ก้าวใหญ่สำหรับ debugging และ UX แทนที่บอก "Connection failed" ตอนนี้ให้คำแนะนำเฉพาะเจาะจงจากเหตุผล bond lost = เป็นที่ปรึกษาความสัมพันธ์ที่ช่วยเหลือได้ แทนผู้ชมงงที่ไหว้แค่ "ไม่รู้เกิดอะไรขึ้น"

จัดการอารมณ์ breakups แล้ว ไปเรื่องเบาๆ: speed dating สำหรับ Bluetooth devices

เจาะลึก #3: Service UUIDs จาก Advertisements

คุยหาคู่ที่เข้ากัน... สำหรับแอป ในโลก BLE อุปกรณ์ไม่ได้เท่ากัน Heart rate monitor ต่าง smart bulb แอปรู้ยังไงว่าคุยกับอุปกรณ์ประเภทไหน? คำตอบ: Service UUID

Service UUID คืออะไร?

Service UUID (Universally Unique Identifier) = ตำแหน่งงานของอุปกรณ์ ตัวเลข 128-bit unique บอกว่า "ฉันคืออุปกรณ์ให้ Heart Rate Service" หรือ "ฉันให้ Battery Service" ข้อมูลสำคัญที่สุดในการกำหนดว่าอุปกรณ์ทำอะไรได้

วิธีเก่า: First Date แบบอึ้ง

หา services ของอุปกรณ์เดิม = กระบวนการยาว เหมือนไป date 3 คอร์สเพื่อรู้อาชีพของอีกฝ่าย:

  1. Scan: หาอุปกรณ์
  2. Connect: เชื่อมต่อ (ช้า กินแบต)
  3. Discover Services: ถาม "คุณทำอะไร?" รอ list services
  4. Evaluate: เช็คว่ามี service ที่เราอยากได้ไหม
  5. Disconnect (หรือ stay): ถ้าไม่ใช่ = หยุดคุย (disconnect) ไปต่อ เหลื่อมเวลาและพลังงาน!

ไม่มีประสิทธิภาพมาก โดยเฉพาะตอนอยู่ห้องแคบ มี BLE devices โหลดๆ แต่อยากแค่ตัวเดียว

วิธีใหม่: Name Tag สว่างไสว

ดีกว่ามั้ยถ้าทุกคนในงานใส่ name tag ใส่ตำแหน่งงาน? AOSP 16 ให้มาพร้อม BluetoothDevice.EXTRA_UUID_LE หลาย BLE devices แล้วใส่ primary service UUID ใน advertisement packets ว่าด้วยการตะโกน "ฉันคือ HEART RATE MONITOR!" ให้ทุกคนรู้

ก่อน AOSP 16 ดึง info นี้จาก advertisement packet = manual parsing raw byte array ของ scan record ทำได้แต่โค้ดแบบเขียนครั้งเดียว ขอให้ทำงาน แล้วไม่กล้าแก้เลย

ตอนนี้ Android parse ให้แล้ว! ระบบ parse advertising data อัตโนมัติ ถ้ามี service UUIDs ส่งมาให้ใน ScanResult เหมือน speed dating!

โค้ด: อ่าน Name Tag

ฟีเจอร์ใหม่นี้ทำ ScanCallback powerful ขึ้น เช็คตำแหน่งงานทันทีตอนเจอ โดยไม่ต้อง connect

private val scanCallback = object : ScanCallback() {
    override fun onScanResult(callbackType: Int, result: ScanResult) {
        Log.d("BleSpeedDating", "Found device: ${result.device.address}")
        // เช็ค name tag!
        val serviceUuids = result.scanRecord?.serviceUuids
        if (serviceUuids.isNullOrEmpty()) {
            Log.d("BleSpeedDating", "คนนี้ลึกลับ ไม่มี service UUIDs ใน ad")
            return
        }
        // UUID ที่เราหา (เช่น Heart Rate Service UUID มาตรฐาน)
        val heartRateServiceUuid = ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
        if (serviceUuids.contains(heartRateServiceUuid)) {
            Log.d("BleSpeedDating", "Match! นี่คือ heart rate monitor Connect ไปเลย!")
            // connect ไป result.device รู้ว่าเป็นตัวถูกต้อง
            stopBleScan() // เจอแล้ว หยุดสแกน
            // connectToDevice(result.device)
        } else {
            Log.d("BleSpeedDating", "ไม่ match เดินต่อ")
        }
    }
    // ... onScanFailed ...
}

โค้ดด้านบน demo ความ 강력한ของการอ่าน service UUIDs จาก advertisement data game-changer สำหรับ device discovery มาแยกวิเคราะห์

scan result มี scanRecord property = raw advertisement packet ที่ BLE device broadcast

ก่อน AOSP 16 extract service UUIDs = manual parse byte array เข้าใจ BLE advertisement format handle data types ต่างๆ ขอให้ไม่ off-by-one error โค้ดแบบเขียนครั้งเดียว แล้วไม่กล้าสัมมือ

ตอนนี้ Android parse ให้เรียบร้อย เรียก result.scanRecord?.serviceUuids ได้ list ของ ParcelUuid สะอาด Safe call (?.) สำคัญ เพราะไม่ใช่ทุกอุปกรณ์มี scan record

เช็ค null หรือ empty บางอุปกรณ์ไม่ใส่ service UUIDs ใน advertisements proprietary format หรือ config ไม่ดี ถ้าไม่มี UUIDs log แล้ว return ไม่ต้องทำต่อ

กำหนด UUID ที่หา ตัวอย่างหา heart rate monitors → standard Heart Rate Service UUID: 0000180D-0000-1000-8000-00805F9B34FB UUID มาจาก Bluetooth SIG heart rate monitor compliant ตัวไหนก็ advertise UUID นี้ หา UUID มาตรฐานอื่นๆ ได้จาก Bluetooth specs หรือ custom UUIDs ถ้าสร้าง peripheral เอง

เวทมนตร์: if (serviceUuids.contains(heartRateServiceUuid)) speed dating: เช็ค name tag ว่าตรงกับที่เราหาไหม

ถ้าตรง = เจอ match! หยุดสแกนทันที (ทำไมต้องหาต่อ?) connect ไป device รู้แน่ๆ ว่าเป็น heart rate monitor ไม่เสียเวลาแบต connect random devices แล้วค่อยรู้ว่าไม่ใช่

ถ้าไม่ match = log "ไม่ match" เดินต่อ callback เรียกใหม่ตอนเจอ device ถัดไป วนจนเจอ heart rate monitor หรือ user หยุดสแกน

ปรับปรุงประสิทธิภาพมากกว่าเก่ามาก เก่า: connect ทุกตัว → service discovery (multiple round-trips) → เช็ค services → disconnect ถ้าไม่ใช่ แต่ละ connection = เวลา แบต radio traffic

ตอนนี้: filter และ identify อุปกรณ์เร็วโหด ตั้งแต่ scanning stage ไม่มี awkward first dates connect smart bulb คิดว่า fitness tracker แค่การเชื่อมต่อที่มีเป้าหมาย ประหยัดพลังงาน

มีประโยชน์มากสำหรับแอปที่หา sensor/peripheral เฉพาะในทะเลอุปกรณ์ irrelevant โรงพยาบาลหลายร้อย medical devices หรือ smart home โหลด sensors/actuators ระบุอุปกรณ์ถูกต้องจาก advertisement ทันที = แอป responsive professional ต่างจาก sluggish unreliable

เจอสามยอดนักดาบครบแล้ว: passive scanning ประหยัดแบต, bond loss reasons debug ดีขึ้น, service UUIDs from ads identify เร็วขึ้น แต่การเดินทางยังไม่จบ ไป advanced scanning techniques กัน

หัวข้อขั้นสูง: Filtering, Batching และเวทย์มนต์อื่นๆ

คุณ master basics แล้ว สแกน passive ได้ ได้ closure breakups speed-date devices แล้ว ไม่ใช่ Bluetooth padawan แล้ว กลายเป็น Jedi Master

ลงลึก advanced arts: filtering, batching, optimization sorcery ทำให้แอปคุณเป็นแชมป์ประหยัดแบตจริงๆ

Hardware Filtering: Personal Assistant ของคุณ

จินตนาการ: คุณดารา จ้าง personal assistant ไม่อยากให้รบกวนทุกคนที่อยากได้ autograph ให้ assistant list: "แจ้งแค่ agent หรือแม่ของฉันเท่านั้น" Assistant ยืนประตู แจ้งตอนคนใน list มา

Hardware filtering = แทนที่โค้ดแอป (ดารา) จะถูก喚醒ทุกครั้งที่ radio เจอ device ให้ Bluetooth controller (assistant) handle filtering logic เอง มีมาตั้งแต่ Android 6.0 แตสำคัญกว่าเดิม

ทำไมดี? โค้ดแอปนอนได้ main processor (AP) ไม่ต้องตื่นทุกครั้งที่แปรงสีฟัน Bluetooth แจ้งตัว Bluetooth controller ที่ power-efficient กว่า handle filtering AP ตื่นตอน controller เจอ device ตรง criteria

โค้ด: สร้าง VIP List

ใช้ ScanFilter กรองโดย name, MAC address, หรือ useful ที่สุด: Service UUID ที่ advertise

// เราอยากรบกวนแค่ heart rate monitor
val heartRateServiceUuid = ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
val filter = ScanFilter.Builder()
    .setServiceUuid(heartRateServiceUuid)
    .build()
val scanFilters: List<ScanFilter> = listOf(filter)
// ตอน start scan ส่ง list นี้
bleScanner.startScan(scanFilters, scanSettings, scanCallback)

โค้ดข้างบน = hardware-level filter ปรับปรุงแบตและ performance มาก มา deep dive

กำหนด service UUID ที่สนใจ = Heart Rate Service UUID เดิม แต่ใช้แบบต่างกัน: แทนที่เช็ค UUID ในโค้ดหลังรับ results บอก hardware ให้ report เฉพาะ device ที่ match UUID นี้

ScanFilter.Builder() = tool สร้าง filter builder pattern chain methods กำหนด criteria ตัวอย่าง setServiceUuid() = match devices ที่ advertise service นี้

Builder มี options อื่นๆ:

  • setDeviceName() - match ชื่อเฉพาะ (เช่น "My Heart Monitor")
  • setDeviceAddress() - match MAC address เฉพาะ (มีประโยชน์ถ้า paired แล้วอยากหาใหม่)
  • setManufacturerData() - match manufacturer data ใน advertisements
  • setServiceData() - match service data ใน advertisement

รวม criteria หลายตัวใน filter เดียวได้ เช่น match service UUID และ manufacturer ID ยิ่ง specific ยิ่งน้อย false positives

สร้าง list ของ filters ทำไม list? เพราะ multiple filters device match ถ้า match any filterใน list เช่น filter หนึ่ง heart rate อีกตัว blood pressure scan report devices match either = OR operation

ส่ง list ไป startScan() พร้อม settings และ callback = จุดที่ magic เกิดขึ้น ไม่ได้แค่ filter ในโค้ดแอป Android push filters ลง Bluetooth controller hardware เอง filtering เกิดที่ low level ก่อนแอปถูก notify

ทำไม powerful? ไม่มี filters: radio ได้ยิน advertisement จาก ทุก device (smart toaster เพื่อนบ้าน, fitness tracker คนเดินผ่าน, speaker สามห้อง) → ต้อง喚醒แอป process ส่ง scan result ให้โค้ดตัดสินใจ แต่ละ wake-up = ค่าแบตและ processing time

มี hardware filters: controller เงียบๆ ข้าม devices ไม่ match แอปนอน main processor นอน เจอ heart rate monitor เท่านั้น → hardware 唤醒แอป ส่ง result เหมือนบウンサー club ใส่แค่ VIP list คนอื่นถูกไล่ที่ประตู คุณไม่รู้ว่ามาจริง

ScanFilter = บอก hardware "อย่าตื่นฉัน ถ้าไม่เจอ heart rate monitor" Ultimate power-saving move สำหรับ background scanning รวมกับ passive scanning และ batch reporting = ระบบสแกนวิ่งได้ชั่วโมงหรือวัน แบตแทบไม่หาย นี่คือวิธี professional apps handle long-term monitoring

Batch Scanning: รายงานประจำวัน

กลับไปดารา analogy บางครั้งไม่ต้องการรบกวนทันทีที่แม่มา อยากได้รายงานตอนเย็น: "วันนี้แม่มาสองครั้ง agent โทรหนึ่งครั้ง" = Batch scanning

แทนที่ส่ง results real-time controller collect ส่ง batch ใหญ่ Power-saving อีกอย่าง แอปนอนนาน ตื่น process results มากมายครั้งเดียว นอนต่อ

เปิดด้วย setReportDelay() ใน ScanSettings

val scanSettings = ScanSettings.Builder()
    // ... other settings ...
    // ส่งผลทุก 5 วินาที (5000 ms)
    .setReportDelay(5000)
    .build()

ใช้ report delay → onScanResult callback เปลี่ยนเป็น onBatchScanResults ให้ List<ScanResult>

private val scanCallback = object : ScanCallback() {
    override fun onBatchScanResults(results: List<ScanResult>) {
        Log.d("BatchScanner", "รายงานประจำวัน! เจอ ${results.size} devices")
        for (result in results) {
            // process แต่ละ result
        }
    }
    // ... onScanFailed ...
}

Batch scanning mechanism = ฟีเจอร์ประหยัดแบตที่ underutilized ที่สุด การเข้าใจมัน transform battery profile ของแอป

setReportDelay 5000ms = เปลี่ยน scanning pipeline แทนที่ controller 唤醒แอปทุกครั้งที่เจอ device มันจดบันทึก 5 วินาที แอปนอนทั้งหมด ไม่ CPU cycles ไม่ battery drain จาก context switches

หลัง delay หมด controller ส่ง results สะสมทั้งหมดใน batch เดียวไป onBatchScanResults() แทนที่ตื่น 50 ครั้งสำหรับ 50 devices ตื่น 1 ครั้ง รับ 50 results พร้อมกัน แล้ว process efficiently: update UI, log, check devices → นอนรอ batch ถัดไป

results = List<ScanResult> แต่ละ ScanResult = advertisement หนึ่งครั้งในช่วง batching อุปกรณ์เดียว advertise หลายครั้ง → อาจได้ multiple results สำหรับ device เดียวใน batch list ไม่ auto-deduplicate = งานของคุณถ้าต้องการ

ตัวอย่าง log count และ iterate real app อาจ sophisticated มากกว่า: build map keyed by MAC address track จำนวน advertisements, คำนวณ average RSSI ประมาณระยะทาง, filter batch process เฉพาะ devices ที่ตรง criteria

Warning: Batch scanning powerful แต่ไม่ใช่ทุกสถานการณ์ ถ้าต้อง react ทันที (find my keys app user กำลังหา) report delay = ศัตรู User ไม่อยากรอ 5 วินาที อยาก instant feedback กรณีนี้ setReportDelay(0) immediate reporting

แต่ long-term monitoring/data collection = batch scanning = เพื่อนแบต Use cases:

  • Background presence monitoring: เช็คทุกนาทีว่า smartwatch ยังในระยะ ไม่ต้อง update วินาทีละครั้ง
  • Environmental sensing: รวบรวมข้อมูลจาก temperature sensors อาคาร update dashboard ทุก 30 วินาที
  • Beacon analytics: track คนผ่าน retail location จาก BLE advertisements บนโทรศัพท์ aggregate ทุก 10 วินาที

Sweet spot report delay ขึ้นกับ use case สั้นเกิน (1 วินาที) = ไม่ได้ benefit ยังตื่นบ่อย ยาวเกิน (60 วินาที) = unresponsive หรือพลาด time-sensitive events background monitoring = 5-30 วินาที OK

Batch scanning มี limits: controller buffer จำกัด delay ยาว + สภาพแวดล้อมหลายร้อย BLE devices = buffer เต็มก่อน delay หมด → oldest results dropped Android ไม่ warn ตอนเกิด ถ้า missing data → ลด report delay หรือ aggressive filters ลด results

OnFound/OnLost: ดราม่าของการมีอยู่ (Presence)

Android 8.0+ scanning dramatic มากขึ้น ขอให้ hardware บอกตอนเจอ และ ตอนหาย ด้วย CALLBACK_TYPE_FIRST_MATCH และ CALLBACK_TYPE_MATCH_LOST ใน ScanSettings

val scanSettings = ScanSettings.Builder()
    .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH or ScanSettings.CALLBACK_TYPE_MATCH_LOST)
    .build()

ตอนนี้ callbackType parameter ใน onScanResult บอกว่าเกิดอะไรขึ้น:

override fun onScanResult(callbackType: Int, result: ScanResult) {
    when (callbackType) {
        ScanSettings.CALLBACK_TYPE_FIRST_MATCH -> {
            Log.d("PresenceDetector", "เจอแล้ว! ${result.device.address} เข้ามาในอาคาร")
        }
        ScanSettings.CALLBACK_TYPE_MATCH_LOST -> {
            Log.d("PresenceDetector", "หายแล้ว! ${result.device.address} ออกจากอาคาร")
        }
    }
}

Presence detection mechanism = paradigm shift ในการคิด scanning แทน stream "นี่คือสิ่งที่เห็นตอนนี้" กลายเป็น events: "device นี้ปรากฏ" และ "device นี้หาย" ใช้ hardware-level certainty react ต่อการเปลี่ยนแปลง presence โดยไม่ต้อง polling หรือ state machines ซับซ้อน

เมื่อตั้ง callback type ด้วย bitwise OR (or ใน Kotlin, | ใน Java) = บอก hardware track presence state ของ devices ตามเวลา CALLBACK_TYPE_FIRST_MATCH or CALLBACK_TYPE_MATCH_LOST = รวมทั้งสอง flag = อยากรู้ทั้งตอนปรากฏและหาย ใช้แยกได้ถ้าสนใจแค่อย่างใดอย่างหนึ่ง แต่รวมกัน = complete presence awareness

เข้าใจ "first match" และ "match lost": controller ได้ยิน advertisement จาก device match filters ครั้งแรก → trigger FIRST_MATCH ต่างจาก ALL_MATCHES (default) ที่ trigger ทุกครั้งที่ device advertise device อาจ advertise หลายครั้งต่อวินาที ความต่าง = มาก FIRST_MATCH = notification ครั้งเดียวตอน device เข้ามาใน scanning range

MATCH_LOST น่าสนใจกว่า: controller track last heard ของแต่ละ device ถ้า device หยุด advertise (out of range, ปิด, แบตตาย) controller สังเกต absence → trigger MATCH_LOST อัตโนมัติ ไม่ต้อง track timestamps หรือ implement timeout logic เอง hardware ทำให้

Hardware รู้ยังไงว่า device "lost"? ใช้ internal timeout ถ้าไม่ได้ยิน device ช่วงหนึ่ง (typically 몇 초 implementation-dependent ไม่ expose ให้ apps) ถือว่า lost = มี delay เล็กน้อยระหว่าง device จริงๆ ออกจากระยะกับ MATCH_LOST callback ปกติ acceptable สำหรับ presence detection

when expression handle callback types FIRST_MATCH = device เข้า scanning range → log "เจอแล้ว" Perfect สำหรับ trigger actions: unlock door ตอนโทรศัพท์เข้าใกล้, start sync ตอน fitness tracker detected

MATCH_LOST = device ออกจากระยะหรือหยุด advertise → log "หายแล้ว" Ideal สำหรับ cleanup: lock door ตอนโทรศัพท์จากไป, stop sync ตอน tracker disconnect

Useful สำหรับ presence detection: smart lock ในระยะไหม? fitness tracker connected? user's phone ใกล้ไหม? รู้ได้ด้วย hardware certainty react ได้โดยไม่ต้อง polling

ตัวอย่าง practical smart home app:

private val presenceCallback = object : ScanCallback() {
    override fun onScanResult(callbackType: Int, result: ScanResult) {
        when (callbackType) {
            ScanSettings.CALLBACK_TYPE_FIRST_MATCH -> {
                // โทรศัพท์ผู้ใช้เจอ = กลับบ้าน!
                Log.d("SmartHome", "Welcome home! Unlocking door and turning on lights.")
                unlockFrontDoor()
                turnOnLights()
                adjustThermostat(COMFORTABLE_TEMP)
            }
            ScanSettings.CALLBACK_TYPE_MATCH_LOST -> {
                // โทรศัพท์หาย = ออกไป!
                Log.d("SmartHome", "Goodbye! Locking door and entering away mode.")
                lockFrontDoor()
                turnOffLights()
                adjustThermostat(ENERGY_SAVING_TEMP)
                armSecuritySystem()
            }
        }
    }
    override fun onScanFailed(errorCode: Int) {
        Log.e("SmartHome", "Presence detection failed: $errorCode")
    }
}

สำคัญ: FIRST_MATCH และ MATCH_LOST mutually exclusive กับ CALLBACK_TYPE_ALL_MATCHES combine กับ ALL_MATCHES = behavior undefined varies by device อย่าใช้ทั้งสองพร้อมกัน

Presence detection ทำงานดีที่สุดรวมกับ hardware filtering ถ้า scan ทุก device ไม่มี filters controller track presence state ทุก BLE device ในระยะ = overwhelm internal tracking tables ใช้ ScanFilter กรอง devices ที่สนใจตอนใช้ presence detection

รวม advanced techniques: hardware filtering, batch scanning, presence detection = สร้าง Bluetooth apps sophisticated และ power-efficient มาก ไม่ใช่แค่นักพัฒนาแล้ว คุณคือ Bluetooth wizard สร้างแอป aware of surroundings, responsive to changes, respectful of battery

ไปดู real-world applications ที่ใช้พลังเหล่านี้

Real-World Use Cases: จุดที่ Bluetooth ติดถนนจริง

เรียนรู้ tricks มากแล้ว Bluetooth black belts แล้ว แต่นำไปใช้ทำดี (หรือแอป cool) ไปดู scenarios จริงที่ฟีเจอร์ AOSP 16 ทำให้ good app → great app

1. แอป "Find My Everything"

ทุกคนเคย: 遲到ทำงาน กุญแจแจงเล่น hide-and-seek ในมิติอื่น Classic use case สำหรับ BLE tracker

  • วิธีเก่า: แอป active scans ตลอด กินแบตขณะคุณหา ต่อทุก tracker ในบ้านเช็คว่าตัวไหนถูก
  • วิธี AOSP 16: แอป passive scan background ด้วย hardware filter service UUID ของ tracker แบต impact น้อย ตอนเปิดแอปหากุญแจ มันรู้แล้วว่าอยู่ในบ้าน เพราะฟังเงียบๆ มาแล้ว กด "Find" → connect → กุญแจสcream จากในโซฟา connection fail? bond loss reason บอกว่า tracker แบตตาย → ไม่ไปหาอุปกรณ์ตาย

2. ซุปเปอร์มาร์เก็ตอัจฉริยะ

แอปให้คูปองสินค้าเวลาเดินผ่านในร้าน ความฝัน proximity marketing ที่ล้มเหลวเพราะ... battery drain

  • วิธีเก่า: แอปต้อง scan beacons ตลอด โทรศัพท์ร้อน แบตตายก่อนถึงแคชเชียร์
  • วิธี AOSP 16: ซุปเปอร์มาร์เก็ตวาง BLE beacons ทุกช่องทาง แอป passive batched scan ตื่นทุกนาที ได้ list beacons ที่เจอ → นอนต่อ เจอคุณโหลยที่ช่องคุกกี้ 5 นาที (มันรู้ มันรู้ทุกอย่าง) ใช้ Service UUID จาก advertisement identify "Cookie Aisle Beacon" ส่งคูปอง Oreos targeted, efficient, ไม่กินแบตก่อนจ่ายเงิน

3. Smart Home ที่ติดเกินไป

Smart home ควร smart รู้ตอนกลับบ้าน ตอนออกไป Lock door หลังคุณ เปิดไฟตอนถึง

  • วิธีเก่า: GPS (กินแบตหนัก) หรือ Wi-Fi (unreliable) BLE เป็นทางเลือกแต่ constant scanning = ปัญหา
  • วิธี AOSP 16: โทรศัพท์ = กุญแจ Smart hub (central) passive scan ต่อเนื่อง low-power เจอ advertisement โทรศัพท์ = รู้ว่าคุณอยู่บ้าน แตถ้าแค่เดินผ่านบ้าน? OnFound/OnLost ช่วย: hub config ให้ trigger "Welcome Home" แค่เจอ device consistently 1 นาที (OnFound) และ "Goodbye" แค่ไม่เจอ 5 นาที (OnLost) Presence detection อัจฉริยะ reliable ทำให้ smart home 感覺... smart จริง

4. Corporate Asset Tracker

โรงพยาบาล/คลังสินค้าใหญ่ track อุปกรณ์แพงเคลื่อนที่ (IV pumps, forklifts) = ชallenge ใหญ่ BLE tags = solution

  • วิธีเก่า: พนักงานเดิน tablet active scans inventory ช้า manual inefficient
  • วิธี AOSP 16: Network ของ fixed BLE gateways ติดทั่วอาคาร Gateway = device เรียบง่าย (Raspberry Pi) passive scan ต่อเนื่อง รวบรวม advertisement data จาก asset tags ส่ง central server Server เห็น real-time: IV Pump #34 อยู่ห้อง 201, Forklift #3 อยู่ loading bay ไม่ต้อง manual scan Real-time location system ต่ำต้นทุน 低功耗 ขอบคุณ passive scanning efficiency

แค่ตัวอย่าง จาก fitness trackers ถึง industrial sensors ฟีเจอร์ Bluetooth ใหม่ใน AOSP 16 เปิดโลกความเป็นไปได้ สร้างแอป powerful และ polite กับแบตผู้ใช้ ต่อไป: ทำยังไงให้แอป shiny ใหม่ทำงานบนทุกอุปกรณ์ ไม่ใช่แค่เครื่องใหม่

API Version Checking: ไม่ให้แอป Crash

สร้างแอปสวย ประหยัดแบต ใช้ฟีเจอร์ใหม่ AOSP 16 Q4 release พร้อม ship เห็นเงินล้าน เกษียณเกาะส่วนตัว แล้ว bug report มา: แอป crash บน Android 16 device ใหม่ ทำไม?! ยินดีต้อนรับสู่ API version checking ด้วย release schedule ใหม่ สิ่งนี้สำคัญ (และซับซ้อน) กว่าเดิม

ปัญหา: เรื่องของสอง Android 16

2025 ให้ Android 16 สองรุ่น:

  • Q2 Release: "Baklava" หลัก API level 36.0
  • Q4 Release: Minor feature-drop ฟีเจอร์ Bluetooth ใหม่เราอยู่ที่นี่ API level 36.1

Passive scanning API setScanType() มีแค่ 36.1+ เรียกบน device รัน Q2 release (36.0) → crash NoSuchMethodError เหมือนสั่งอาหารเมนูที่เพิ่งเพิ่มคืนนี้ เชฟ (แอป) หงุดหงิด crash

Old Guard: SDK_INT

หลายปี Build.VERSION.SDK_INT เป็นเพื่อนเช็ค API level เรียบง่าย

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    // ใช้ API จาก Android 12 (S) ขึ้นไป
}

SDK_INT รู้แค่ major releases ทั้ง Q2 และ Q4 Android 16 SDK_INT บอกแค่ 36 ไม่รู้ minor version เหมือนถามอายุ ตอบ "สามสิบอย่าง" ไม่ specific

New Hotness: SDK_INT_FULL

Android team ให้ tool แม่นยำกว่า: Build.VERSION.SDK_INT_FULL รู้ major และ minor version codes ใหม่: Build.VERSION_CODES_FULL

เรียก passive scanning API อย่างปลอดภัย ต้อง check specific:

// สร้าง ScanSettings
val scanSettingsBuilder = ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
// เช็คว่า passive ได้ไหม
if (Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.BAKLAVA_1) {
    Log.d("ApiCheck", "Device นี้ cool ไป passive")
    // API ใหม่จาก Q4 release (36.1)
    scanSettingsBuilder.setScanType(ScanSettings.SCAN_TYPE_PASSIVE)
} else {
    Log.d("ApiCheck", "Device เก่า school อยู่ active scanning")
    // Fallback สำหรับ device ไม่มี API ใหม่
    // ไม่ต้องทำอะไร active เป็น default
}
val scanSettings = scanSettingsBuilder.build()

Graceful Degradation: ศิลปะของการล้มด้วยสไตล์

Concept สำคัญ: graceful degradation แอปควรทำงานบนอุปกรณ์เก่าได้ แม้ไม่ใช้ latest features ต้อง fallback gracefully

ตัวอย่างข้างบน: ถ้า setScanType ไม่มี → ไม่เรียก แอป default เป็น active scan ไม่ประหยัดเท่าไหร่แตกทำงาน user อุปกรณ์เก่าได้แอป functional user อุปกรณ์ใหม่ได้ optimized experience ทุกชนะ

ตารางช่วยจำ:

ถ้าใช้ API จาก...ใช้ check นี้...
Major Android release (เช่น Android 16 Q2)if (SDK_INT >= VERSION_CODES.BAKLAVA)
Minor feature-drop release (เช่น Android 16 Q4)if (SDK_INT_FULL >= VERSION_CODES_FULL.BAKLAVA_1)

Master API checking ใหม่ = non-negotiable Key สำหรับ modern Android apps ที่ innovative และ stable ตอนรู้ build robust app แล้ว ไป talk fixing ตอนมันพัง

Testing และ Debugging: ส่วนสนุก (ไม่มีใครบอกเลย)

สอง universal truths ใน software development:

  • Works on my machine
  • จะพังแบบสวยงามระหว่าง live demo

Bluetooth development เหมาะกับ truth ที่สอง มัน capricious invisible force ที่มี vendetta กับนักพัฒนา

ต่อยยังไง? Testing และ debugging strategy ไม่ glamorous แต่วิธีเดียว stay sane

Emulator: ดินแดนจินตนาการ

Android Studio emulator = tool ยอดเยี่ยม เร็ว สะดวก simulate devices Bluetooth? ช่วย... ได้บ้าง มี virtual Bluetooth support เปิดได้ แอปคิดว่ามี Bluetooth adapter ดีสำหรับ test UI และเช็คว่าแอปไม่ crash ตอนขอ BluetoothLeScanner

แต่มันไม่ใช่จริง Emulator ไม่ interact กับ radio waves ในห้อง ไม่สามารถหา BLE headphones จริงได้ ต้องลง real world

Real World: ที่ bugs อาศัยอยู่

No substitute สำหรับ testing บน physical devices ทุก manufacturer มี Bluetooth stack รสชาติตัวเอง antenna design quirks วิธีทำให้ชีวิตยากเฉพาะตัว Scan ที่ works บน Pixel อาจ fail บน brand อื่น ทดสอบจริงเท่านั้น

Testing arsenal ควรมี:

  • หลายโทรศัพท์: brands ต่างๆ, Android versions ต่างๆ ยิ่งมากยิ่งดี
  • หลาย BLE peripherals: อย่าทดสอบแค่ประเภทเดียว หา beacons, sensors, wearables หลายตัว จะตกใจว่าพวกมัน behave ต่างกันแค่ไหน

Common Errors: The Usual Suspects

ตอน scan fail จะได้ error code ตัวที่พบบ่อย:

Error Codeปัญหาแก้ยังไง
SCAN_FAILED_ALREADY_STARTEDพยายาม start scan ที่กำลังรันอยู่ตื่นเต้นเกินไป เช็คว่าไม่ได้ call startScan() หลายครั้งโดยไม่ stopScan() ระหว่างทาง
SCAN_FAILED_APPLICATION_REGISTRATION_FAILEDอะไรผิดพื้นฐานกับ app setupError ambiguous มัก permission problem หรือ system bad day ลอง restart Bluetooth
SCAN_FAILED_INTERNAL_ERRORBluetooth stack panicClassic "มันไม่ใช่คุณ คือมัน" Internal issue กับ controller ลองใหม่ภายหลัง
SCAN_FAILED_FEATURE_UNSUPPORTEDพยายามใช้ feature ที่ hardware ไม่ supportอาจพยายาม batch scanning บน device ไม่ support ใช้ API version checks!

Debugging Tools: Ghost-Hunting Kit

ตอนพัง ต้องมี tools ให้เห็นโลก Bluetooth ที่มองไม่เห็น

  • logcat: เพื่อนสนิท เยอะกับ log statements Log ตอน start scan, stop scan, เจอ device, scan fail สร้าง filter สำหรับ tag ของแอป ดู signal ผ่าน noise
  • Android Bluetooth HCI Snoop Log: Holy grail ของ Bluetooth debugging Developer option บันทึกทุก Bluetooth packet in/out 详细มาก overwhelming แต่ ultimate source of truth เปิด log file ใน Wireshark ดู raw conversation ระหว่างโทรศัพท์และ BLE device เหมือน wiretap บนคลื่นวิทยุ
  • nRF Connect for Mobile: Free app จาก Nordic Semiconductor essential tool สำหรับ BLE developer Scan devices, ดู advertising data, connect, explore GATT services แอปคุณหา device ไม่เจอ → เช็ค nRF Connect ก่อน เจอ = ปัญหาแอป ไม่เจอ = ปัญหา peripheral

Testing debugging Bluetooth = marathon ไม่ใช่ sprint ต้องการ patience, methodical approach, self-deprecating humor เยอะ ด้วย tools และ techniques ถูกต้อง ทำให้ Bluetooth ฟัง話ได้

ต่อไป: ทำยังไงให้แอป well-behaved เป็น good citizen ด้าน performance

Performance และ Best Practices: เป็นพลเมือง Bluetooth ที่ดี

เขียนโค้ดที่ทำงาน = 一回事 เขียนโค้ดที่ทำงานดี มีประสิทธิภาพ ไม่ทำให้ user อยากโยนโทรศัพท์ = อีกเรื่อง Bluetooth good citizen = 一件事: battery, battery, battery

Bluetooth radio = hardware powerful แต่ thirsty ทุก moment active = สิบพลังงาน งานของคุณ: ทำให้มันสิบแค่เมื่อจำเป็น Golden rules:

1. อย่าสแกนถ้าไม่จำเป็น

ดูอvious แต่ common mistake ก่อน start scan ถามตัวเอง: "ต้องทำตอนนี้จริงไหม?" User ไม่อยู่หน้าจอที่ต้องการ scan results = อย่าสแกน App ใน background = critical มาก Background scanning = huge drain Android restrict heavily เพราะนั้น

2. หยุดสแกนของคุณ!

พูดซ้ำเพราะสำคัญ: หยุดสแกนเสมอตอนจบ สแกนลี้ลับ = ก๊อกน้ำรั่วแบต จะไหลจนหมด Best practice: ผูก scan lifecycle กับ UI lifecycle

override fun onPause() {
    super.onPause()
    // User ไม่เห็นหน้าจอ = ไม่ต้องการผลลัพธ์
    stopBleScan()
}
override fun onResume() {
    super.onResume()
    // User กลับมาหน้าจอ เริ่มสแกนใหม่
    startBleScan()
}

เจอ device ที่หา = หยุดสแกนทันที ไม่ต้องหาต่อ

3. เลือก Scan Mode ให้ถูก

ScanSettings ให้ modes หลายแบบ เลือกฉลาด:

  • SCAN_MODE_LOW_POWER: Default everyday mode สแกน intervals balance discovery speed และ battery ใช้ foreground scanning ส่วนใหญ่
  • SCAN_MODE_BALANCED: กลาง สแกนบ่อยกว่า low power
  • SCAN_MODE_LOW_LATENCY: "ต้องหาตอนนี้!" สแกนต่อเนื่อง หาเร็วที่สุด แตกินแบตเร็วที่สุด ใช้ short critical operations เท่านั้น
  • SCAN_MODE_OPPORTUNISTIC: Ultimate passive mode แอปไม่ trigger scan ได้ results ถ้า app อื่น scanning ใช้แบตศูนย์ แต่ไม่ guarantee results ใช้ non-critical background updates

และ AOSP 16 QPR2+ ใช้ setScanType(SCAN_TYPE_PASSIVE) ตอนไม่ต้องการ scan response data = 國王ใหม่ของ power efficiency

4. ใช้ Hardware Filtering และ Batching

ครอบคลุมใน advanced section แต่ best practice worth repeating หาอุปกรณ์เฉพาะ = ScanFilter Long-running scan = setReportDelay() batch results สองเทคนิคนี้ offload งานไป controller ที่ power-efficient ให้โค้ดแอปนอน = #1 way ประหยัดแบต

5. ระวัง Memory

ทุก ScanResult object = หน่วยความจำ พื้นที่แอ่น BLE devices หลายร้อย ตัว ไม่มี filters = overwhelmed OOM อีกเหตุผล filtering สำคัญ รับแค่ results ที่สนใจจริง

Follow rules นี้ = Bluetooth app powerful, feature-rich และ respectful กับ device ผู้ใช้ = Bluetooth sensei จริงๆ ปิดท้ายกับ future

สรุป: อนาคตคือ Passive (และมันโอเค)

เดินทางมายาวแล้วหรือ? กลับไปยุคมืด Classic Bluetooth สाक्षี renaissance BLE ออกสู่โลกใหม่ AOSP 16 เรียนรู้เป็นนินจาเงียบ passive scanning เล่นนักสืบ bond loss reasons master speed dating service UUIDs from advertisements

Takeaway ใหญ่: อนาคต Bluetooth บน Android = smarter, more efficient, less frustrating ทีม Android ฟัง pain points นักพัฒนา ให้ tools สร้างแอป better, battery-friendlier Passive scanning ไม่ใช่แค่ featureใหม่ = เปลี่ยนปรัชญา การสื่อสารที่ดีที่สุดบางครั้ง = แค่ฟัง

Tools ใหม่ empower เราไป beyond "connect and stream" สร้าง sophisticated context-aware apps aware of surroundings โดยไม่ทำโทรศัพท์ผู้ใช้ = expensive paperweights ฝัน smart connected world seamlessly ใกล้ขึ้น 建立在 power-efficient technologies

ต่อไปอะไร? Bluetooth evolve ตลอด Bluetooth 5.4 Auracast, mesh networking, precise location-finding อยู่ระหว่างทาง ข้อความแน่ใจ: tools จะดีขึ้น challenges จะน่าสนใจขึ้น

ตอนนี้ appreciate ความก้าวหน้า ครั้งหน้า start Bluetooth scan มัน just works → ขอบคุณ engineers ที่ทำให้มันเป็นไปได้ ครั้งหน้า battery graph แอปคุณ = เส้นแบนสวยแทน ski slope ระทึก → กระซิบขอบคุณ passive scanning

Bluetooth beast อาจไม่ domesticated fully แต่ AOSP 16 ให้ leash ที่แข็งแรงกว่า เดินไปสร้างสิ่งมหัศจรรย์ และเจ้าแก๊ะ อย่าลืม stop scan ของคุณ!

Learn to code for free. freeCodeCamp's open source curriculum has helped more than 40,000 people get jobs as developers. Get started