为了在两台设备上创建一个连接,你必须在软件上实现服务器端和客户端的机制,因为一个设备必须必须打开一个server socket,而另一个必须初始化这个连接(使用服务器端设备的MAC地址进行初始化)。
当服务器端和客户端在同一个RFCOMM信道上都有一个BluetoothSocket时,就可以认为它们之间建立了一个连接。在这个时刻,每个设备能获得一个输出流和一个输入流,也能够开始数据传输。本节介绍如何在两个设备之间初始化一个连接。
服务器端和客户端获得BluetoothSocket的方法是不同的,服务器端是当一个进入的连接被接受时才产生一个BluetoothSocket,客户端是在打开一个到服务器端的RFCOMM信道时获得BluetoothSocket的。
一种实现技术是,每一个设备都自动作为一个服务器,所以每个设备都有一个server socket并监听连接。然后每个设备都能作为客户端建立一个到另一台设备的连接。另外一种代替方法是,一个设备按需打开一个server socket,另外一个设备仅初始化一个到这个设备的连接。
Note: 如果两个设备在建立连接之前并没有配对,那么在建立连接的过程中,Android框架将自动显示一个配对请求的notification或者一个对话框,如Figure 3所示。所以,在尝试连接设备时,你的应用程序无需确保设备之间已经进行了配对。你的RFCOMM连接将会在用户确认配对之后继续进行,或者用户拒绝或者超时之后失败。
当你想要连接两个设备时,其中一个必须保持一个打开的BluetoothServerSocket,作为服务器。服务器socket将监听进入的连接请求,一旦连接被接受,将产生一个BluetoothSocket。
一个Universally Unique Identifier(UUID)是一个字符串ID的标准化128位格式,将被用于唯一标识信息。你可以使用web上的任何一款UUID产生器为你的程序获取一个UUID,然后使用fromString(String)初始化一个UUID。你可以用UUID来标识你的蓝牙服务。
这儿是展示设置一个server socket并接受一个连接的基础过程:
listenUsingRfcommWithServiceRecord(String, UUID)获得一个BluetoothServerSocket。
字符串参数是你的服务的标识名,系统将自动将这个标识名写到设备上一个新的Service Discovery Protocol(SDP)数据库条目中(这个标识名可以简单地作为你的程序的名字,也就是说可以把你的程序的名字作为命名)。UUID也会被包含在这个新的SDP条目中,并作为与客户端设备建立连接的基础。 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).
accept()开始监听连接请求
这是一个阻塞调用。当一个连接被接受或者发生异常时将返回。一个连接,仅当一个远程设备发出的请求包含UUID匹配正在监听的server socket所注册的UUID时被接受。成功的时候,accept()将返回一个连接的BluetoothSocket。
这将释放server socket和它占用的所有资源,但不要用来关闭accept返回的已连接的BluetoothSocket。不像TCP/IP,RFCOMM仅允许一个信道在某一时刻有一个连接的客户端。所以,创建了一个连接的socket之后立即调用close()来关闭BluetoothServerSocket。
accept()方法不应该在主Activity UI线程中执行,因为它是一个阻塞调用,如果在主Activity UI线程中条也能够将会阻止与用户的交互。一般使用BluetoothServerSocket或者BluetoothSocket进行相关工作时都是在一个新的线程中。为了避免调用诸如accept()这样的阻塞调用,针对来自其他线程的BluetoothServerSocket或者BluetoothSocket调用close()将会使阻塞调用立即返回。注意,针对BluetoothServerSocket或者BluetoothSocket调用的方法都是线程安全的,也就是说可以在多个线程中使用。
manageConnectedSocket()
这个方法能初始化用于传输数据的线程。
感觉这些英语基本上都能看懂,就不翻译了...
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:
BluetoothDevice
, get a BluetoothSocket
by calling createRfcommSocketToServiceRecord(UUID)
. This initializes a BluetoothSocket
that will connect to the BluetoothDevice
. The UUID passed here must match the UUID used by the server device when it opened its BluetoothServerSocket
(with listenUsingRfcommWithServiceRecord(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.
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 the connect()
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.
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.
当你成功地连接了两台(或多台)设备时,每个设备都有一个已连接的BluetoothSocket。这时你可以在设备之间共享数据,乐趣才刚开始。 使用BluetoothSocket,传输二进制数据的过程是简单的:
首先,你必须使用一个线程专门用于数据的读或写。这是非常重要的,因为read(byte[])和write(byte[])方法都是阻塞调用。read(byte[])将会阻塞到流中有数据可读。write(byte[])一般不会阻塞,但当远程设备的中间缓冲区已满而对方没有及时地调用read(byte[])时将会一直阻塞。所以,你的线程中的主循环将一直用于从InputStream中读取数据。 A separate public method in the thread can be used to initiate writes to the OutputStream
.