Para criar uma conexão entre dois dispositivos, é necessário implementar os mecanismos do lado do servidor e do cliente, porque um dispositivo precisa abrir um soquete de servidor e o outro precisa iniciar a conexão usando o endereço MAC do dispositivo do servidor. O dispositivo servidor e o dispositivo cliente recebem o
BluetoothSocket
necessário de maneiras
diferentes. O servidor recebe informações de soquete quando uma conexão de entrada é
aceita. O cliente fornece informações sobre o soquete quando abre um canal RFCOMM para o servidor.
O servidor e o cliente são considerados conectados entre si quando cada um tem um BluetoothSocket
conectado no mesmo canal RFCOMM. Nesse ponto, cada
dispositivo pode receber streams de entrada e saída, e a transferência de dados pode começar, o que
é discutido na seção sobre como transferir dados
do Bluetooth. Esta seção
descreve como iniciar a conexão entre dois dispositivos.
Verifique se você tem as permissões de Bluetooth adequadas e configure seu app para usar esse recurso antes de tentar encontrar dispositivos Bluetooth.
Técnicas de conexão
Uma técnica de implementação é preparar automaticamente cada dispositivo como servidor para que cada dispositivo tenha um soquete de servidor aberto e detecte conexões. Nesse caso, qualquer um dos dispositivos pode iniciar uma conexão com o outro e se tornar o cliente. Como alternativa, um dispositivo pode hospedar explicitamente a conexão e abrir um soquete de servidor sob demanda, e o outro dispositivo inicia a conexão.
Figura 1. Caixa de diálogo de pareamento do Bluetooth.
Conectar como servidor
Para conectar dois dispositivos, um deles precisa atuar como servidor, mantendo um
BluetoothServerSocket
aberto.
O objetivo do soquete do servidor é detectar solicitações de conexão de entrada
e fornecer um BluetoothSocket
conectado depois que uma solicitação é aceita. Quando o
BluetoothSocket
é adquirido do BluetoothServerSocket
, o
BluetoothServerSocket
pode (e precisa) ser descartado, a menos que você queira
que o dispositivo aceite mais conexões.
Para configurar um soquete de servidor e aceitar uma conexão, siga estas etapas:
Para receber um
BluetoothServerSocket
, chamelistenUsingRfcommWithServiceRecord(String, UUID)
.A string é um nome identificável do serviço, que o sistema grava automaticamente em uma nova entrada de banco de dados do Service Discovery Protocol (SDP) no dispositivo. Ele é arbitrário e pode ser simplesmente o nome do app. O identificador universal exclusivo (UUID) também está incluído na entrada SDP e forma a base para o contrato de conexão com o dispositivo cliente. Ou seja, quando o cliente tenta se conectar com esse dispositivo, ele carrega um UUID que identifica exclusivamente o serviço com que quer se conectar. Esses UUIDs precisam ser correspondentes para que a conexão seja aceita.
Um UUID é um formato padronizado de 128 bits para um ID de string usado para identificar informações de maneira exclusiva. Um UUID é usado para identificar informações que precisam ser exclusivas em um sistema ou rede, porque a probabilidade de um UUID ser repetido é efetivamente zero. Eles são gerados de forma independente, sem o uso de uma autoridade centralizada. Nesse caso, ele é usado para identificar exclusivamente o serviço Bluetooth do app. Para ter um UUID a ser usado com seu app, use um dos muitos geradores aleatórios
UUID
na Web e inicialize um UUID comfromString(String)
.Comece a detectar solicitações de conexão chamando
accept()
.Essa é uma chamada de bloqueio. Ela retorna quando uma conexão é aceita ou ocorre uma exceção. A conexão só será aceita quando um dispositivo remoto enviar uma solicitação de conexão contendo um UUID correspondente ao registrado nesse soquete do servidor de detecção. Quando bem-sucedido,
accept()
retorna umBluetoothSocket
conectado.A menos que você queira aceitar outras conexões, chame
close()
.Essa chamada de método libera o soquete do servidor e todos os recursos dele, mas não fecha o
BluetoothSocket
conectado retornado poraccept()
. Ao contrário do TCP/IP, o RFCOMM permite apenas um cliente conectado por canal por vez. Portanto, na maioria dos casos, faz sentido chamarclose()
noBluetoothServerSocket
imediatamente após aceitar um soquete conectado.
Como a chamada accept()
é de bloqueio, não a execute na linha de execução
de interface da atividade principal. Executá-lo em outra linha de execução garante que seu app ainda possa
responder a outras interações do usuário. Geralmente, faz sentido fazer todo o trabalho
que envolve um BluetoothServerSocket
ou BluetoothSocket
em uma nova linha de execução
gerenciada pelo app. Para cancelar uma chamada bloqueada, como accept()
, chame close()
no BluetoothServerSocket
ou BluetoothSocket
em outra linha de execução. Todos
os métodos em um BluetoothServerSocket
ou BluetoothSocket
são
seguros para linha de execução.
Exemplo
Veja a seguir uma linha de execução simplificada para o componente do servidor que aceita conexões de entrada:
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); } } }
Neste exemplo, apenas uma conexão de entrada é desejada. Portanto, assim que uma
conexão for aceita e a BluetoothSocket
for adquirida, o app vai transmitir a
BluetoothSocket
adquirida para uma linha de execução separada, fechar a
BluetoothServerSocket
e sair da repetição.
Quando accept()
retornar o BluetoothSocket
, o soquete já estará
conectado. Portanto, não chame
connect()
, como faz
no lado do cliente.
O método manageMyConnectedSocket()
específico do app foi projetado para iniciar a
linha de execução para transferência de dados, o que é discutido no tópico sobre
como transferir dados
do Bluetooth.
Normalmente, você precisa fechar o BluetoothServerSocket
assim que terminar de
detectar as conexões recebidas. Neste exemplo, close()
é chamado assim
que o BluetoothSocket
é adquirido. Também convém fornecer um método
público na linha de execução que possa fechar o BluetoothSocket
particular caso
você precise parar de detectar no soquete do servidor.
Conectar como cliente
Para iniciar uma conexão com um dispositivo remoto que aceite
conexões em um soquete de servidor aberto, primeiro consiga um objeto BluetoothDevice
que represente o dispositivo remoto. Para aprender a criar um
BluetoothDevice
, consulte Encontrar dispositivos
Bluetooth. É necessário
usar o BluetoothDevice
para adquirir um BluetoothSocket
e iniciar a
conexão.
Este é o procedimento básico:
Usando o
BluetoothDevice
, receba umBluetoothSocket
chamandocreateRfcommSocketToServiceRecord(UUID)
.Esse método inicializa um objeto
BluetoothSocket
que permite que o cliente se conecte a umBluetoothDevice
. O UUID transmitido aqui precisa corresponder ao UUID usado pelo dispositivo servidor quando ele chamoulistenUsingRfcommWithServiceRecord(String, UUID)
para abrir oBluetoothServerSocket
. Para usar um UUID correspondente, codifique a string UUID no seu app e faça referência a ela no código do servidor e do cliente.Inicie a conexão chamando
connect()
. Observe que esse método é uma chamada de bloqueio.Depois que um cliente chama esse método, o sistema executa uma pesquisa de SDP para encontrar o dispositivo remoto com o UUID correspondente. Se a pesquisa for bem-sucedida e o dispositivo remoto aceitar a conexão, ele compartilhará o canal RFCOMM para uso durante a conexão, e o método
connect()
será retornado. Se a conexão falhar ou se o métodoconnect()
expirar (após cerca de 12 segundos), o método vai gerar umaIOException
.
Como connect()
é uma chamada de bloqueio, sempre execute esse
procedimento de conexão em uma linha de execução separada da linha de execução da atividade
principal (interface).
Exemplo
Este é um exemplo básico de uma linha de execução de cliente que inicia uma conexão Bluetooth:
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); } } }
Nesse snippet, cancelDiscovery()
é chamado antes da tentativa de
conexão. Sempre chame cancelDiscovery()
antes de connect()
,
principalmente porque cancelDiscovery()
será bem-sucedido, independente de a descoberta do
dispositivo estar em andamento ou não. Caso seu app precise determinar se
a descoberta do dispositivo está em andamento, verifique usando
isDiscovering()
.
O método manageMyConnectedSocket()
específico do app foi projetado para iniciar a
linha de execução para transferência de dados, o que é discutido na seção sobre
como transferir dados do Bluetooth.
Quando terminar de usar o BluetoothSocket
, sempre chame close()
. Essa ação
fecha imediatamente o soquete conectado e libera todos os recursos internos
relacionados.