作为连接的服务端
当你想要连接两个设备时,一个必须通过持有一个打开的BluetoothServerSocket对象来作为服务端。服务套接字的用途是监听输入的连接请求,并且在一个连接请求被接收时,提供一个BluetoothSocket连接对象。在从BluetoothServerSocket对象中获取BluetoothSocket时,BluetoothServerSocket能够(并且也应该)被废弃,除非你想要接收更多的连接。
以下是建立服务套接字和接收一个连接的基本过程。
1. 调用listenUsingRfcommWithServiceRecord(String, UUID)方法来获得一个BluetoothServerSocket对象。该方法中的String参数是一个可识别的你的服务端的名称,系统会自动的把它写入设备上的Service Discovery Protocol(SDP)数据库实体(该名称是任意的,并且可以简单的使用你的应用程序的名称)。UUID参数也会被包含在SDP实体中,并且是跟客户端设备连接的基本协议。也就是说,当客户端尝试跟服务端连接时,它会携带一个它想要连接的服务端能够唯一识别的UUID。只有在这些UUID完全匹配的情况下,连接才可能被接收。
2. 通过调用accept()方法,启动连接请求。这是一个阻塞调用。只有在连接被接收或发生异常的情况下,该方法才返回。只有在发送连接请求的远程设备所携带的UUID跟监听服务套接字所注册的一个UUID匹配的时候,该连接才被接收。连接成功,accept()方法会返回一个被连接的BluetoothSocket对象。
3. 除非你想要接收其他连接,否则要调用close()方法。该方法会释放服务套接字以及它所占用的所有资源,但不会关闭被连接的已经有accept()方法所返回的BluetoothSocket对象。跟TCP/IP不一样,每个RFCOMM通道一次只允许连接一个客户端,因此在大多数情况下,在接收到一个连接套接字之后,立即调用BluetoothServerSocket对象的close()方法是有道理的。
accept()方法的调用不应该在主Activity的UI线程中被执行,因为该调用是阻塞的,这会阻止应用程序的其他交互。通常在由应用程序所管理的一个新的线程中来使用BluetoothServerSocket对象或BluetoothSocket对象来工作。要终止诸如accept()这样的阻塞调用方法,就要从另一个线程中调用BluetoothServerSocket对象(或BluetoothSocket对象)的close()方法,这时阻塞会立即返回。注意在BluetoothServerSocket或BluetoothSocket对象上的所有方法都是线程安全的。
示例
以下是一个被简化的接收连接请求的服务端组件:
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 = mBluetoothAdapter.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) { }
}
}
在这个例子中,只希望有一个呼入连接,因此连接一旦被接收,并获取了一个BluetoothSocket对象,应用程序就会把获得的BluetoothSocket对象发送给一个独立的线程,然后关闭BluetoothServerSocket对象并中断循环。
注意,在accept()方法返回BluetoothSocket对象时,套接字已经是被连接的,因此你不应该再调用像客户端那样调用connect()方法了。
应用程序中的manageConnectedSocket()方法是一个自定义方法,它会初始化用于传输数据的线程。
通常,一旦你监听完成呼入连接,就应该关闭BluetoothServerSocket对象。在这个示例中,close()方法是在获得BluetoothSocket对象之后被调用的。你可能还想要提供一个公共方法,以便在你的线程中能够关闭你想要终止监听的服务套接字事件中的私有BluetoothSocket对象。
作为连接的客户端
为了初始化一个与远程设备(持有打开的服务套接字的设备)的连接,首先必须获取个代表远程设备的BluetoothDevice对象。然后使用BluetoothDevice对象来获取一个BluetoothSocket对象,并初始化该连接。
以下是一个基本的连接过程:
1. 通过调用BluetoothDevice的createRfcommSocketToServiceRecord(UUID)方法,获得一个BluetoothSocket对象。这个方法会初始化一个连接到BluetoothDevice对象的BluetoothSocket对象。传递给这个方法的UUID参数必须与服务端设备打开BluetoothServerSocket对象时所使用的UUID相匹配。在你的应用程序中简单的使用硬编码进行比对,如果匹配,服务端和客户端代码就可以应用这个BluetoothSocket对象了。
2. 通过调用connect()方法来初始化连接。在这个调用中,为了找到匹配的UUID,系统会在远程的设备上执行一个SDP查询。如果查询成功,并且远程设备接收了该连接请求,那么它会在连接期间共享使用RFCOMM通道,并且connect()方法会返回。这个方法是一个阻塞调用。如果因为某些原因,连接失败或连接超时(大约在12秒之后),就会抛出一个异常。
因为connect()方法是阻塞调用,这个连接过程始终应该在独立与主Activity线程之外的线程中被执行。
注意:在调用connect()方法时,应该始终确保设备没有正在执行设备发现操作。如果是在发现操作的过程中,那么连接尝试会明显的变慢,并且更像是要失败的样子。
示例
以下是初始化蓝牙连接线程的一个基本的例子:
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
mBluetoothAdapter.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) { }
}
}
在建立连接之前要调用cancelDiscovery()方法。在连接之前应该始终调用这个方法,并且不用实际的检查蓝牙发现处理是否正在运行也是安全的(如果想要检查,调用isDiscovering()方法)。
manageConnectedSocket()方法是一个应用程序中自定义的方法,它会初始化传输数据的线程。
当使用完BluetoothSocket对象时,要始终调用close()方法来进行清理工作。这样做会立即关闭被连接的套接字,并清理所有的内部资源。