Connecting Devices
In order to create a connection between your application on 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 to initiate a connection). 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 Managing a Connection. This section describes how to initiate the connection between two devices.
The server device and the client device each obtain the required BluetoothSocket
in different ways. The server will receive it when an incoming connection is accepted. The client will receive it when it opens an RFCOMM channel to the server.
One implementation technique is to automatically prepare each device as a server, so that each one has a server socket open and listening for connections. Then 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 can simply initiate the connection.
Note: If the two devices have not been previously paired, then the Android framework will automatically show a pairing request notification or dialog to the user during the connection procedure, as shown in Figure 3. So when attempting to connect devices, your application does not need to be concerned about whether or not the devices are paired. Your RFCOMM connection attempt will block until the user has successfully paired, or will fail if the user rejects pairing, or if pairing fails or times out.
Connecting 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 when one is accepted, provide a connected BluetoothSocket
. When the BluetoothSocket
is acquired from the BluetoothServerSocket
, the BluetoothServerSocket
can (and should) be discarded, unless you want to accept more connections.
About UUID
A Universally Unique Identifier (UUID) is a standardized 128-bit format for a string ID used to uniquely identify information. The point of a UUID is that it's big enough that you can select any random and it won't clash. In this case, it's used to uniquely identify your application's Bluetooth service. To get a UUID to use with your application, you can use one of the many random UUID generators on the web, then initialize a UUID
with fromString(String)
.
Here's the basic procedure to set up a server socket and accept a connection:
- Get a
BluetoothServerSocket
by calling thelistenUsingRfcommWithServiceRecord(String, UUID)
.The string is an identifiable name of your service, which the system will automatically write to a new Service Discovery Protocol (SDP) database entry on the device (the name is arbitrary and can simply be your application name). The UUID is also included in the SDP entry and will be the basis for the connection agreement with the client device. That is, when the client attempts to connect with this device, it will carry 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 (in the next step).
- Start listening for connection requests by calling
accept()
.This is a blocking call. It will return 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 with a UUID matching the one registered with this listening server socket. When successful,
accept()
will return a connectedBluetoothSocket
. - Unless you want to accept additional connections, call
close()
.This releases the server socket and all its resources, but does not close the connected
BluetoothSocket
that's been returned byaccept()
. Unlike TCP/IP, RFCOMM only allows 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.
The accept()
call should not be executed in the main Activity UI thread because it is a blocking call and will prevent any other interaction with the application. It usually makes sense to do all work with a BluetoothServerSocket
or BluetoothSocket
in a new thread managed by your application. To abort a blocked call such as accept()
, call close()
on the BluetoothServerSocket
(or BluetoothSocket
) from another thread and the blocked call will immediately return. Note that all methods on a BluetoothServerSocket
or BluetoothSocket
are thread-safe.
Example
Here's a simplified thread for the server component that accepts incoming connections:
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 = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (IOException 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) {
break;
}
// If a connection was accepted
if (socket != null) {
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(socket);
mmServerSocket.close();
break;
}
}
}
/** Will cancel the listening socket, and cause the thread to finish */
public void cancel() {
try {
mmServerSocket.close();
} catch (IOException e) { }
}
}
In this example, only one incoming connection is desired, so as soon as a connection is accepted and the BluetoothSocket
is acquired, the application sends the acquired BluetoothSocket
to a separate thread, closes the BluetoothServerSocket
and breaks the loop.
Note that when accept()
returns the BluetoothSocket
, the socket is already connected, so you should not call connect()
(as you do from the client-side).
manageConnectedSocket()
is a fictional method in the application that will initiate the thread for transferring data, which is discussed in the section about Managing a Connection.
You should usually 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 the server socket.
Connecting as a client
In order to initiate a connection with a remote device (a device holding an open server socket), you must first obtain a BluetoothDevice
object that represents the remote device. (Getting a BluetoothDevice
is covered in the above section about Finding Devices.) You must then use the BluetoothDevice
to acquire a BluetoothSocket
and initiate the connection.
Here's the basic procedure:
- Using the
BluetoothDevice
, get aBluetoothSocket
by callingcreateRfcommSocketToServiceRecord(UUID)
.This initializes a
BluetoothSocket
that will connect to theBluetoothDevice
. The UUID passed here must match the UUID used by the server device when it opened itsBluetoothServerSocket
(withlistenUsingRfcommWithServiceRecord(String, UUID)
). Using the same UUID is simply a matter of hard-coding the UUID string into your application and then referencing it from both the server and client code. - Initiate the connection by calling
connect()
.Upon this call, the system will perform an SDP lookup on the remote device in order to match the UUID. If the lookup is successful and the remote device accepts the connection, it will share the RFCOMM channel to use during the connection and
connect()
will return. This method is a blocking call. If, for any reason, the connection fails or theconnect()
method times out (after about 12 seconds), then it will throw an exception.Because
connect()
is a blocking call, this connection procedure should always be performed in a thread separate from the main Activity thread.Note: You should always ensure that the device is not performing device discovery when you call
connect()
. If discovery is in progress, then the connection attempt will be significantly slowed and is more likely to fail.
Example
Here is a basic example of a thread that initiates a Bluetooth connection:
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;
// Get a BluetoothSocket to connect with the given BluetoothDevice
try {
// MY_UUID is the app's UUID string, also used by the server code
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) { }
mmSocket = tmp;
}
public void run() {
// Cancel discovery because it will slow down the connection
mAdapter.cancelDiscovery();
try {
// Connect the device through the socket. This will block
// until it succeeds or throws an exception
mmSocket.connect();
} catch (IOException connectException) {
// Unable to connect; close the socket and get out
try {
mmSocket.close();
} catch (IOException closeException) { }
return;
}
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(mmSocket);
}
/** Will cancel an in-progress connection, and close the socket */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
Notice that cancelDiscovery()
is called before the connection is made. You should always do this before connecting and it is safe to call without actually checking whether it is running or not (but if you do want to check, call isDiscovering()
).
manageConnectedSocket()
is a fictional method in the application that will initiate the thread for transferring data, which is discussed in the section about Managing a Connection.
When you're done with your BluetoothSocket
, always call close()
to clean up. Doing so will immediately close the connected socket and clean up all internal resources.