赞
踩
Android的蓝牙API来完成的四个必要的主要任务,使用蓝牙进行设备通信,主要包含四个部分:蓝牙设置、搜索设备(配对的或可见的)、连接、传输数据。
一、 基础知识
所有的蓝牙API在android.bluetooth包中。实现这些功能主要需要下面这几个类和接口:
为了在你的应用中使用蓝牙功能,至少要在AndroidManifest.xml中声明两个权限:BLUETOOTH(任何蓝牙相关API都要使用这个权限) 和 BLUETOOTH_ADMIN(设备搜索、蓝牙设置等)。
为了执行蓝牙通信,例如连接请求,接收连接和传送数据都必须有BLUETOOTH权限。
必须要求BLUETOOTH_ADMIN的权限来启动设备发现或操纵蓝牙设置。大多数应用程序都需要这个权限能力,发现当地的蓝牙设备。此权限授予其他的能力不应该使用,除非应用程序是一个“电源管理”,将根据用户要求修改的蓝牙设置。
注释:要请求BLUETOOTH_ADMIN的话,必须要先有BLUETOOTH。
在你的应用通过蓝牙进行通信之前,你需要确认设备是否支持蓝牙,如果支持,确信它被打开。
如果不支持,则不能使用蓝牙功能。如果支持蓝牙,但不能够使用,你刚要在你的应用中请求使用蓝牙。这个要两步完成,使用BluetoothAdapter。
所有的蓝牙活动请求BluetoothAdapter,为了获取BluetoothAdapter,呼叫静态方法getDefaultAdapter() 。这个会返回一个BluetoothAdapter,代表设备自己的蓝牙适配器(蓝牙无线电)。这个蓝牙适配器应用于整个系统中,你的应用可以通过这个对象进行交互。如果getDefaultAdapter()返回null,则这个设备不支持蓝牙。例如:
- BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- if (mBluetoothAdapter == null) {
- // Device does not support Bluetooth
- }
其次。你需要确定蓝牙能够使用。通过isEnabled()来检查蓝牙当前是否可用。如果这个方法返回false,则蓝牙不能够使用。为了请求蓝牙使用,呼叫startActivityForResult()与的ACTION_REQUEST_ENABLE动作意图。通过系统设置中启用蓝牙将发出一个请求(不停止蓝牙应用)。例如:
- if (mBluetoothAdapter.isEnabled()) {
- Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
- startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
- }
REQUEST_ENABLE_BT常量作为一个整型传到startActivityForResult()中(值必须大于0),该系统传回给你,在你onActivityResult()作为实现的requestCode参数。
如果调用蓝牙成功,你的Activity就会在onActivityResult()中收到RESULT_OK结果,如果蓝牙不能使用由于错误(或用户响应“NO”那么结果返回RESULT_CANCELED。
除了通过onActivityResult(),还可以通过监听ACTION_STATE_CHANGED这个broadcast Intent来知道蓝牙状态是否改变。这个Intent包含EXTRA_STATE,EXTRA_PREVIOUS_STATE两个字段,分别代表新旧状态。可能的值是STATE_TURNING_ON, STATE_ON, STATE_TURNING_OFF, 还有STATE_OFF。
使用BluetoothAdapter可以通过搜索新设备或查询配对设备找到远程Bluetooth设备。
Device discovery(设备搜索)是一个扫描搜索本地已使能Bluetooth设备并且从搜索到的设备请求一些信息的过程(有时候会收到类似“discovering”,“inquiring”或“scanning”)。但是,搜索到的本地Bluetooth设备只有在打开被发现功能后才会响应一个discovery请求,响应的信息包括设备名,类,唯一的MAC地址。发起搜寻的设备可以使用这些信息来初始化跟被发现的设备的连接。
一旦与远程设备的第一次连接被建立,一个pairing请求就会自动提交给用户。如果设备已配对,配对设备的基本信息(名称,类,MAC地址)就被保存下来了,能够使用Bluetooth API来读取这些信息。使用已知的远程设备的MAC地址,连接可以在任何时候初始化而不必先完成搜索(当然这是假设远程设备是在可连接的空间范围内)。
需要记住,配对和连接是两个不同的概念:
目前Android Bluetooth API's要求设备在建立RFCOMM信道前必须配对(配对是在使用Bluetooth 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());
- }
- };
要开始搜索设备,只需简单的调用startDiscovery() 。该函数时异步的,调用后立即返回,返回值表示搜索是否成功开始。搜索处理通常包括一个12秒钟的查询扫描,然后跟随一个页面显示搜索到设备Bluetooth名称。
应用中可以注册一个带ACTION_FOUND Intent的BroadcastReceiver,搜索到每一个设备时都接收到消息。对于每一个设备,系统都会广播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

注意:完成设备搜索对于Bluetooth适配器来说是一个重量级的处理,要消耗大量它的资源。一旦你已经找到一个设备来连接,请确保你在尝试连接前使用了cancelDiscovery()来停止搜索。同样,如果已经保持了一个连接的时候,同时执行搜索设备将会显著的降低连接的带宽,所以在连接的时候不应该执行搜索发现。
如果想让本地设备被其他设备发现,可以带ACTION_REQUEST_DISCOVERABLE action Intent调用startActivityForResult(Intent, int) 方法。该方法会提交一个请求通过系统刚设置使设备出于可以被发现的模式(而不影响应用程序)。默认情况下,设备在120秒后变为可以被发现的。可以通过额外增加EXTRA_DISCOVERABLE_DURATION Intent自定义一个值,最大值是3600秒,0表示设备总是可以被发现的(小于0或者大于3600则会被自动设置为120秒)。下面示例设置时间为300:
- Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
- discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
- startActivity(discoverableIntent);
在规定的时间内,设备会静静的保持可以被发现模式。如果想在可以被发现模式被更改时受到通知,可以用ACTION_SCAN_MODE_CHANGED Intent注册一个BroadcastReceiver,包含额外的字段信息EXTRA_SCAN_MODE和EXTRA_PREVIOUS_SCAN_MODE分别表示新旧扫描模式,其可能的值为SCAN_MODE_CONNECTABLE_DISCOVERABLE(discoverable mode),SCAN_MODE_CONNECTABLE(not in discoverable mode but still able to receive connections),SCAN_MODE_NONE(not in discoverable mode and unable to receive connections)。
如果只需要连接远程设备就不需要打开设备的可以被发现功能。只在应用作为一个服务器socket的宿主用来接收进来的连接时才需要使能可以被发现功能,因为远程设备在初始化连接前必须先发现了你的设备。
为了在两台设备上创建一个连接,你必须在软件上实现服务器端和客户端的机制,因为一个设备必须必须打开一个server socket,而另一个必须初始化这个连接(使用服务器端设备的MAC地址进行初始化)。
当服务器端和客户端在同一个RFCOMM信道上都有一个BluetoothSocket时,就可以认为它们之间建立了一个连接。在这个时刻,每个设备能获得一个输出流和一个输入流,也能够开始数据传输。本节介绍如何在两个设备之间初始化一个连接。
服务器端和客户端获得BluetoothSocket的方法是不同的,服务器端是当一个进入的连接被接受时才产生一个BluetoothSocket,客户端是在打开一个到服务器端的RFCOMM信道时获得BluetoothSocket的。
Note: 如果两个设备在建立连接之前并没有配对,那么在建立连接的过程中,Android框架将自动显示一个配对请求的notification或者一个对话框。所以,在尝试连接设备时,你的应用程序无需确保设备之间已经进行了配对。你的RFCOMM连接将会在用户确认配对之后继续进行,或者用户拒绝或者超时之后失败。
如果要连接两个设备,其中一个必须充当服务器,通过持有一个打开的BluetoothServerSocket对象。服务器socket的作用是侦听进来的连接,如果一个连接被接受,提供一个连接好的BluetoothSocket对象。从BluetoothServerSocket获取到BluetoothSocket对象之后,BluetoothServerSocket就可以(也应该)丢弃了,除非你还要用它来接收更多的连接。
下面是建立服务器socket和接收一个连接的基本步骤:
该字符串为服务的识别名称,系统将自动写入到一个新的服务发现协议(SDP)数据库接入口到设备上的(名字是任意的,可以简单地是应用程序的名称)项。 UUID也包括在SDP接入口中,将是客户端设备连接协议的基础。也就是说,当客户端试图连接本设备,它将携带一个UUID用来唯一标识它要连接的服务,UUID必须匹配,连接才会被接受。
2)通过调用accept()来侦听连接请求。
这是一个阻塞的调用,知道有连接进来或者产生异常才会返回。只有远程设备发送一个连接请求,并且携带的UUID与侦听它socket注册的UUID匹配,连接请求才会被接受。如果成功,accept())将返回一个连接好的BluetoothSocket对象。
3)除非需要再接收另外的连接,否则的话调用close()) 。
close()释放server socket和它的资源,但不会关闭连接accept()返回的连接好的BluetoothSocket对象。与TCP/IP不同,RFCOMM同一时刻一个信道只允许一个客户端连接,因此大多数情况下意味着在BluetoothServerSocket接受一个连接请求后应该立即调用close()。
accept()调用不应该在主Activity UI线程中进行,因为这是个阻塞的调用,会妨碍其他的交互。经常是在在一个新线程中做BluetoothServerSocket或BluetoothSocket的所有工作来避免UI线程阻塞。注意所有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后,socket已经连接了,所以在客户端不应该呼叫connnect()。
manageConnectedSocket()是一个虚方法,用来初始化线程好传输数据。
通常应该在处理完侦听到的连接后立即关闭BluetoothServerSocket。在本例中,close()在得到BluetoothSocket后马上被调用。还需要在线程中提供一个公共的方法来关闭私有的BluetoothSocket,停止服务端socket的侦听。
为了实现与远程设备的连接,你必须首先获得一个代表远程设备BluetoothDevice对象。然后使用BluetoothDevice对象来获取一个BluetoothSocket来实现来接。
下面是基本的步骤:
1)用BluetoothDevice调用createRfcommSocketToServiceRecord(UUID)获取一个BluetoothSocket对象。
这个初始化的BluetoothSocket会连接到BluetoothDevice。UUID必须匹配服务器设备在打开BluetoothServerSocket 时用到的UUID(用java.util.UUID) listenUsingRfcommWithServiceRecord(String, UUID))。可以简单的生成一个UUID串然后在服务器和客户端都使用该UUID。
2)调用connect()完成连接
当调用这个方法的时候,系统会在远程设备上完成一个SDP查找来匹配UUID。如果查找成功并且远程设备接受连接,就共享RFCOMM信道,connect())会返回。这也是一个阻塞的调用,不管连接失败还是超时(12秒)都会抛出异常。
注意:要确保在调用connect())时没有同时做设备查找,如果在查找设备,该连接尝试会显著的变慢,慢得类似失败了。
下面是一个完成Bluetooth连接的样例线程:
- 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())来关闭连接的socket和清理所有的内部资源。
如果已经连接了两个设备,他们都已经拥有各自的连接好的BluetoothSocket对象。那就是一个有趣的开始,因为你可以在设备间共享数据了。使用BluetoothSocket,传输任何数据通常来说都很容易了:
1)通过socket获取输入输出流来处理传输(分别使用getInputStream()和getOutputStream() )。
2)用read(byte[])和write(byte[])来实现读写。
仅此而已。
当然,还是有很多细节需要考虑的。首要的,需要用一个专门的线程来实现流的读写。只是很重要的,因为read(byte[])和write(byte[])都是阻塞的调用。read(byte[])会阻塞直到流中有数据可读。write(byte[])通常不会阻塞,但是如果远程设备调用read(byte[])不够快导致中间缓冲区满,它也可能阻塞。所以线程中的主循环应该用于读取InputStream。线程中也应该有单独的方法用来完成写OutputStream。
下面是一个如上面描述那样的例子:
- private class ConnectedThread extends Thread {
- private final BluetoothSocket mmSocket;
- private final InputStream mmInStream;
- private final OutputStream mmOutStream;
- public ConnectedThread(BluetoothSocket socket) {
- mmSocket = socket;
- InputStream tmpIn = null;
- OutputStream tmpOut = null;
- // 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,然后继续等待读取流中的数据。
向外发送数据只需简单的调用线程的write()方法。
线程的cancel()方法时很重要的,以便连接可以在任何时候通过关闭BluetoothSocket来终止。它应该总在处理完Bluetooth连接后被调用。
从Android 3.0开始,Bluetooth API就包含了对Bluetooth profiles的支持。 Bluetooth profile是基于蓝牙的设备之间通信的无线接口规范。Android Bluetooth API完成了下面的Bluetooth profile:
下面是使用profile的基本步骤:
例如,下面的代码片段显示如何连接到一个BluetoothHeadset协议对象,用来控制Headset profile:
- 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)

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。