Łączenie urządzeń Bluetooth

Aby utworzyć połączenie między 2 urządzeniami, musisz zaimplementować oba po stronie serwera i klienta, ponieważ jedno urządzenie musi otworzyć serwer i drugie musi zainicjować połączenie przy użyciu Adres MAC. Serwer i urządzenie klienckie uzyskują wymagane BluetoothSocket w różnych kilka sposobów. Serwer odbiera informacje o gniazdach, gdy połączenie przychodzące jest zaakceptowano. Klient podaje informacje o gniazdku, gdy otwiera kanał RFCOMM do serwera.

Serwer i klient są uważane za połączone, jeśli mają połączone urządzenie BluetoothSocket w tym samym kanale RFCOMM. W tym momencie każdy może pobierać strumienie wejściowe i wyjściowe i może rozpocząć się przesyłanie danych, jest omówione w sekcji o przenoszeniu Bluetootha, danych. Ta sekcja zawiera opis sposobu inicjowania połączenia między dwoma urządzeniami.

Upewnij się, że masz odpowiedni Uprawnienia Bluetooth oraz skonfiguruj Bluetooth w aplikacji przed wyszukiwania urządzeń Bluetooth.

Techniki nawiązywania połączeń

Jedną z technik implementacji jest automatyczne przygotowanie każdego urządzenia jako serwera dzięki czemu każde urządzenie ma otwarte gniazdo serwera i nasłuchuje połączeń. W w tym przypadku jedno urządzenie może zainicjować połączenie z drugim i stać się klienta. Jedno urządzenie może też bezpośrednio hostować połączenie i otworzyć plik z gniazda serwera na żądanie, a drugie urządzenie inicjuje połączenie.


() Rysunek 1. Okno parowania Bluetooth.

Połącz jako serwer

Jeśli chcesz połączyć 2 urządzenia, jedno musi działać jako serwer, przytrzymując przycisk otwórz BluetoothServerSocket Gniazdo serwera służy do nasłuchiwania przychodzących żądań połączenia i po zaakceptowaniu prośby podaj BluetoothSocket połączony. Gdy Pole BluetoothSocket jest pozyskiwane z: BluetoothServerSocket, BluetoothServerSocket można – i powinien – odrzucić, chyba że chcesz aby mogło akceptować więcej połączeń.

Aby skonfigurować gniazdo serwera i zaakceptować połączenie, wykonaj następujące czynności sekwencja kroków:

  1. Zadzwoń, aby otrzymać BluetoothServerSocket listenUsingRfcommWithServiceRecord(String, UUID)

    Ciąg znaków to rozpoznawalna nazwa usługi, którą system automatycznie zapisuje w nowym wpisie w bazie danych protokołu Service Discovery Protocol (SDP) na urządzeniu. Nazwa jest dowolna i może być po prostu nazwą Twojej aplikacji. Unikalny identyfikator uniwersalny (UUID) jest także uwzględniony we wpisie SDP. i stanowi podstawę umowy dotyczącej połączenia z urządzeniem klienckim. Ten Przy próbie połączenia się z tym urządzeniem klient będzie miał identyfikator UUID jednoznacznie identyfikują usługę, z którą chce się połączyć. Te Aby połączenie zostało zaakceptowane, identyfikatory UUID muszą być takie same.

    Identyfikator UUID to ustandaryzowany 128-bitowy format identyfikatora ciągu znaków używanego do identyfikować informacje. Identyfikator UUID służy do identyfikowania informacji, które należy jest unikalny w systemie lub sieci, ponieważ prawdopodobieństwo, że identyfikator UUID będzie powtarzanie wynosi w rzeczywistości zero. Jest on generowany niezależnie, scentralizowanego organu. W tym przypadku jest on używany do jednoznacznej identyfikacji usługa Bluetooth aplikacji. Aby uzyskać identyfikator UUID do wykorzystania w aplikacji, możesz go użyć spośród wielu przypadkowych do wygenerowania UUID w internecie, a następnie zainicjuj Identyfikator UUID z fromString(String)

  2. Zacznij nasłuchiwać próśb o połączenie przez połączenie telefoniczne accept()

    To jest połączenie blokujące. Ten komunikat pojawia się, gdy jedno połączenie zostało lub wystąpił wyjątek. Połączenie jest akceptowane tylko wtedy, gdy urządzenie zdalne wysłało żądanie połączenia zawierające zgodny identyfikator UUID zarejestrowane w tym gniazdie serwera nasłuchu. Jeśli operacja się uda, Funkcja accept() zwraca połączone źródło BluetoothSocket.

  3. Jeśli nie chcesz akceptować dodatkowych połączeń, zadzwoń pod close()

    To wywołanie metody zwalnia gniazdo serwera i wszystkie jego zasoby, ale nie zamyka połączonego elementu BluetoothSocket, który został zwrócony przez accept() W przeciwieństwie do TCP/IP, RFCOMM zezwala tylko na jednego połączonego klienta na jednocześnie, więc w większości przypadków warto zadzwonić pod numer close(). BluetoothServerSocket natychmiast po zaakceptowaniu podłączonego gniazdka.

Wywołanie accept() jest wywołaniem blokującym, więc nie wykonuj go w elemencie głównym wątek UI aktywności. Wykonanie go w innym wątku sprawi, że aplikacja nadal reagować na inne interakcje użytkowników. Zazwyczaj warto wykonać całą pracę obejmujący BluetoothServerSocket lub BluetoothSocket w nowym wątku i zarządzane przez Twoją aplikację. Aby przerwać zablokowane połączenie, np. accept(), zadzwoń pod numer close() w: BluetoothServerSocket lub BluetoothSocket z innego wątku. Notatka że wszystkie metody w obiekcie BluetoothServerSocket lub BluetoothSocket są i nie tylko w wątkach.

Przykład

Poniżej znajduje się uproszczony wątek komponentu serwera, który akceptuje połączenia przychodzące:

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

W tym przykładzie wymagane jest tylko jedno połączenie przychodzące, więc gdy tylko zostanie zaakceptowane połączenie i uzyskano: BluetoothSocket, aplikacja przekazuje pozyskał(a) użytkownika BluetoothSocket w osobnym wątku, zamyka BluetoothServerSocket i wyjdzie poza pętlę.

Pamiętaj, że gdy accept() zwraca BluetoothSocket, gniazdo jest już – podłączono Nie należy więc wywoływać connect(), tak jak Ty po stronie klienta.

Metoda manageMyConnectedSocket() obowiązująca w konkretnej aplikacji służy do inicjowania wątku dotyczącego przenoszenia danych, który jest omówiony przenoszenie danych Bluetooth .

Zazwyczaj należy zamknąć urządzenie BluetoothServerSocket zaraz po zakończeniu pracy nasłuchującego połączeń przychodzących. W tym przykładzie funkcja close() jest wywoływana w najbliższym czasie wraz z pozyskiwaniem funkcji BluetoothSocket. Warto też podać publiczny adres URL w wątku, która może zamknąć prywatny BluetoothSocket w wydarzeniu musisz przestać nasłuchiwać w tym gnieździe serwera.

Połącz jako klient

Aby zainicjować połączenie z urządzeniem zdalnym, które akceptuje połączeń z otwartym gniazdem serwera, musisz najpierw uzyskać BluetoothDevice który reprezentuje urządzenie zdalne. Aby dowiedzieć się, jak utworzyć BluetoothDevice, zobacz Znajdź Bluetooth urządzeń. Musisz użyj BluetoothDevice do uzyskania BluetoothSocket i zainicjuj połączenia.

Podstawowa procedura jest następująca:

  1. Użyj karty BluetoothDevice, aby otrzymać BluetoothSocket, dzwoniąc createRfcommSocketToServiceRecord(UUID).

    Ta metoda inicjuje obiekt BluetoothSocket, który umożliwia klientowi połączyć się z urządzeniem BluetoothDevice. Przekazany tutaj identyfikator UUID musi być zgodny z używanym identyfikatorem UUID przez urządzenie serwera, gdy została wywołana listenUsingRfcommWithServiceRecord(String, UUID) aby ją otworzyć: BluetoothServerSocket. Aby użyć pasującego identyfikatora UUID, zakoduj na stałe parametr UUID do aplikacji, a potem odwoływanie się do niego zarówno z serwera, i kod klienta.

  2. Zainicjuj połączenie, dzwoniąc pod numer connect(). Pamiętaj, że ta metoda jest blokując połączenie.

    Gdy klient wywoła tę metodę, system przeprowadza wyszukiwanie SDP, aby znaleźć urządzenie zdalne z pasującym identyfikatorem UUID. Jeśli wyszukiwanie zakończy się pomyślnie, a urządzenie zdalne zaakceptuje połączenie, współdzieli kanał RFCOMM do używania podczas połączenia, a metoda connect() zostanie zwrócona. Jeśli połączenie nie uda się lub po przekroczeniu limitu czasu metody connect() (po około 12 sekundach), metoda zwraca IOException.

Ponieważ connect() to wywołanie blokujące, należy je zawsze wykonywać procedura nawiązywania połączenia w wątku oddzielnym od głównego działania (UI) w wątku.

Przykład

Oto podstawowy przykład wątku klienta, który inicjuje połączenie Bluetooth połączenie:

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

Zwróć uwagę, że w tym fragmencie kodu usługa cancelDiscovery() jest wywoływana przed połączeniem może być przeprowadzana. Zawsze należy dzwonić pod numer cancelDiscovery() przed connect(), zwłaszcza, że cancelDiscovery() działa niezależnie od tego, czy urządzenie trwa odkrywanie. Jeśli aplikacja musi określić, wykrywanie urządzeń jest w toku, możesz to sprawdzić za pomocą isDiscovering()

Metoda manageMyConnectedSocket() obowiązująca w konkretnej aplikacji służy do inicjowania wątku przenoszenia danych, który jest omówiony w sekcji przenoszenie danych Bluetooth.

Gdy skończysz korzystać z urządzenia BluetoothSocket, zawsze dzwoń pod numer close(). Robię to powoduje natychmiastowe zamknięcie podłączonego gniazda i zwolnienie wszystkich powiązanych i zasobami Google Cloud.