查找设备
使用BluetoothAdapter对象,能够通过设备发现或查询已配对的设备列表来找到远程的蓝牙设备。
设备发现是一个扫描过程,该过程搜索本地区域内可用的蓝牙设备,然后请求一些彼此相关的一些信息(这个过程被叫做“发现”、“查询”或“扫描”)。但是,本地区域内的蓝牙设备只有在它们也启用了可发现功能时,才会响应发现请求。如果一个设备是可发现的,那么它会通过共享某些信息(如设备名称、类别和唯一的MAC地址)来响应发现请求。使用这些信息,执行发现处理的设备能够有选择的初始化跟被发现设备的连接。
一旦跟远程的设备建立的首次连接,配对请求就会自动的被展现给用户。当设备完成配对,相关设备的基本信息(如设备名称、类别和MAC地址)就会被保存,并能够使用蓝牙API来读取。使用已知的远程设备的MAC地址,在任何时候都能够初始化一个连接,而不需要执行发现处理(假设设备在可连接的范围内)。
要记住配对和连接之间的差异。配对意味着两个设备对彼此存在性的感知,它们之间有一个共享的用于验证的连接密钥,用这个密钥两个设备之间建立被加密的连接。连接意味着当前设备间共享一个RFCOMM通道,并且能够被用于设备间的数据传输。当前Android蓝牙API在RFCOMM连接被建立之前,要求设备之间配对。(在使用蓝牙API初始化加密连接时,配对是自动被执行的。)
以下章节介绍如何发现已配对的设备,或发现新的使用了可发现功能的设备。
注意:默认Android设备是不可发现的。用户能够通过系统设置在限定的时间内变成可发现的设备,或者应用程序能够请求用户启用可发现性,而不离开应用程序。如何启用可发现性,会在下文来讨论。
查询配对设备
在执行设备发现之前,应该先查询已配对的设备集合,来看期望的设备是否是已知的。调用getBondedDevices()方法来完成这件工作。这个方法会返回一个代表已配对设备的BluetoothDevice对象的集合。例如,你能够查询所有的配对设备,然后使用一个ArrayAdapter对象把每个已配对设备的名称显示给用户。
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if(pairedDevices.size()>0){
// Loop through paired devices
for(BluetoothDevice device : pairedDevices){
// Add the name and address to an array adapter to show in a ListView
mArrayAdapter.add(device.getName()+"\n"+ device.getAddress());
}
}
从BluetoothDevice对象来初始化一个连接所需要的所有信息就是MAC地址。在这个例子中,MAC地址被作为ArrayAdapter的一部分来保存,并显示给用户。随后,该MAC地址能够被提取用于初始化连接。
发现设备
简单的调用startDiscovery()方法就可以开始发现设备。该过程是异步的,并且该方法会立即返回一个布尔值来指明发现处理是否被成功的启动。通常发现过程会查询扫描大约12秒,接下来获取扫描发现的每个设备的蓝牙名称。
为了接收每个被发现设备的的信息,你的应用程序必须注册一个ACTION_FOUND类型的广播接收器。对应每个蓝牙设备,系统都会广播ACTION_FOUND类型的Intent。这个Intent会携带EXTRA_DEVICE和EXTRA_CLASS附加字段,这个两个字段分别包含了BluetoothDevice和BluetoothClass对象。例如,下列演示了你如何注册和处理设备发现时的广播:
// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// Add the name and address to an array adapter to show in a ListView
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don‘t forget to unregister during onDestroy
警告:执行设备发现,对于蓝牙适配器来说是一个沉重的过程,它会消耗大量的资源。一旦发现要连接设备,在尝试连接之前一定要确认用cancelDiscovery()方法来终止发现操作。另外,如果已经有一个跟设备的连接,那么执行发现会明显的减少连接的可用带宽,因此在有连接的时候不应该执行发现处理。
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对象,并初始化该连接。
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()方法来进行清理工作。这样做会立即关闭被连接的套接字,并清理所有的内部资源。
Android的联通性---Bluetooth(六)
管理连接
当你成功的连接了两个(或更多)设备时,每一个设备都有一个被连接的BluetoothSocket对象。这是良好的开始,因为你能够在设备之间共享数据。使用BluetoothSocket对象来传输任意数据的过程是简单的:
1. 分别通过getInputStream()和getOutputStream()方法来获得通过套接字来处理传输任务的InputStream和OutputStream对象;
2. 用read(byte[])和write(byte[])方法来读写流中的数据。
当然,有更多实现细节要考虑。首先和前提,对于所有数据流的读写应该使用专用的线程。这是重要的,因为read(byte[])和write(byte[])方法是阻塞式调用。Read(byte[])方法在从数据流中读取某些数据之前一直是阻塞的。write(byte[])方法通常是不阻塞的,但是对于流的控制,如果远程设备不是足够快的调用read(byte[])方法,并且中间缓存被填满,那么write(byte[])方法也会被阻塞。因此,你的线程中的主循环应该是专用于从InputStream对象中读取数据。在线程类中有一个独立的公共方法用于初始化对OutputStream对象的写入操作。
示例
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
// Get the input and output streams, using temp objects because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI activity
mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
break;
}
}
}
/* Call this from the main activity to send data to the remote device */
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) { }
}
/* Call this from the main activity to shutdown the connection */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
上例中,构造器用于获取必要的流对象,并且一旦被执行,线程就会等待通过InputStream对象传入的输入。当read(byte[])方法从数据流中返回指定字节的数据时,使用来自其父类的Handler把该数据会被发送给主Activity,然后返回继续等待流中的更多的字节。
发送输出数据就像在主Activity中调用线程的write()方法一样简单,并且给该方法传入要发送的字节。然后这个方法简单的调用write(byte[])方法把数据发送给远程设备。
线程的cancel()方法是至关重要的,以便该连接在任何时候能够通过关闭BluetoothSocket对象来终止。这个方法在使用完蓝牙连接时应该始终被调用。
使用配置来工作
从Android3.0开始,蓝牙API就包含了对用蓝牙配置来进行工作的支持。Bluetooth Profile是设备之间基于蓝牙进行通信的无线接口规范。其中一个示例是Hands-Free配置。对于一个连接到无线耳机的移动电话,移动电话和蓝牙耳机都必须支持Hands-Free配置。
你能够实现BluetoothProfile接口,来编写你自己的支持特殊蓝牙配置的类。Android蓝牙API提供了对以下蓝牙配置的实现:
· Headset(耳机):该配置提供了对用于移动电话的蓝牙耳机的支持。Android提供了BluetoothHeadset类,这个类是一个代理,它通过进程间通信(IPC)来控制蓝牙耳机服务。它包含了Bluetoot Headset和Hands-Free(v1.5)配置。BluetoothHeadset类包含了对AT命令的支持。关于AT命令的更多讨论,请看“Vendor-specific AT commands”。
· A2DP:Advanced Audio Distribution Profile的缩写。它定义如何流化高品质的音频,并让音频流在通过蓝牙连接在设备之间传输。Android提供了BluetoothA2dp类,它一个通过IPC来控制的蓝牙A2DP服务的代理。
· Health Device:Android4.0(API Level 14)中引入了对Bluetooth Health Device Profile(HDP)的支持。它能够让你创建一个跟支持蓝牙的健康保健设备进行蓝牙通信的应用程序,这些健康保健包括:心率监护仪、血压测量仪、体温计、体重秤等。对应它支持的设备的列表和它们相应的设备数据规范,蓝牙联盟的网站:www.bluetooth.org。注意,这些值也是参考了ISO/IEEE11073-20601[7]规范中命名编码规范附件中的名为MDC_DEV_SPEC_PROFILE_*规范。
以下是使用配置进行工作的基本步骤
1. 获取默认的适配器;
2.使用getProfileProxy()方法来建立一个与配置相匹配的配置代理对象的连接。在下面的示例中,配置代理对象是一个BluetoothHeadset对象实例;
3.创建一个BluetoothProfile.ServiceListener监听器,该监听器会在它们连接到服务器或中断与服务器的连接时,通知BluetoothProfile的IPC客户端。
4.在onServiceConnected()事件中,获得对配置代理对象的处理权;
5.一旦获得配置代理对象,就可以用它来监视连接的状态,并执行与配置有关的其他操作。
例如,以下代码片段显示如何连接一个BluetoothHeadset代理对象,以便控制Headset配置:
BluetoothHeadset mBluetoothHeadset;
// Get the default adapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// Establish connection to the proxy.
mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
mBluetoothHeadset = (BluetoothHeadset) proxy;
}
}
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.HEADSET) {
mBluetoothHeadset = null;
}
}
};
// ... call functions on mBluetoothHeadset
// Close proxy connection after use.
mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset);