To create a connection between two devices, you must implement both the
server-side and client-side mechanisms because one device must open a server
socket, and the other one must initiate the connection using the server device's
MAC address. The server device and the client device each obtain the required
BluetoothSocket
in different
ways. The server receives socket information when an incoming connection is
accepted. The client provides socket information when it opens an RFCOMM channel
to the server.
The server and client are considered connected to each other when they each have
a connected BluetoothSocket
on the same RFCOMM channel. At this point, each
device can obtain input and output streams, and data transfer can begin, which
is discussed in the section about transferring Bluetooth
data. This section
describes how to initiate the connection between two devices.
Make sure you have the appropriate Bluetooth permissions and set up your app for Bluetooth before attempting to find Bluetooth devices.
Connection techniques
One implementation technique is to automatically prepare each device as a server so that each device has a server socket open and listening for connections. In this case, either device can initiate a connection with the other and become the client. Alternatively, one device can explicitly host the connection and open a server socket on demand, and the other device initiates the connection.
Figure 1. The Bluetooth pairing dialog.
Connect as a server
When you want to connect two devices, one must act as a server by holding an
open
BluetoothServerSocket
.
The purpose of the server socket is to listen for incoming connection requests
and provide a connected BluetoothSocket
after a request is accepted. When the
BluetoothSocket
is acquired from the BluetoothServerSocket
, the
BluetoothServerSocket
can—and should—be discarded, unless you want
the device to accept more connections.
To set up a server socket and accept a connection, complete the following sequence of steps:
Get a
BluetoothServerSocket
by callinglistenUsingRfcommWithServiceRecord(String, UUID)
.The string is an identifiable name of your service, which the system automatically writes to a new Service Discovery Protocol (SDP) database entry on the device. The name is arbitrary and can simply be your app name. The Universally Unique Identifier (UUID) is also included in the SDP entry and forms the basis for the connection agreement with the client device. That is, when the client attempts to connect with this device, it carries a UUID that uniquely identifies the service with which it wants to connect. These UUIDs must match in order for the connection to be accepted.
A UUID is a standardized 128-bit format for a string ID used to uniquely identify information. A UUID is used to identify information that needs to be unique within a system or a network because the probability of a UUID being repeated is effectively zero. It is generated independently, without the use of a centralized authority. In this case, it's used to uniquely identify your app's Bluetooth service. To get a UUID to use with your app, you can use one of the many random
UUID
generators on the web, then initialize a UUID withfromString(String)
.Start listening for connection requests by calling
accept()
.This is a blocking call. It returns when either a connection has been accepted or an exception has occurred. A connection is accepted only when a remote device has sent a connection request containing a UUID that matches the one registered with this listening server socket. When successful,
accept()
returns a connectedBluetoothSocket
.Unless you want to accept additional connections, call
close()
.This method call releases the server socket and all its resources, but doesn't close the connected
BluetoothSocket
that's been returned byaccept()
. Unlike TCP/IP, RFCOMM allows only one connected client per channel at a time, so in most cases it makes sense to callclose()
on theBluetoothServerSocket
immediately after accepting a connected socket.
Because the accept()
call is a blocking call, do not execute it in the main
activity UI thread. Executing it in another thread ensures that your app can
still respond to other user interactions. It usually makes sense to do all work
that involves a BluetoothServerSocket
or BluetoothSocket
in a new thread
managed by your app. To abort a blocked call such as accept()
, call close()
on the BluetoothServerSocket
or BluetoothSocket
from another thread. Note
that all methods on a BluetoothServerSocket
or BluetoothSocket
are
thread-safe.
Example
The following is a simplified thread for the server component that accepts incoming connections:
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); } } }
In this example, only one incoming connection is desired, so as soon as a
connection is accepted and the BluetoothSocket
is acquired, the app passes the
acquired BluetoothSocket
to a separate thread, closes the
BluetoothServerSocket
, and breaks out of the loop.
Note that when accept()
returns the BluetoothSocket
, the socket is already
connected. Therefore, you shouldn't call
connect()
, as you do
from the client side.
The app-specific manageMyConnectedSocket()
method is designed to initiate the
thread for transferring data, which is discussed in the topic about
transferring Bluetooth
data.
Usually, you should close your BluetoothServerSocket
as soon as you are done
listening for incoming connections. In this example, close()
is called as soon
as the BluetoothSocket
is acquired. You may also want to provide a public
method in your thread that can close the private BluetoothSocket
in the event
that you need to stop listening on that server socket.
Connect as a client
In order to initiate a connection with a remote device that is accepting
connections on an open server socket, you must first obtain a BluetoothDevice
object that represents the remote device. To learn how to create a
BluetoothDevice
, see Find Bluetooth
devices. You must
then use the BluetoothDevice
to acquire a BluetoothSocket
and initiate the
connection.
The basic procedure is as follows:
Using the
BluetoothDevice
, get aBluetoothSocket
by callingcreateRfcommSocketToServiceRecord(UUID)
.This method initializes a
BluetoothSocket
object that allows the client to connect to aBluetoothDevice
. The UUID passed here must match the UUID used by the server device when it calledlistenUsingRfcommWithServiceRecord(String, UUID)
to open itsBluetoothServerSocket
. To use a matching UUID, hard-code the UUID string into your app, and then reference it from both the server and client code.Initiate the connection by calling
connect()
. Note that this method is a blocking call.After a client calls this method, the system performs an SDP lookup to find the remote device with the matching UUID. If the lookup is successful and the remote device accepts the connection, it shares the RFCOMM channel to use during the connection, and the
connect()
method returns. If the connection fails, or if theconnect()
method times out (after about 12 seconds), then the method throws anIOException
.
Because connect()
is a blocking call, you should always perform this
connection procedure in a thread that is separate from the main activity (UI)
thread.
Example
The following is a basic example of a client thread that initiates a Bluetooth connection:
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); } } }
Notice that in this snippet, cancelDiscovery()
is called before the connection
attempt occurs. You should always call cancelDiscovery()
before connect()
,
especially because cancelDiscovery()
succeeds regardless of whether device
discovery is currently in progress. If your app needs to determine whether
device discovery is in progress, you can check using
isDiscovering()
.
The app-specific manageMyConnectedSocket()
method is designed to initiate the
thread for transferring data, which is discussed in the section about
transferring Bluetooth data.
When you're done with your BluetoothSocket
, always call close()
. Doing so
immediately closes the connected socket and releases all related internal
resources.