Kết nối thiết bị Bluetooth

Để tạo kết nối giữa hai thiết bị, bạn phải triển khai cả cơ chế phía máy chủ và phía máy khách vì một thiết bị phải mở máy chủ ổ cắm còn lại phải bắt đầu kết nối bằng Địa chỉ MAC. Cả thiết bị máy chủ và thiết bị khách đều có được yêu cầu BluetoothSocket ở các quốc gia khác nhiều cách. Máy chủ nhận thông tin về ổ cắm khi có kết nối đến đã chấp nhận. Ứng dụng cung cấp thông tin về cổng khi mở kênh RFCOMM đến máy chủ.

Máy chủ và máy khách được coi là kết nối với nhau khi chúng có một BluetoothSocket được kết nối trên cùng một kênh RFCOMM. Tại thời điểm này, mỗi thiết bị nào có thể thu được các luồng đầu vào và đầu ra, đồng thời quá trình truyền dữ liệu có thể bắt đầu, được thảo luận trong phần về chuyển Bluetooth . Phần này mô tả cách bắt đầu kết nối giữa hai thiết bị.

Hãy đảm bảo rằng bạn có Quyền truy cập Bluetooththiết lập ứng dụng của bạn cho Bluetooth trước đang cố tìm thiết bị Bluetooth.

Kỹ thuật kết nối

Một kỹ thuật triển khai là tự động chuẩn bị từng thiết bị làm máy chủ sao cho mỗi thiết bị đều mở một ổ cắm máy chủ và theo dõi các kết nối. Trong trong trường hợp này, một trong hai thiết bị có thể bắt đầu kết nối với thiết bị còn lại và trở thành khách hàng. Ngoài ra, một thiết bị có thể lưu trữ kết nối một cách rõ ràng và mở một ổ cắm máy chủ theo yêu cầu và thiết bị còn lại bắt đầu kết nối.


Hình 1. Hộp thoại ghép nối qua Bluetooth.

Kết nối với tư cách máy chủ

Khi bạn muốn kết nối hai thiết bị, một thiết bị phải hoạt động như một máy chủ bằng cách giữ một mở BluetoothServerSocket. Mục đích của cổng máy chủ là để theo dõi các yêu cầu kết nối đến và cung cấp một BluetoothSocket được kết nối sau khi yêu cầu được chấp nhận. Khi BluetoothSocket được mua lại từ BluetoothServerSocket, BluetoothServerSocket có thể và nên bị loại bỏ, trừ phi bạn muốn để thiết bị chấp nhận thêm kết nối.

Để thiết lập cổng máy chủ và chấp nhận kết nối, hãy hoàn thành các bước sau trình tự các bước:

  1. Nhận BluetoothServerSocket bằng cách gọi điện listenUsingRfcommWithServiceRecord(String, UUID).

    Chuỗi này là tên có thể nhận dạng của dịch vụ của bạn do hệ thống tự động ghi vào mục cơ sở dữ liệu Giao thức khám phá dịch vụ (SDP) mới trên thiết bị. Tên này là tuỳ ý và có thể chỉ cần là tên ứng dụng của bạn. Giá trị nhận dạng duy nhất trên toàn cầu (UUID) cũng được đưa vào mục SDP và tạo cơ sở cho thoả thuận kết nối với thiết bị của khách hàng. Đó tức là khi ứng dụng cố gắng kết nối với thiết bị này, nó sẽ mang mã nhận dạng duy nhất (UUID) xác định duy nhất dịch vụ mà dịch vụ đó muốn kết nối. Các Mã nhận dạng duy nhất (UUID) phải khớp để chấp nhận kết nối.

    Mã nhận dạng duy nhất (UUID) là một định dạng 128 bit được chuẩn hoá cho một mã nhận dạng chuỗi dùng để nhận dạng duy nhất xác định thông tin. Mã nhận dạng duy nhất (UUID) được dùng để xác định thông tin cần được duy nhất trong một hệ thống hoặc một mạng vì xác suất để một UUID là độ lặp lại thực sự là 0. Được tạo một cách độc lập, không sử dụng của một cơ quan tập trung. Trong trường hợp này, tên này được dùng để nhận dạng duy nhất dịch vụ Bluetooth của ứng dụng Google Maps. Để có mã nhận dạng duy nhất (UUID) để sử dụng với ứng dụng của mình, bạn có thể sử dụng một trong số rất nhiều UUID trình tạo trên web, sau đó khởi chạy một Mã nhận dạng duy nhất (UUID) với fromString(String).

  2. Bắt đầu lắng nghe các yêu cầu kết nối bằng cách gọi accept().

    Đây là cuộc gọi chặn. URL này sẽ trả về khi một kết nối đã hoặc đã xảy ra ngoại lệ. Kết nối chỉ được chấp nhận khi thiết bị từ xa đã gửi yêu cầu kết nối chứa mã nhận dạng duy nhất (UUID) khớp với một mã đã đăng ký với ổ cắm máy chủ theo dõi này. Khi thành công, accept() trả về một BluetoothSocket đã kết nối.

  3. Nếu bạn không muốn chấp nhận thêm kết nối, hãy gọi close().

    Lệnh gọi phương thức này giải phóng ổ cắm máy chủ và tất cả tài nguyên của nó, nhưng không đóng BluetoothSocket được kết nối được trả về bởi accept() Không giống như TCP/IP, RFCOMM chỉ cho phép một ứng dụng được kết nối trên mỗi cùng một lúc, vì vậy, trong hầu hết các trường hợp, bạn nên gọi close() trên BluetoothServerSocket ngay sau khi chấp nhận một ổ cắm đã kết nối.

Vì lệnh gọi accept() là một lệnh gọi chặn, bạn không nên thực thi lệnh gọi này trong lệnh gọi chính luồng giao diện người dùng hoạt động. Việc thực thi đoạn mã đó trong một luồng khác đảm bảo rằng ứng dụng của bạn có thể vẫn phản hồi các tương tác khác của người dùng. Thường thì họ nên làm mọi việc liên quan đến BluetoothServerSocket hoặc BluetoothSocket trong một luồng mới do ứng dụng của bạn quản lý. Để huỷ một lệnh gọi bị chặn, chẳng hạn như accept(), hãy gọi close() trên BluetoothServerSocket hoặc BluetoothSocket từ một chuỗi khác. Ghi chú tất cả phương thức trên BluetoothServerSocket hoặc BluetoothSocket đều an toàn cho luồng.

Ví dụ

Sau đây là một luồng đơn giản cho thành phần máy chủ chấp nhận kết nối đến:

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);
       }
   }
}

Trong ví dụ này, chỉ cần một kết nối đến, vì vậy ngay khi một kết nối được chấp nhận và có được BluetoothSocket, ứng dụng vượt qua đã thu nạp BluetoothSocket vào một chuỗi riêng biệt, hãy đóng BluetoothServerSocket và thoát khỏi vòng lặp.

Lưu ý rằng khi accept() trả về BluetoothSocket, ổ cắm đã đã kết nối. Do đó, bạn không nên gọi connect(), giống như bạn từ phía máy khách.

Phương thức manageMyConnectedSocket() dành riêng cho ứng dụng được thiết kế để khởi tạo chuỗi thảo luận về việc chuyển dữ liệu, vấn đề này sẽ được thảo luận trong chủ đề về đang chuyển Bluetooth dữ liệu.

Thông thường, bạn nên đóng BluetoothServerSocket ngay khi hoàn tất đang nghe các kết nối đến. Trong ví dụ này, close() sẽ được gọi ngay khi có BluetoothSocket. Bạn cũng nên cung cấp phương thức trong chuỗi của bạn có thể đóng BluetoothSocket riêng tư trong sự kiện mà bạn cần dừng theo dõi trên ổ cắm máy chủ đó.

Kết nối với tư cách là khách hàng

Để bắt đầu kết nối với thiết bị từ xa đang chấp nhận kết nối trên một cổng máy chủ mở, trước tiên, bạn phải có được một BluetoothDevice đại diện cho thiết bị từ xa. Để tìm hiểu cách tạo một BluetoothDevice, xem phần Tìm Bluetooth thiết bị. Bạn phải sau đó sử dụng BluetoothDevice để có được BluetoothSocket và bắt đầu kết nối.

Quy trình cơ bản như sau:

  1. Sử dụng BluetoothDevice, nhận BluetoothSocket bằng cách gọi createRfcommSocketToServiceRecord(UUID).

    Phương thức này khởi chạy một đối tượng BluetoothSocket cho phép ứng dụng kết nối với một BluetoothDevice. Mã nhận dạng duy nhất (UUID) được truyền ở đây phải khớp với UUID được sử dụng bởi thiết bị máy chủ khi nó được gọi listenUsingRfcommWithServiceRecord(String, UUID) để mở BluetoothServerSocket. Để sử dụng UUID phù hợp, hãy mã hoá cứng chuỗi UUID vào ứng dụng của bạn, sau đó tham chiếu chuỗi đó từ cả máy chủ và mã khách hàng của bạn.

  2. Bắt đầu kết nối bằng cách gọi connect(). Lưu ý rằng phương pháp này là cuộc gọi chặn.

    Sau khi ứng dụng gọi phương thức này, hệ thống sẽ thực hiện thao tác tra cứu SDP để tìm thiết bị từ xa có mã nhận dạng duy nhất (UUID) trùng khớp. Nếu tra cứu thành công và thiết bị từ xa chấp nhận kết nối và chia sẻ kênh RFCOMM để sử dụng trong khi kết nối và phương thức connect() trả về. Nếu kết nối không thành công hoặc nếu phương thức connect() hết thời gian chờ (sau khoảng 12 giây), thì phương thức này sẽ gửi một IOException.

connect() là một lệnh gọi chặn, bạn phải luôn thực hiện thao tác này quy trình kết nối trong một luồng tách biệt với hoạt động chính (giao diện người dùng) chuỗi.

Ví dụ

Sau đây là ví dụ cơ bản về luồng ứng dụng khách bắt đầu Bluetooth kết nối:

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);
       }
   }
}

Lưu ý trong đoạn mã này, cancelDiscovery() được gọi trước kết nối lỗi. Bạn phải luôn gọi cancelDiscovery() trước connect(), đặc biệt là vì cancelDiscovery() thành công bất kể thiết bị hiện đang được khám phá. Nếu ứng dụng của bạn cần xác định xem quá trình khám phá thiết bị đang diễn ra, bạn có thể kiểm tra bằng cách sử dụng isDiscovering().

Phương thức manageMyConnectedSocket() dành riêng cho ứng dụng được thiết kế để khởi tạo chuỗi để chuyển dữ liệu, sẽ được thảo luận trong phần về chuyển dữ liệu Bluetooth.

Khi bạn dùng xong BluetoothSocket, hãy luôn gọi close(). Đang thực hiện ngay lập tức đóng ổ cắm đã kết nối và huỷ bỏ mọi bộ nhớ nội bộ có liên quan của chúng tôi.