เชื่อมต่ออุปกรณ์บลูทูธ

หากต้องการสร้างการเชื่อมต่อระหว่างอุปกรณ์ 2 เครื่อง คุณต้องใช้ทั้ง กลไกฝั่งเซิร์ฟเวอร์และฝั่งไคลเอ็นต์เนื่องจากอุปกรณ์หนึ่งต้องเปิดเซิร์ฟเวอร์ ซ็อกเก็ต และอีกช่องหนึ่งต้องเริ่มต้นการเชื่อมต่อโดยใช้อุปกรณ์ของเซิร์ฟเวอร์ ที่อยู่ MAC อุปกรณ์เซิร์ฟเวอร์และอุปกรณ์ไคลเอ็นต์แต่ละเครื่องจะได้รับข้อมูลที่จำเป็น BluetoothSocket ในแบบต่างๆ ได้ เซิร์ฟเวอร์จะได้รับข้อมูลซ็อกเก็ตเมื่อมีการเชื่อมต่อขาเข้า ยอมรับ ไคลเอ็นต์ให้ข้อมูลซ็อกเก็ตเมื่อเปิดช่องทาง RFCOMM ไปยังเซิร์ฟเวอร์

เซิร์ฟเวอร์และไคลเอ็นต์จะถือว่าเชื่อมต่อกันเมื่อ BluetoothSocket ที่เชื่อมต่ออยู่ในช่อง RFCOMM เดียวกัน ในจุดนี้ แต่ละ อุปกรณ์สามารถรับสตรีมอินพุตและเอาต์พุต และจะเริ่มโอนข้อมูลได้ ในหัวข้อเกี่ยวกับการโอนบลูทูธ ข้อมูล ส่วนนี้ อธิบายวิธีเริ่มการเชื่อมต่อระหว่างอุปกรณ์ 2 เครื่อง

โปรดตรวจสอบว่าคุณมี สิทธิ์การใช้บลูทูธและ ตั้งค่าแอปสำหรับบลูทูธก่อน กำลังพยายามหาอุปกรณ์บลูทูธ

เทคนิคการเชื่อมต่อ

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


รูปที่ 1 กล่องโต้ตอบการจับคู่บลูทูธ

เชื่อมต่อในฐานะเซิร์ฟเวอร์

เมื่อต้องการเชื่อมต่ออุปกรณ์ 2 เครื่อง เครื่องหนึ่งต้องทำหน้าที่เป็นเซิร์ฟเวอร์โดยกด เปิด BluetoothServerSocket วัตถุประสงค์ของซ็อกเก็ตเซิร์ฟเวอร์คือการรอฟังคำขอเชื่อมต่อที่เข้ามา และระบุ BluetoothSocket ที่เชื่อมต่อหลังจากยอมรับคำขอแล้ว เมื่อ ซื้อ BluetoothSocket จาก BluetoothServerSocket สามารถและควรทิ้ง BluetoothServerSocket ได้ ยกเว้นกรณีที่คุณต้องการ อุปกรณ์เพื่อยอมรับการเชื่อมต่อเพิ่มเติม

หากต้องการตั้งค่าซ็อกเก็ตเซิร์ฟเวอร์และยอมรับการเชื่อมต่อ ให้ทำดังนี้ ลำดับขั้นตอน

  1. รับ BluetoothServerSocket ด้วยการโทร listenUsingRfcommWithServiceRecord(String, UUID)

    สตริงคือชื่อบริการที่ระบุได้ ซึ่งระบบ เขียนไปยังรายการฐานข้อมูล Service Discovery Protocol (SDP) ใหม่โดยอัตโนมัติ ในอุปกรณ์ ชื่อที่กำหนดเองอาจเป็นชื่อแอปก็ได้ ตัวระบุสากลที่ไม่ซ้ำกัน (UUID) จะรวมอยู่ในรายการ SDP ด้วย และเป็นพื้นฐานสำหรับข้อตกลงการเชื่อมต่อกับอุปกรณ์ของลูกค้า นั่น คือ เมื่อไคลเอ็นต์พยายามเชื่อมต่อกับอุปกรณ์นี้ จะมี UUID ซึ่งระบุบริการที่ต้องการเชื่อมต่อโดยไม่ซ้ำกัน เหล่านี้ UUID ต้องตรงกันเพื่อให้ระบบยอมรับการเชื่อมต่อ

    UUID คือรูปแบบ 128 บิตแบบมาตรฐานสำหรับรหัสสตริงที่ใช้ไม่ซ้ำกัน ระบุตัวตนได้ UUID ใช้เพื่อระบุข้อมูลที่จำเป็นต้องมี ในระบบหรือเครือข่ายหนึ่งๆ เนื่องจากความน่าจะเป็นของ UUID การเกิดซ้ำจะมีค่าเป็น 0 สร้างขึ้นอย่างอิสระโดยไม่ได้ใช้ ของหน่วยงานแบบรวมศูนย์ ในกรณีนี้ จะใช้เพื่อระบุ บริการบลูทูธของแอป หากต้องการรับ UUID เพื่อใช้กับแอป ให้ใช้ ของการสุ่ม โปรแกรมสร้าง UUID บนเว็บ จากนั้นเริ่มต้น UUID ที่มี fromString(String)

  2. เริ่มฟังคำขอเชื่อมต่อโดยการโทร accept()

    การโทรนี้เป็นแบบบล็อก จะแสดงผลเมื่อมีการเชื่อมต่อ ยอมรับแล้วหรือมีข้อผิดพลาดเกิดขึ้น จะยอมรับการเชื่อมต่อก็ต่อเมื่อ อุปกรณ์ระยะไกลส่งคำขอการเชื่อมต่อที่มี UUID ตรงกับ อันที่ลงทะเบียนกับซ็อกเก็ตเซิร์ฟเวอร์การฟังนี้ เมื่อทำสำเร็จ accept() แสดงผล BluetoothSocket ที่เชื่อมต่อ

  3. ยกเว้นกรณีที่คุณต้องการยอมรับการเชื่อมต่อเพิ่มเติม ให้โทร close()

    การเรียกเมธอดนี้จะปล่อยซ็อกเก็ตเซิร์ฟเวอร์และทรัพยากรทั้งหมดของซ็อกเก็ต แต่ ไม่ปิด BluetoothSocket ที่เชื่อมต่ออยู่ซึ่งส่งคืนโดย accept() RFCOMM อนุญาตให้มีไคลเอ็นต์ที่เชื่อมต่อได้เพียง 1 รายการต่อ ทีละช่อง ดังนั้นในกรณีส่วนใหญ่จึงเหมาะสมที่จะโทรหา close() ใน BluetoothServerSocket ทันทีหลังจากยอมรับซ็อกเก็ตที่เชื่อมต่อ

เนื่องจากการเรียก accept() เป็นการเรียกที่บล็อก โปรดอย่าเรียกใช้ในการเรียก ชุดข้อความ UI กิจกรรม การเรียกใช้ในชุดข้อความอื่นช่วยให้มั่นใจได้ว่าแอปสามารถ ยังคงตอบสนองต่อการโต้ตอบของผู้ใช้คนอื่นๆ อยู่ คุณควรทำงานทั้งหมดทุกครั้ง ที่เกี่ยวข้องกับ BluetoothServerSocket หรือ BluetoothSocket ในชุดข้อความใหม่ ที่จัดการโดยแอปของคุณ หากต้องการล้มเลิกการโทรที่ถูกบล็อก เช่น accept() โปรดโทรหา close() ใน BluetoothServerSocket หรือ BluetoothSocket จากชุดข้อความอื่น หมายเหตุ เมธอดทั้งหมดใน BluetoothServerSocket หรือ BluetoothSocket Thread-safe

ตัวอย่าง

ต่อไปนี้เป็นเธรดอย่างง่ายสำหรับคอมโพเนนต์ของเซิร์ฟเวอร์ที่ยอมรับ การเชื่อมต่อขาเข้า:

Kotlin

private inner class AcceptThread : Thread() {

   private val mmServerSocket: BluetoothServerSocket? by lazy(LazyThreadSafetyMode.NONE) {
       bluetoothAdapter?.listenUsingInsecureRfcommWithServiceRecord(NAME, MY_UUID)
   }

   override fun run() {
       // Keep listening until exception occurs or a socket is returned.
       var shouldLoop = true
       while (shouldLoop) {
           val socket: BluetoothSocket? = try {
               mmServerSocket?.accept()
           } catch (e: IOException) {
               Log.e(TAG, "Socket's accept() method failed", e)
               shouldLoop = false
               null
           }
           socket?.also {
               manageMyConnectedSocket(it)
               mmServerSocket?.close()
               shouldLoop = false
           }
       }
   }

   // Closes the connect socket and causes the thread to finish.
   fun cancel() {
       try {
           mmServerSocket?.close()
       } catch (e: IOException) {
           Log.e(TAG, "Could not close the connect socket", e)
       }
   }
}

Java

private class AcceptThread extends Thread {
   private final BluetoothServerSocket mmServerSocket;

   public AcceptThread() {
       // Use a temporary object that is later assigned to mmServerSocket
       // because mmServerSocket is final.
       BluetoothServerSocket tmp = null;
       try {
           // MY_UUID is the app's UUID string, also used by the client code.
           tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
       } catch (IOException e) {
           Log.e(TAG, "Socket's listen() method failed", e);
       }
       mmServerSocket = tmp;
   }

   public void run() {
       BluetoothSocket socket = null;
       // Keep listening until exception occurs or a socket is returned.
       while (true) {
           try {
               socket = mmServerSocket.accept();
           } catch (IOException e) {
               Log.e(TAG, "Socket's accept() method failed", e);
               break;
           }

           if (socket != null) {
               // A connection was accepted. Perform work associated with
               // the connection in a separate thread.
               manageMyConnectedSocket(socket);
               mmServerSocket.close();
               break;
           }
       }
   }

   // Closes the connect socket and causes the thread to finish.
   public void cancel() {
       try {
           mmServerSocket.close();
       } catch (IOException e) {
           Log.e(TAG, "Could not close the connect socket", e);
       }
   }
}

ในตัวอย่างนี้ต้องการการเชื่อมต่อขาเข้าเพียงรายการเดียว ดังนั้นทันที ยอมรับการเชื่อมต่อ และได้ BluetoothSocket มาแล้ว แอปจะส่งผ่าน ได้รับ BluetoothSocket ไปยังชุดข้อความแยกต่างหาก ปิด BluetoothServerSocket และหลุดออกจากลูป

โปรดทราบว่าเมื่อ accept() แสดง BluetoothSocket นั่นหมายถึงซ็อกเก็ตแล้ว เชื่อมต่อ แล้ว ดังนั้น คุณไม่ควรเรียกใช้ connect() เหมือนกับที่คุณใช้ จากฝั่งไคลเอ็นต์

เมธอด manageMyConnectedSocket() เฉพาะแอปออกแบบมาเพื่อเริ่มต้น ชุดข้อความสำหรับโอนข้อมูล ซึ่งพูดถึงในหัวข้อเกี่ยวกับ กำลังโอนบลูทูธ

โดยทั่วไป คุณควรปิด BluetoothServerSocket ทันทีที่ใช้เสร็จแล้ว กำลังฟังการเชื่อมต่อที่เข้ามาใหม่ ในตัวอย่างนี้ ระบบจะเรียก close() ทันที เมื่อมีการซื้อ BluetoothSocket คุณอาจต้องแสดง ในชุดข้อความที่สามารถปิด BluetoothSocket ส่วนตัวในเหตุการณ์ คุณต้องหยุดฟังในซ็อกเก็ตเซิร์ฟเวอร์นั้น

เชื่อมต่อในฐานะลูกค้า

เพื่อเริ่มการเชื่อมต่อกับอุปกรณ์ระยะไกลที่ยอมรับ การเชื่อมต่อในซ็อกเก็ตเซิร์ฟเวอร์แบบเปิด คุณต้องรับ BluetoothDevice ก่อน ที่แสดงถึงอุปกรณ์ระยะไกล ดูวิธีสร้าง BluetoothDevice โปรดดูที่ค้นหาบลูทูธ อุปกรณ์ คุณต้อง แล้วใช้ BluetoothDevice เพื่อให้ได้ BluetoothSocket และเริ่มต้น การเชื่อมต่อ

ขั้นตอนพื้นฐานมีดังนี้

  1. เมื่อใช้ BluetoothDevice รับ BluetoothSocket ด้วยการโทรหา createRfcommSocketToServiceRecord(UUID)

    เมธอดนี้จะเริ่มต้นออบเจ็กต์ BluetoothSocket ที่อนุญาตให้ไคลเอ็นต์ เชื่อมต่อกับ BluetoothDevice UUID ที่ส่งที่นี่ต้องตรงกับ UUID ที่ใช้ โดยอุปกรณ์เซิร์ฟเวอร์เมื่อมีการเรียก listenUsingRfcommWithServiceRecord(String, UUID) เพื่อเปิด BluetoothServerSocket หากต้องการใช้ UUID ที่ตรงกัน ให้ฮาร์ดโค้ด สตริง UUID ในแอปของคุณ แล้วอ้างอิงจากทั้งเซิร์ฟเวอร์ และรหัสไคลเอ็นต์

  2. เริ่มการเชื่อมต่อโดยโทรหา connect() โปรดทราบว่าวิธีนี้เป็น การบล็อกสาย

    หลังจากที่ไคลเอ็นต์เรียกใช้เมธอดนี้ ระบบจะทำการค้นหา SDP เพื่อค้นหา อุปกรณ์ระยะไกลที่มี UUID ตรงกัน หากการค้นหาสำเร็จและ อุปกรณ์ระยะไกลยอมรับการเชื่อมต่อ และแชร์ช่องทาง RFCOMM เพื่อใช้งาน ในระหว่างการเชื่อมต่อ และวิธี connect() จะแสดงขึ้น หากการเชื่อมต่อ ล้มเหลวหรือหากเมธอด connect() หมดเวลา (หลังจากผ่านไปประมาณ 12 วินาที) แล้ว เมธอดดังกล่าวจะทำให้เกิด IOException

เนื่องจาก connect() เป็นสายที่บล็อกไว้ คุณจึงควรดำเนินการนี้เสมอ ขั้นตอนการเชื่อมต่อในเทรดที่แยกต่างหากจากกิจกรรมหลัก (UI) ชุดข้อความ

ตัวอย่าง

ต่อไปนี้เป็นตัวอย่างพื้นฐานของเธรดของไคลเอ็นต์ที่เริ่มต้นบลูทูธ การเชื่อมต่อ:

Kotlin

private inner class ConnectThread(device: BluetoothDevice) : Thread() {

   private val mmSocket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) {
       device.createRfcommSocketToServiceRecord(MY_UUID)
   }

   public override fun run() {
       // Cancel discovery because it otherwise slows down the connection.
       bluetoothAdapter?.cancelDiscovery()

       mmSocket?.let { socket ->
           // Connect to the remote device through the socket. This call blocks
           // until it succeeds or throws an exception.
           socket.connect()

           // The connection attempt succeeded. Perform work associated with
           // the connection in a separate thread.
           manageMyConnectedSocket(socket)
       }
   }

   // Closes the client socket and causes the thread to finish.
   fun cancel() {
       try {
           mmSocket?.close()
       } catch (e: IOException) {
           Log.e(TAG, "Could not close the client socket", e)
       }
   }
}

Java

private class ConnectThread extends Thread {
   private final BluetoothSocket mmSocket;
   private final BluetoothDevice mmDevice;

   public ConnectThread(BluetoothDevice device) {
       // Use a temporary object that is later assigned to mmSocket
       // because mmSocket is final.
       BluetoothSocket tmp = null;
       mmDevice = device;

       try {
           // Get a BluetoothSocket to connect with the given BluetoothDevice.
           // MY_UUID is the app's UUID string, also used in the server code.
           tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
       } catch (IOException e) {
           Log.e(TAG, "Socket's create() method failed", e);
       }
       mmSocket = tmp;
   }

   public void run() {
       // Cancel discovery because it otherwise slows down the connection.
       bluetoothAdapter.cancelDiscovery();

       try {
           // Connect to the remote device through the socket. This call blocks
           // until it succeeds or throws an exception.
           mmSocket.connect();
       } catch (IOException connectException) {
           // Unable to connect; close the socket and return.
           try {
               mmSocket.close();
           } catch (IOException closeException) {
               Log.e(TAG, "Could not close the client socket", closeException);
           }
           return;
       }

       // The connection attempt succeeded. Perform work associated with
       // the connection in a separate thread.
       manageMyConnectedSocket(mmSocket);
   }

   // Closes the client socket and causes the thread to finish.
   public void cancel() {
       try {
           mmSocket.close();
       } catch (IOException e) {
           Log.e(TAG, "Could not close the client socket", e);
       }
   }
}

โปรดสังเกตว่าในข้อมูลโค้ดนี้ มีการเรียกใช้ cancelDiscovery() ก่อนการเชื่อมต่อ เกิดขึ้น คุณควรโทรหา cancelDiscovery() ก่อน connect() เสมอ โดยเฉพาะอย่างยิ่งเนื่องจาก cancelDiscovery() จะประสบความสำเร็จไม่ว่าอุปกรณ์ อยู่ระหว่างดำเนินการค้นพบ หากแอปต้องกำหนดว่า กำลังค้นหาอุปกรณ์ คุณสามารถตรวจสอบได้โดยใช้ isDiscovering()

เมธอด manageMyConnectedSocket() เฉพาะแอปออกแบบมาเพื่อเริ่มต้น ชุดข้อความสำหรับโอนข้อมูล ซึ่งพูดถึงในส่วนที่เกี่ยวกับ กำลังโอนข้อมูลบลูทูธ

เมื่อใช้ BluetoothSocket เสร็จแล้ว ให้โทรหา close() เสมอ ดังนั้น ปิดซ็อกเก็ตที่เชื่อมต่อทันทีและปล่อยสายภายในที่เกี่ยวข้องทั้งหมด ที่ไม่ซับซ้อน