คุณอาจเคยเกิดเหตุการณ์เช่นนี้มาก่อน:หูฟังไร้สายของคุณเชื่อมต่อได้อย่างสมบูรณ์ในวันหนึ่ง และวันต่อมาก็ทำเหมือนกับว่าไม่เคยเจอโทรศัพท์ของคุณเลย หรือสมาร์ทวอทช์ของคุณหล่นลงระหว่างการวิ่ง บลูทูธใช้งานได้อย่างน่าทึ่ง แต่จะน่าหงุดหงิดเมื่อไม่ได้ใช้งาน
ฉันทำงานเป็นวิศวกรซอฟต์แวร์บลูทูธบนอุปกรณ์สวมใส่ได้ เช่น แว่นตาอัจฉริยะ และฉันใช้เวลามากกว่าที่จะยอมรับว่าทำไมสิ่งเหล่านี้ถึงพัง
ในบทความนี้ ฉันจะให้คุณดูเบื้องหลัง:วิธีการทำงานของ Bluetooth Stack ของ Android จริงๆ เหตุใดบางครั้งจึงคาดเดาไม่ได้ และสิ่งที่คุณสามารถทำได้ในฐานะนักพัฒนาซอฟต์แวร์เพื่อทำให้แอปหรือระบบของคุณเชื่อถือได้มากขึ้น
บลูทูธเป็นภาษาอังกฤษธรรมดา
โดยแก่นแท้แล้ว บลูทูธเป็นเพียงการสนทนาระหว่างอุปกรณ์สองเครื่อง แต่นี่ไม่ใช่การสื่อสารง่ายๆ เพียงสายเดียว เพราะมีหลายชั้นซ้อนกัน
-
วิทยุ (คอนโทรลเลอร์): ส่งและรับสัญญาณจริงผ่านตัวกลางอากาศ
-
สมองของซอฟต์แวร์ (Host Stack): ตัดสินใจว่าจะพูดคุยกับใครและอย่างไร รวมถึงต้องการหรือไม่
-
โปรไฟล์: กำหนดวัตถุประสงค์ของการสนทนา เช่น การสตรีมเพลงหรือการซิงค์ข้อมูลด้านสุขภาพ
-
โปรโตคอล: กำหนดวิธีการพูดคุยกับอุปกรณ์อื่น
บลูทูธมี "รสชาติ" ใหญ่ๆ สองประการ:
-
คลาสสิก (BR/EDR): ใช้สำหรับสิ่งต่างๆ เช่น หูฟังและชุดอุปกรณ์ติดรถยนต์ สามารถยกน้ำหนักได้มากขึ้น
-
พลังงานต่ำ (LE): ใช้สำหรับวงฟิตเนส บีคอน และอุปกรณ์สวมใส่ส่วนใหญ่ สามารถคงอยู่ได้นานขึ้น
อุปกรณ์สมัยใหม่ส่วนใหญ่ใช้ทั้งสองอย่างพร้อมกัน นั่นทรงพลังมาก แต่ยังเปิดประตูให้มีสิ่งผิดพลาดอีกมากมาย
เหตุใด Android จึงเพิ่มนิสัยแปลกๆ ของตัวเอง

บน Android บลูทูธไม่ได้เป็นเพียงแพ็คเกจเดียวเท่านั้น มันเป็นห่วงโซ่ของชิ้นส่วนที่เคลื่อนไหว:
-
แอปของคุณเรียก
09. -
สิ่งเหล่านี้จะเข้าสู่บริการของระบบ เช่น
12. -
จากนั้นเป็นโค้ดเนทิฟผ่าน JNI (อินเทอร์เฟซดั้งเดิมของ Java)
-
จากนั้นไปที่สแต็ก Bluetooth ของผู้จำหน่ายชิป .
-
ในที่สุดก็กระทบกับฮาร์ดแวร์วิทยุ .
ผู้ผลิตโทรศัพท์ทุกรายจัดส่งชิป Bluetooth และเฟิร์มแวร์ที่แตกต่างกันเล็กน้อย นั่นหมายความว่าแอปบลูทูธเดียวกันนี้อาจทำงานแตกต่างออกไปใน Samsung, Pixel หรือโทรศัพท์ราคาประหยัดอื่นๆ ที่ใช้ Android
ปัญหาที่แท้จริงเบื้องหลัง “มันเพิ่งตัดการเชื่อมต่อ”
ต่อไปนี้เป็นอาการปวดหัวทั่วไปบางส่วนที่ฉันเห็น อธิบายง่ายๆ:
ปัญหาความผูกพัน (ปัญหา "กุญแจหาย")
เมื่ออุปกรณ์ Bluetooth สองเครื่องจับคู่กัน อุปกรณ์จะแลกเปลี่ยนคีย์การเข้ารหัส (คีย์ลิงก์สำหรับ Classic, Long Term Keys สำหรับ LE) และจัดเก็บไว้ในหน่วยความจำแบบไม่ลบเลือน ปุ่มเหล่านี้คือสิ่งที่ทำให้อุปกรณ์จดจำกันได้ในภายหลังและเชื่อมต่อใหม่อย่างปลอดภัยโดยไม่ต้องถามผู้ใช้อีก
ปัญหา "หน่วยความจำไม่ตรงกัน" เกิดขึ้นเมื่อคีย์ที่จัดเก็บไว้ในอุปกรณ์เครื่องหนึ่งไม่ตรงกับอีกเครื่องอีกต่อไป สาเหตุนี้อาจเกิดจาก:
-
การอัปเดตเฟิร์มแวร์หรืออัปเกรดระบบปฏิบัติการที่จะล้างข้อมูลหรือสร้างคีย์ใหม่
-
การรีเซ็ตเป็นค่าเริ่มต้นจากโรงงานหรือ "ลืมอุปกรณ์" ที่ด้านหนึ่ง แต่ไม่ใช่อีกด้านหนึ่ง
-
คีย์เสียหายหรือถูกไล่ออกโดยระบบเพื่อเพิ่มพื้นที่จัดเก็บข้อมูล
จากมุมมองของผู้ใช้ อุปกรณ์อาจยังคงดูอยู่ จับคู่แล้ว (แสดงในเมนู Bluetooth) แต่การเชื่อมต่อล้มเหลวอย่างลึกลับโดยมีข้อผิดพลาดเช่น "การรับรองความถูกต้องล้มเหลว" หรือ "การเข้ารหัสไม่เพียงพอ" วิธีเดียวที่จะแก้ได้คือลบอุปกรณ์ทั้งสองด้านแล้วจับคู่ใหม่ ซึ่งถือว่าไร้สาระสำหรับผู้ใช้ที่ไม่เชี่ยวชาญด้านเทคนิค
เวลาไม่ตรงกัน
อุปกรณ์บลูทูธไม่เพียงแค่แชททุกเมื่อที่ต้องการ แต่ยังเห็นด้วยกับช่วงเวลาการเชื่อมต่อ ซึ่งเป็นกำหนดเวลาที่แต่ละฝ่ายจะ "ตื่น" และแลกเปลี่ยนแพ็กเก็ต ลองนึกถึงการที่คนสองคนตกลงที่จะพบกันทุกๆ 30 นาทีที่ร้านกาแฟ
ไม่ตรงกันเกิดขึ้นเมื่อ:
-
ทั้งสองฝ่ายเจรจาในช่วงเวลาที่แตกต่างกันแต่ไม่ได้ตกลงกันทั้งหมด (เช่น ฝ่ายหนึ่งคิดว่าเป็น 30 มิลลิวินาที อีกฝ่ายหนึ่งคิดว่า 50 มิลลิวินาที)
-
การอัปเดตเฟิร์มแวร์หรือการเปลี่ยนแปลงการกำหนดค่าของฝ่ายหนึ่งจะเปลี่ยนแปลงนโยบายกำหนดเวลา
-
สภาวะทางวิทยุทำให้ฝ่ายหนึ่งพลาดการเช็คอินตามกำหนดเวลาหลายรายการ ทำให้นาฬิกาห่างกัน
-
ตรรกะการประหยัดพลังงาน (เช่น โทรศัพท์เข้าสู่โหมด Doze) จะขยายช่วงเวลาออกไปอย่างเงียบๆ
สิ่งนี้อธิบายว่าทำไมการเชื่อมต่ออาจทำงานได้ดีในตอนแรกแต่เริ่มล้มเหลวในภายหลัง:อุปกรณ์เริ่มซิงค์ตามช่วงเวลา แต่นโยบายหรือพฤติกรรมของฝ่ายหนึ่งเปลี่ยนไป จากมุมมองของผู้ใช้ ดูเหมือนว่าเสียงติดขัด อินพุตล่าช้า (บนตัวควบคุมเกม) หรือการตัดการเชื่อมต่อแบบสุ่มหลังจาก “ก่อนหน้านี้ทำงานได้ดี”
การตัดการเชื่อมต่อที่ไม่คาดคิด
เมื่อการเชื่อมต่อ Bluetooth สิ้นสุดลง เลเยอร์วิทยุ (ตัวควบคุม) และสแต็ก OS ระดับที่สูงกว่า (โฮสต์) ควรจะแลกเปลี่ยนสัญญาณที่ชัดเจน ตัวควบคุมส่งเหตุการณ์ HCI Disconnection Complete (โดยพื้นฐานแล้ว:“ลาก่อน เสร็จแล้ว” ). จากนั้นโฮสต์ควรอัปเดตสถานะภายใน ล้างเซสชัน GATT/ACL และเตรียมพร้อมสำหรับการเชื่อมต่อใหม่
แต่ในทางปฏิบัติ สิ่งนี้ไม่ได้เข้ากันเสมอไป:
-
บางครั้งตัวควบคุมบอกลาอย่างชัดเจน แต่สแต็กโฮสต์ไม่อัปเดตสถานะอย่างถูกต้อง แอปยังคง "คิดว่า" การเชื่อมต่อทำงานอยู่ ดังนั้นความพยายามในการเชื่อมต่อใหม่จึงล้มเหลวโดยไม่แจ้งให้ทราบ
-
บางแพลตฟอร์มแคชสถานะการเชื่อมต่ออย่างรุนแรง (โดยเฉพาะ iOS) หากระบบปฏิบัติการเชื่อว่าการเชื่อมต่อยังคงใช้งานได้ ระบบจะไม่พยายามเชื่อมต่อใหม่จนกว่าคุณจะสลับบลูทูธหรือรีบูต
-
สภาวะการแข่งขันอาจเกิดขึ้นได้หากเหตุการณ์การตัดการเชื่อมต่อเกิดขึ้นในขณะที่การดำเนินการอื่น (เช่น การค้นหาบริการ การเชื่อมต่อ หรือการตั้งค่าการเข้ารหัส) อยู่ระหว่างดำเนินการ ระบบปฏิบัติการอาจสับสนว่าอุปกรณ์แท้จริงอยู่ในสถานะใด ใน.
-
ในอุปกรณ์บางชนิด ความพยายามในการเชื่อมต่อใหม่อย่างรวดเร็วหลังจากการตัดการเชื่อมต่อที่เรียบร้อยขัดแย้งกับตัวจับเวลาคูลดาวน์ภายใน ตัวควบคุมเพิกเฉยต่อแอปดังกล่าว โดยปล่อยให้แอปรออยู่
จากมุมมองของผู้ใช้ อุปกรณ์ดูเหมือน "ติดอยู่" วิธีเดียวที่จะกู้คืนได้คือการสลับบลูทูธ รีสตาร์ทแอป หรือเปิดปิดอุปกรณ์เสริม แม้ว่าในทางเทคนิคแล้วไม่มีอะไร "ล้มเหลว"
นักพัฒนาซอฟต์แวร์สามารถปรับปรุงให้ดีขึ้นได้อย่างไร
หากคุณกำลังสร้างแอปบลูทูธ นิสัยบางส่วนที่ช่วยบรรเทาความเจ็บปวดได้มากมีดังนี้:
ตรวจสอบอุปกรณ์ที่ถูกผูกมัดก่อน
สาเหตุที่พบบ่อยที่สุดประการหนึ่งของการเชื่อมต่อล้มเหลวคือข้อมูลการเชื่อมต่อที่ไม่ตรงกัน:โทรศัพท์และอุปกรณ์เสริมไม่ได้ใช้คีย์เข้ารหัสเดียวกันอีกต่อไป แม้ว่าอุปกรณ์จะปรากฏใน UI แต่ระบบปฏิบัติการก็อาจสูญเสียคีย์ไปแล้ว
ก่อนที่จะพยายามเชื่อมต่อ ให้ค้นหารายการอุปกรณ์ที่เชื่อมโยงของระบบด้วย 24 เสมอ . ตัวอย่างเช่น:
if (adapter.getBondedDevices().contains(targetDevice)) {
targetDevice.connectGatt(context, false, gattCallback);
} else {
showToast("Please re-pair this device to restore the connection.");
}
สิ่งนี้ทำให้แน่ใจได้ว่าคุณจะพยายามเชื่อมต่ออย่างปลอดภัยกับอุปกรณ์ที่ระบบปฏิบัติการยังคงเชื่อถือเท่านั้น หากอุปกรณ์เป้าหมายไม่อยู่ในรายการเชื่อมโยง คุณสามารถให้คำแนะนำที่ชัดเจนแก่ผู้ใช้ (“โปรดจับคู่อุปกรณ์นี้ใหม่”) แทนที่จะปล่อยให้ผู้ใช้มีข้อผิดพลาดในการเชื่อมต่อที่ทำให้สับสน
จัดการการโทรกลับอย่างระมัดระวัง
ข้อผิดพลาดเล็กๆ น้อยๆ อีกประการหนึ่งคือการสมมติว่า 30 เหตุการณ์หมายถึงการเชื่อมต่อสำเร็จ ในความเป็นจริง 40 สามารถรายงานสถานะที่เชื่อมต่อได้แม้ว่าการดำเนินการพื้นฐานจะล้มเหลว แต่ผลลัพธ์ที่แท้จริงจะอยู่ใน 58 อาร์กิวเมนต์ เพื่อหลีกเลี่ยงการไล่ตามการเชื่อมต่อ Phantom ให้ตรวจสอบทั้ง 65 เสมอ และ 70รหัส> :
if (status == BluetoothGatt.GATT_SUCCESS &&
newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices();
} else {
gatt.close();
}
รูปแบบนี้ป้องกันไม่ให้คุณพยายามค้นหาบริการบนการเชื่อมต่อที่ไม่ทำงาน และช่วยให้แน่ใจว่าเซสชันเก่าจะถูกปิดทันที ปล่อยให้สแต็กพร้อมสำหรับการลองใหม่ทั้งหมด
คาดหวังความล้มเหลว
การเชื่อมต่อ Bluetooth จะล้มเหลวตลอดเวลาในโลกแห่งความเป็นจริง - อุปกรณ์อยู่นอกระยะ สัญญาณรบกวนที่เพิ่มขึ้นอย่างรวดเร็วในย่านความถี่ 2.4 GHz หรือวิทยุไม่ว่าง สิ่งที่แย่ที่สุดที่แอปสามารถทำได้คือลองใหม่ทันทีในวงที่แน่นหนา ซึ่งจะทำให้แบตเตอรี่หมดและทำให้สแตกไม่เสถียร
แนวทางที่ดีกว่าคือการใช้การถอยกลับแบบเอ็กซ์โปเนนเชียลดังนี้:
long delay = (long) Math.min(250 * Math.pow(2, attempt), 30000);
new Handler(Looper.getMainLooper()).postDelayed(connectAction, delay);
ซึ่งหมายความว่าการลองใหม่ครั้งแรกของคุณเกิดขึ้นอย่างรวดเร็ว (~250 ms) แต่การลองใหม่ครั้งต่อไปช้าลง (500 ms, 1 วินาที, 2 วินาที…) ต่อยอดที่สูงสุดที่สมเหตุสมผล Backoff ทำให้แอปของคุณมีความยืดหยุ่นโดยไม่ต้องมีคลื่นวิทยุหรือระบบปฏิบัติการมากเกินไป
ใช้เครื่องมือที่เหมาะสม
หากไม่สามารถมองเห็นสิ่งที่เกิดขึ้นภายใต้ประทุน ปัญหาการเชื่อมต่อจะดูแบบสุ่ม เครื่องมืออย่าง nRF Connect ช่วยให้คุณสามารถสแกน เชื่อมต่อ และเรียกใช้การดำเนินการ GATT กับอุปกรณ์ของคุณได้อย่างโต้ตอบ ในขณะที่บันทึกการสอดแนม Bluetooth HCI ของ Android จะเผยให้เห็นแพ็กเก็ตจริงที่มีการแลกเปลี่ยน ตัวอย่างเช่น:
Settings.Secure.putInt(context.getContentResolver(), "bluetooth_hci_log", 1);
เมื่อเปิดใช้งานแล้ว คุณจะบันทึกการติดตาม Logcat และยืนยันว่าความล้มเหลวเกิดจากคีย์ที่หายไปหรือไม่ (85 ) เวลาไม่ตรงกัน หรือการรบกวน การใช้เครื่องมือเหล่านี้ไม่เพียงช่วยให้คุณแก้ไขจุดบกพร่องของแอปเท่านั้น แต่ยังพิสูจน์ได้ว่าปัญหาอยู่ที่โค้ด ระบบปฏิบัติการ หรือเฟิร์มแวร์เสริมของคุณ

บทเรียนที่ยิ่งใหญ่
การทำงานกับ Bluetooth ทำให้ฉันได้เรียนรู้บทเรียนที่เกี่ยวข้องกับวิศวกรรมโดยทั่วไป:
-
ระบบไร้สายไม่เคยสมบูรณ์แบบ ดังนั้นจงสร้างโดยคำนึงถึงการฟื้นฟูอยู่เสมอ
-
บันทึกและเมตริกไม่ใช่ทางเลือก พวกมันคือแผนที่ของคุณท่ามกลางความสับสนวุ่นวาย
-
วิธีแก้ปัญหาที่ง่ายที่สุดมักจะอยู่รอดได้ดีที่สุดในโลกที่วุ่นวาย
บทสรุป
บลูทูธยุ่งเหยิงเพราะเป็นสายโซ่ของฮาร์ดแวร์ เฟิร์มแวร์ และซอฟต์แวร์ที่พยายามร่วมมือกัน บน Android ชิปและผู้จำหน่ายที่หลากหลายทำให้มีความซับซ้อนมากขึ้น
แต่นั่นไม่ได้หมายความว่าคุณทำอะไรไม่ถูก ด้วยการทำความเข้าใจวิธีการทำงานของเลเยอร์และออกแบบแอปของคุณด้วยการลองใหม่ การตรวจสอบ และการบันทึกที่เหมาะสม คุณสามารถทำให้ Bluetooth รู้สึก "แปลก" น้อยลงสำหรับผู้ใช้ของคุณ
ครั้งต่อไปที่หูฟังเอียร์บัดของคุณทำงานผิดปกติ คุณจะรู้ว่าไม่ใช่คุณ บลูทูธเป็นเพียงบลูทูธ
⚡ นี่เป็นบทความแรกจากบทความจำนวนหนึ่งที่ฉันจะเขียนเกี่ยวกับการพัฒนา Bluetooth ในตอนถัดไป เราจะเจาะลึกลงไปถึงวิธีสร้างไคลเอ็นต์และเซิร์ฟเวอร์ Bluetooth Low Energy (BLE) GATT ที่ปลอดภัยบน Android คอยติดตาม! ป>
เรียนรู้การเขียนโค้ดฟรี หลักสูตรโอเพ่นซอร์สของ freeCodeCamp ช่วยให้ผู้คนมากกว่า 40,000 คนได้งานในตำแหน่งนักพัฒนา เริ่มต้น