Android蓝牙开发小结(转)

学习之前先了解两个基本概念:

一、RFCOMM协议: 

  一个基于欧洲电信标准协会ETSI07.10规程的串行线性仿真协议。此协议提供RS232控制和状态信号,如基带上的损坏,CTS以及数据信号等,为上层业务(如传统的串行线缆应用)提供了传送能力。 

  RFCOMM是一个简单传输协议,其目的是针对如何在两个不同设备上的应用之间保证一条完整的通信路径,并在它们之间保持一通信段。

  RFCOMM是为了兼容传统的串口应用,同时取代有线的通信方式,蓝牙协议栈需要提供与有线串口一致的通信接口而开发出的协议。RFCOMM协议提供对基于L2CAP协议的串口仿真,基于ETSI07.10。可支持在两个BT设备之间同时保持高达60路的通信连接。

  RFCOMM只针对直接互连设备之间的连接,或者是设备与网络接入设备之间的互连。通信两端设备必须兼容于RFCOMM协议,有两类设备:DTE (Data Terminal Endpoint,通信终端,如PC,PRINTER)和DCE (Data Circuit Endpoint,通信段的一部分,如Modem)。此两类设备不作区分。

 

二、MAC硬件地址

  MAC(Medium/MediaAccess Control, 介质访问控制)MAC地址是烧录在NetworkInterfaceCard(网卡,NIC)里的.MAC地址,也叫硬件地址,是由48比特长(6字节),16进制的数字组成.0-23位叫做组织唯一标志符(organizationally unique,是识别LAN(局域网)节点的标识.24-47位是由厂家自己分配。其中第40位是组播地址标志位。网卡的物理地址通常是由网卡生产厂家烧入网卡的EPROM(一种闪存芯片,通常可以通过程序擦写),它存储的是传输数据时真正赖以标识发出数据的电脑和接收数据的主机的地址。

 

  Android平台提供的蓝牙API去实现蓝牙设备之间的通信,蓝牙设备之间的通信主要包括了四个步骤:设置蓝牙设备、寻找局域网内可能或者匹配的设备、连接设备和设备之间的数据传输。以下是建立蓝牙连接的所需要的一些基本类:

BluetoothAdapter类:代表了一个本地的蓝牙适配器。它是所有蓝牙交互的的入口点。利用它你可以发现其他蓝牙设备,查询绑定了的设备,使用已知的MAC地址实例化一个蓝牙设备和建立一个BluetoothServerSocket(作为服务器端)来监听来自其他设备的连接。

BluetoothDevice类:代表了一个远端的蓝牙设备,使用它请求远端蓝牙设备连接或者获取远端蓝牙设备的名称、地址、种类和绑定状态。(其信息是封装在bluetoothsocket中)。

Bluetoothsocket类:代表了一个蓝牙套接字的接口(类似于tcp中的套接字),它是应用程序通过输入、输出流与其他蓝牙设备通信的连接点。

Blueboothserversocket类:代表打开服务连接来监听可能到来的连接请求(属于server端),为了连接两个蓝牙设备必须有一个设备作为服务器打开一个服务套接字。当远端设备发起连接连接请求的时候,并且已经连接到了的时候,Blueboothserversocket类将会返回一个bluetoothsocket

Bluetoothclass类:描述了一个蓝牙设备的一般特点和能力。它的只读属性集定义了设备的主、次设备类和一些相关服务。然而,它并没有准确地描述所有该设备所支持的蓝牙文件和服务,而是作为对设备种类来说的一个小小暗示。

 

下面说说具体的编程实现

1.启动蓝牙功能:

首先通过调用静态方法getDefaultAdapter()获取蓝牙适配器BluetoothAdapter,以后你就可以使用该对象了。如果返回为空,the story is over例如:

1 BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
2 if (mBluetoothAdapter == null) {
3 // Device does not support Bluetooth
4 }

其次,调用isEnabled()来查询当前蓝牙设备的状态,如果返回为false,则表示蓝牙设备没有开启,接下来你需要封装一个ACTION_REQUEST_ENABLE请求到intent里面,调用startActivityForResult()方法使能蓝牙设备,例如:

 

1 if (!mBluetoothAdapter.isEnabled()) {
2 Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
3 startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
4 //不做提示,强行打开
5 // mAdapter.enable();
6 }

 

2. 查找设备:

  使用BluetoothAdapter类里的方法,你可以查找远端设备(大概十米以内)或者查询在你手机上已经匹配(或者说绑定)的其他手机了。当然需要确定对方蓝牙设备已经开启或者已经开启了“被发现使能”功能(对方设备是可以被发现的是你能够发起连接的前提条件)。如果该设备是可以被发现的,会反馈回来一些对方的设备信息,比如名字、MAC地址等,利用这些信息,你的设备就可以选择去向对方初始化一个连接。

  如果你是第一次与该设备连接,那么一个配对的请求就会自动的显示给用户。当设备配对好之后,他的一些基本信息(主要是名字和MAC)被保存下来并可以使用蓝牙的API来读取。使用已知的MAC地址就可以对远端的蓝牙设备发起连接请求。

  匹配好的设备和连接上的设备的不同点:匹配好只是说明对方设备发现了你的存在,并拥有一个共同的识别码,并且可以连接。连接上:表示当前设备共享一个RFCOMM信道并且两者之间可以交换数据。也就是是说蓝牙设备在建立RFCOMM信道之前,必须是已经配对好了的。

 

3. 查询匹配好的设备:

  在建立连接之前你必须先查询配对好了的蓝牙设备集(你周围的蓝牙设备可能不止一个),以便你选取哪一个设备进行通信,例如你可以你可以查询所有配对的蓝牙设备,并使用一个数组适配器将其打印显示出来:

1 Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
2 // If there are paired devices
3 if (pairedDevices.size() > 0) {
4 //Loop through paired devices
5 for (BluetoothDevice device : pairedDevices) {
6 // Add the name and address to an array adapter to show in a ListView
7 mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
8 }
9 }

建立一个蓝牙连接只需要MAC地址就已经足够了。

 

4. 扫描设备:

  扫描设备,只需要简单的调用startDiscovery()方法,这个扫描的过程大概持续是12秒,应用程序为了ACTION_FOUND动作需要注册一个BroadcastReceiver来接受设备扫描到的信息。对于每一个设备,系统都会广播ACTION_FOUND动作。例如:

 1 // Create a BroadcastReceiver for ACTION_FOUND
2 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
3 public void onReceive(Context context, Intent intent) {
4 String action = intent.getAction();
5 // When discovery finds a device
6 if (BluetoothDevice.ACTION_FOUND.equals(action)) {
7 // Get the BluetoothDevice object from the Intent
8 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
9 // Add the name and address to an array adapter to show in a ListView
10 mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
11 }
12 }
13 };
14
15 // Register the BroadcastReceiver
16 IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
17 registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy

 

注意:扫描的过程是一个很耗费资源的过程,一旦你找到你需要的设备之后,在发起连接请求之前,确保你的程序调用cancelDiscovery()方法停止扫描。显然,如果你已经连接上一个设备,启动扫描会减少你的通信带宽。

 

5. 使能被发现:Enabling discoverability

  如果你想使你的设备能够被其他设备发现,将ACTION_REQUEST_DISCOVERABLE动作封装在intent中并调用startActivityForResult(Intent, int)方法就可以了。他将在不使你应用程序退出的情况下使你的设备能够被发现。缺省情况下的使能时间是120秒,当然你可以可以通过添加EXTRA_DISCOVERABLE_DURATION字段来改变使能时间(最大不超过300秒,这是出于对你设备上的信息安全考虑)。例如:

1 Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
2 discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
3 startActivity(discoverableIntent);

  运行该段代码之后,系统会弹出一个对话框来提示你启动设备使能被发现(此过程中如果你的蓝牙功能没有开启,系统会帮你开启),并且如果你准备对该远端设备发现一个连接,你不需要开启使能设备被发现功能,因为该功能只是在你的应用程序作为服务器端的时候才需要。

 

6. 连接设备:

  在应用程序中,想建立两个蓝牙设备之间的连接,必须实现客户端和服务器端的代码(因为任何一个设备都必须可以作为服务端或者客户端)。一个开启服务来监听,一个发起连接请求(使用服务器端设备的MAC地址)。当他们都拥有一个蓝牙套接字在同一RFECOMM信道上的时候,可以认为他们之间已经连接上了。服务端和客户端通过不同的方式或其他们的蓝牙套接字。当一个连接监听到的时候,服务端获取到蓝牙套接字。当客户可打开一个FRCOMM信道给服务器端的时候,客户端获取到蓝牙套接字。

  注意:在此过程中,如果两个蓝牙设备还没有配对好的,android系统会通过一个通知或者对话框的形式来通知用户。RFCOMM连接请求会在用户选择之前阻塞。如下图:                           

 

7. 服务端的连接:

  当你想要连接两台设备时,一个必须作为服务端(通过持有一个打开的BluetoothServerSocket),目的是监听外来连接请求,当监听到以后提供一个连接上的BluetoothSocket给客户端,当客户端从BluetoothServerSocket得到BluetoothSocket以后就可以销毁BluetoothServerSocket,除非你还想监听更多的连接请求。

  建立服务套接字和监听连接的基本步骤:

  首先通过调用listenUsingRfcommWithServiceRecord(String, UUID)方法来获取BluetoothServerSocket对象,参数String代表了该服务的名称,UUID代表了和客户端连接的一个标识(128位格式的字符串ID,相当于PIN码),UUID必须双方匹配才可以建立连接。

  其次调用accept()方法来监听可能到来的连接请求,当监听到以后,返回一个连接上的蓝牙套接字BluetoothSocket

  最后,在监听到一个连接以后,需要调用close()方法来关闭监听程序。(一般蓝牙设备之间是点对点的传输)

  注意:accept()方法不应该放在主Acitvity里面,因为它是一种阻塞调用(在没有监听到连接请求之前程序就一直停在那里)。解决方法是新建一个线程来管理。例如:

 1 private class AcceptThread extends Thread {
2 private final BluetoothServerSocket mmServerSocket;
3 public AcceptThread() {
4 // Use a temporary object that is later assigned to mmServerSocket,
5         // because mmServerSocket is final
6 BluetoothServerSocket tmp = null;
7 try {
8 // MY_UUID is the app's UUID string, also used by theclient code
9 tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
10 } catch (IOException e) { }
11 mmServerSocket = tmp;
12 }
13
14 public void run() {
15 BluetoothSocket socket = null;
16 // Keep listening until exception occurs or a socket is returned
17 while (true) {
18 try {
19 socket = mmServerSocket.accept();
20 } catch (IOException e) {
21 break;
22 }
23 // If a connection was accepted
24 if (socket != null) {
25 // Do work to manage the connection (in a separate thread)
26 manageConnectedSocket(socket);
27 mmServerSocket.close();
28 break;
29 }
30 }
31 }
32
33 /** Will cancel the listening socket, and cause the thread to finish */
34 public void cancel() {
35 try {
36 mmServerSocket.close();
37 } catch (IOException e) { }
38 }
39 }

 

8. 客户端的连接:

  为了初始化一个与远端设备的连接,需要先获取代表该设备的一个BluetoothDevice对象。通过BluetoothDevice对象来获取BluetoothSocket并初始化连接,具体步骤:

  使用BluetoothDevice对象里的方法createRfcommSocketToServiceRecord(UUID)来获取BluetoothSocket。UUID就是匹配码。然后,调用connect()方法来。如果远端设备接收了该连接,他们将在通信过程中共享RFFCOMM信道,并且connect()方法返回。例如:

 1 private class ConnectThread extends Thread {
2 private final BluetoothSocket mmSocket;
3 private final BluetoothDevice mmDevice;
4 public ConnectThread(BluetoothDevice device) {
5 // Use a temporary object that is later assigned to mmSocket,
6         // because mmSocket is final
7 BluetoothSocket tmp = null;
8 mmDevice = device;
9 // Get a BluetoothSocket to connect with the given BluetoothDevice
10 try {
11 // MY_UUID is the app's UUID string, also used by the server code
12 tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
13 } catch (IOException e) { }
14 mmSocket = tmp;
15 }
16
17
18 public void run() {
19 // Cancel discovery because it will slow down the connection
20 mAdapter.cancelDiscovery();
21 try {
22 // Connect the device through the socket. This will block
23 // until it succeeds or throws an exception
24 mmSocket.connect();
25 } catch (IOException connectException) {
26 // Unable to connect; close the socket and get out
27 try {
28 mmSocket.close();
29 } catch (IOException closeException) { }
30 return;
31 }
32 // Do work to manage the connection (in a separate thread)
33 manageConnectedSocket(mmSocket);
34 }
35 }

  注意:conncet()方法也是阻塞调用,一般建立一个独立的线程中来调用该方法。在设备discover过程中不应该发起连接connect(),这样会明显减慢速度以至于连接失败。且数据传输完成只有调用close()方法来关闭连接,这样可以节省系统内部资源。

 

9. 管理连接(主要涉及数据的传输):

  当设备连接上以后,每个设备都拥有各自的BluetoothSocket。现在你就可以实现设备之间数据的共享了。

  1> 首先通过调用getInputStream()和getOutputStream()方法来获取输入输出流。然后通过调用read(byte[]) 和write(byte[]).方法来读取或者写数据。

  2> 实现细节:以为读取和写操作都是阻塞调用,需要建立一个专用现成来管理。

  3> 

 1 private class ConnectedThread extends Thread {
2 private final BluetoothSocket mmSocket;
3 private final InputStream mmInStream;
4 private final OutputStream mmOutStream;
5
6 public ConnectedThread(BluetoothSocket socket) {
7 mmSocket = socket;
8 InputStream tmpIn = null;
9 OutputStream tmpOut = null;
10 // Get the input and output streams, using temp objects because
11         // member streams are final
12 try {
13 tmpIn = socket.getInputStream();
14 tmpOut = socket.getOutputStream();
15 } catch (IOException e) { }
16 mmInStream = tmpIn;
17 mmOutStream = tmpOut;
18 }
19
20 public void run() {
21 byte[] buffer = new byte[1024]; // buffer store for the stream
22 int bytes; // bytes returned from read()
23         // Keep listening to the InputStream until an exception occurs
24 while (true) {
25 try {
26 // Read from the InputStream
27 bytes = mmInStream.read(buffer);
28 // Send the obtained bytes to the UI Activity
29 mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer).sendToTarget();
30 } catch (IOException e) {
31 break;
32 }
33 }
34 }
35
36 /* Call this from the main Activity to send data to the remote device */
37 public void write(byte[] bytes) {
38 try {
39 mmOutStream.write(bytes);
40 } catch (IOException e) { }
41 }
42
43 /* Call this from the main Activity to shutdown the connection */
44 public void cancel() {
45 try {
46 mmSocket.close();
47 } catch (IOException e) { }
48 }
49 }
posted @ 2012-02-28 21:49  天南星客  阅读(6121)  评论(1编辑  收藏  举报