您的位置:首页 > 移动开发 > Android开发

Android 蓝牙对等通信初探

2016-02-28 22:57 621 查看
一、前言

很早以前,大概是12年的时候Android刚刚流行,那时候我也刚刚接触Android,对蓝牙模块通信产生了兴趣,在网上一顿狂搜,想摸黑搞一个蓝牙通信的小APP出来,结果卡在蓝牙配对问题上,毫无进展,后来就对蓝牙模块产生了一种莫名的恐惧,一直到工作三年了,也没敢碰这一块,这几天项目组比较清闲,我决定克服掉这个心病,看看Android应用到底怎么实现蓝牙通信的。于是花了两三天分析了下蓝牙连接的实现方式,就此写一篇文章,既是给将来使用做个基本总结,也是对自己过去心病的一个了结,至少在看到自己写的蓝牙通信跑通的那一霎那,我是非常激动的。

整体来讲,Android封装的蓝牙模块还是比较清晰,可用性是比较高的。Android 14(4.0)对蓝牙有一次大的调整,增加了不少接口,不过一般情况下这些接口都可以不用,自API 5增加的接口就已经够用了。但是从另一个角度来看,蓝牙模块抛出的各种异常实在是多于牛毛,我这几天基本上都是在各种异常与异常解决中度过的,大概也是由于蓝牙通信本身过于复杂导致的一个缺陷,在没有基础知识积累的情况下,基本上每一步都能产生异常。所以建议想看此文章来尝试写蓝牙通信的同学们从头到尾看一遍,漏一点代码就有可能有各种问题出来。

二、基本知识

蓝牙采用类似Socket的通信机制,只不过表现在类上是BluetoothSocket,一个BluetoothSocket关联了一个InputStream和一个OutputStream,设备可以往这个输出流写数据,或从这个输入流内读取数据。请注意这里:作为服务端的蓝牙设备能获得一个BluetoothServerSocket,并通过accept阻塞方法监听到一个客户端连接,从而形成一个BluetoothSocket,然后服务端从这个socket中读数据,处理完数据后可以把结果从这个BluetoothSocket写回给客户端。

2.1 蓝牙的工作步骤

要使用蓝牙传递数据,需要完成以下步骤:开启蓝牙-->使设备可见->搜索可用设备-->配对->建立连接,连接建立后即可开始通信。各步骤需要实现的代码如下:

1)首先要声明权限

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>


2)开启蓝牙:

mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(!mBluetoothAdapter.isEnabled()) {
//在蓝牙设备状态改变的广播中继续未完成的搜索设备功能
mBluetoothAdapter.enable();
}
这里取不到BluetoothAdapter的就是设备不支持蓝牙的机型。enable不阻塞立刻返回,但是设备蓝牙打开需要一定的时间,需要注册BluetoothAdapter.ACTION_STATE_CHANGED来监听设备打开的事件,通过
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
获取当前设备的蓝牙状态,BluetoothAdapter.STATE_ON表示蓝牙已经打开,另外三个状态是:STATE_TURNING_ON正在打开 STATE_TURNING_OFF正在关闭,STATE_OFF已关闭。

3)使设备可见,这一步应在收到蓝牙打开的广播后进行:

Intent in=new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
in.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, visibleSecond);
context.startActivity(in);
一般情况下,界面都会弹出一个对话框提示要使蓝牙设备可见,询问是否继续。visibleSecond就是可见的秒数,最大是300s。

4)搜索可用设备,在蓝牙打开后,就可以进行搜索可用远程蓝牙设备了:

//当蓝牙设备没有启动成功时返回false
boolean result = mBluetoothAdapter.startDiscovery();
这个动作是不阻塞的,但是执行了这个方法后,蓝牙就会进入高负荷工作状态,会影响蓝牙连接的稳定性,所以创建蓝牙连接前一定要用cancelDiscovery()取消掉设备发现过程。探索过程开始时会收到BluetoothAdapter.ACTION_DISCOVERY_STARTED广播,结束会收到BluetoothAdapter.ACTION_DISCOVERY_FINISHED广播,这个过程一般会持续12s左右,在这个过程中发现设备则发送BluetoothDevice.ACTION_FOUND广播。

5)配对,在搜索到目标设备后,如果当前设备没有与目标设备配对,则需要执行配对,配对的方法是高版本才公布的,所以需要使用反射来兼容低版本。

if (device.getBondState() == BluetoothDevice.BOND_NONE) {
// 调用配对绑定接口,到广播中返回绑定结果
Method creMethod = BluetoothDevice.class.getMethod("createBond");
creMethod.invoke(device);
}
配对也不是阻塞方法,但配对需要两方用户确认配对才能进行,所以也需要监听广播,配对结果的广播是BluetoothDevice.ACTION_BOND_STATE_CHANGED,处理方式为:

BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
int boundState = device.getBondState();
boundSate为BluetoothDevice.BOND_BONDED则配对完成,中间还会回调BluetoothDevice.BOND_BONDING状态。

6)建立连接:配对完成后,我们就可以建立连接了。蓝牙通信必须有一方在监听接入(即服务端),另一方才能接入到该设备(即客户端)。

BluetoothServerSocket bss = mBluetoothAdapter.listenUsingRfcommWithServiceRecord( BLUETOOTH_SOCKET_NAME, UUID_SERVER);
BluetoothSocket socket= bss.accept();

服务端执行完上述方法后,就已经处于可以通信的状态了,这里accept()是阻塞方法,只有客户端有连接接入使得通信信道建立时才能执行完成,所以这里是需要线程的。这两处用到的UUID下面会发散一下,而第一个参数是连接的名字,可以随便取。

客户端通过以下方法来建立到服务端的连接:

BluetoothSocket bs = device.createRfcommSocketToServiceRecord(UUID_SERVER);
bs.connect();
这里有几点需要注意:
1、connect是阻塞的,我们需要另起线程;

2、这两段方法一定要在服务端监听了服务后,客户端连接才不会出错,否则会有各种异常,第四部分问题总结会有例证;

3、这两个方法分属两个设备运行,当connect方法完成后,与服务端建立连接成功,就可以相i互读写数据了;

4、connect执行前一定要cancelDiscovery,否则连接很可能失败。

7)双方通信

作为服务端,需要接收数据时,从socket中getInputStream获取数据,需要注意的是,这里千万不能把stream给关掉,否则各种异常都会冒出来,另外下文中死循环可以不断的读取流中的数据,而read是阻塞的,所以可以不断的监听管道中的数据。

InputStream is = null;
try {
if(null == mServerCommuicateBluetoothSocket) {
mServerCommuicateBluetoothSocket = mBluetoothServerSocket.accept();
Logging.d(TAG, "readDataFromServerConnection()| connect success");
}
is = mServerCommuicateBluetoothSocket.getInputStream();
byte[] tempData = new byte[256];
while(true) {
//is的 read是阻塞的,来了数据才往下走
int bytesRead = is.read(tempData);

if(bytesRead == -1) {
continue;
}
//通知外层取到了数据
}
} catch (Exception e) {
Logging.d(TAG, "readDataFromServerConnection()| read data failed", e);
} finally {
//不能加is.close(),否则下次读失败
}

需要返回数据结果时就BluetoothSocket.getOutputStream()完成写操作,比较简单就不贴了。作为客户端,读写也是基本一样的,所以这里也不贴了,这些下面的源码里有。

8)释放连接

在通信完成或或者界面退出时需要释放连接,BluetoothSocket.close()。另外可以关闭蓝牙设备省电,BluetoothAdapter.disable()。

2.2 蓝牙与UUID

UUID标识的是一个蓝牙服务,Android端对端通信需要使用自己的UUID标识自己提供的服务。我找到了这样一段话:

在蓝牙中,每个服务和服务属性都唯一地由"全球唯一标识符" (UUID)来校验。正如它的名字所暗示的,每一个这样的标识符都要在时空上保证唯一。UUID类可表现为短整形(16或32位)和长整形(128 位)UUID。他提供了分别利用String和16位或32位数值来创建类的构造函数,提供了一个可以比较两个UUID(如果两个都是128位)的方法,还有一个可以转换一个UUID为一个字符串的方法。UUID实例是不可改变的(immutable),只有被UUID标示的服务可以被发现。

网上有各种UUID的介绍,见http://blog.csdn.net/spmno/article/details/6931941。这些UUID都是蓝牙已预定提供的服务,其中用的广泛的是00001101-0000-1000-8000-00805F9B34FB,如果我们需要与某种非Android设备连接,可按其中的标识使用。如果是Android设备间的通信建立,我们可以生成一个自己的UUID使用,当然通用的这个UUID也是可以用的,生成比较简单,有在线生成工具就可以了。

2.3 谈谈为什么不用createRfcommSocket建立BluetoothSocket连接

这一段摘录自网上:

createRfcommSocketToServiceRecord 基于SDP并使用UUID决定使用哪个radio channel建立链接。同时其检查远端是不是有一个Server在使用该UUID监听连接请求。因此,这是最可靠的建立连接的办法,其总是使用正确的channel,而且只要建立连接成功,就能确认对方一定在运行。

相反,createRfcommSocket 只在你告知它的channel上建立连接,这时候我们只知道有一个设备在远端,却不知道远端是否有正在倾听的服务。同时我们选取的 radio channel 可能不正确。所以这个方法一直未公开,而推荐使用其他方法。createRfcommSocket一开始会看起来更可靠,但那仅仅只是因为其不检查远端的监听服务其忽略了某些异常场景,这可能在实验中没关系,但是在正式产品中却不可靠,因为用户很可能会忘记打开远端的服务,导致APP呈现奇怪的错误提示。

2.3 蓝牙的对等通信实现

两个Android应用使用蓝牙通信时,如果我们需要两个应用都可以接收对方发来的数据又都可以发送数据到对方,也就是说,没有指定某个设备作为服务端,另一个设备作为客户端(两个设备都既是服务端又是客户端),我们就需要建立两路通信信道,让两个设备都作为服务端监听唯一生成的UUID的端口,同时两者又实现客户端逻辑connect到对方,这样设备A可以监听设备B的客户端请求,设备B也监听设备A的客户端请求,从而实现对等通信。下面的代码就是一个对等通信的例子。

三、代码

代码中关键部分都有注释,我就不一一啰嗦了,虽然不想看别人的代码是通病,但我为了将来自己能很快实现功能,还是决定贴代码,大家可以不用细看,里面很多是细节,注释最好看一下。

首先蓝牙管理类有个抽象接口IBluetoothDevice.java:

package org.common.bluetooth;

import java.util.List;

import android.bluetooth.BluetoothDevice;

/**
* 蓝牙设备管理能力
* @author qq
*
*/
public interface IBluetoothManager {

public void setBluetoothEventHandler(IBluetoothEventHandler eventHandler);
/**
* 开启蓝牙设备
* @return resultCode 结果错误码
*/
public int enableBluetooth();

public int searchAvailableDevice();

public void connectBoundedDevice();
public List<BluetoothDevice> getFoundedDevices();

public List<BluetoothDevice> getBoundedDevices();

public void createConnection(BluetoothDevice device);

public void writeDataToServerConnection(byte[] data);

public void writeDataToClientConnection(String deviceAddress, byte[] data);

public void endConnection();

public void disableBluetooth();

public interface IBluetoothEventHandler {
int RESULT_SUCCESS = 0;
int RESULT_FAIL = 1;

public boolean isDeviceMatch(BluetoothDevice device);

public void onServerSocketConnectResult(int resultCode);

<span style="white-space:pre">		</span>public void onDeviceFound(BluetoothDevice device);

public void onClientSocketConnectResult(BluetoothDevice device, int resultCode);

public void onReadServerSocketData(BluetoothDevice remoteDevice, byte[] data, int length);

public void onReadClientSocketData(BluetoothDevice remoteDevice, byte[] data, int length);

public void onBluetoothOn();

public void onBluetoothOff();
}
}
然后其他辅助类 BluetoothErrorCode.java 和BluetoothUtil.java:

package org.common.bluetooth;

public class BluetoothErrorCode {

public static final int RESULT_SUCCESS = 0;

public static final int RESULT_ERROR = 1;

public static final int RESULT_ERROR_NOT_SUPPORT = 101;
}
package org.common.bluetooth;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;

import org.common.log.Logging;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.content.Intent;

public class BluetoothUtil {

private static final String TAG = "BluetoothUtil";

private static ConcurrentHashMap<String, Boolean> mConnectMap =
new ConcurrentHashMap<String, Boolean>();
@SuppressLint("NewApi")
public static boolean isConnected(BluetoothSocket socket) {
if(android.os.Build.VERSION.SDK_INT >= 14) {
return socket.isConnected();
} else {
try {
Field fld = BluetoothSocket.class.getDeclaredField("mClosed");
fld.setAccessible(true);
return fld.getBoolean(socket);
} catch (Exception e) {
Logging.d(TAG, "error happened", e);
}
}
return false;
}

public static synchronized void notifyBeginConnection(String deviceAddress) {
if(null == deviceAddress) {
return;
}
mConnectMap.put(deviceAddress, true);
}

public static synchronized void clearConnectionFlags() {
mConnectMap.clear();
}

public static synchronized boolean isConnectionBegined(String deviceAddress) {
if(null == deviceAddress) {
return false;
}
Boolean resultFlag = mConnectMap.get(deviceAddress);
if(null == resultFlag) {
return false;
}
return resultFlag;
}

public static boolean boundDeviceIfNeed(BluetoothDevice device) {
// 未绑定
try {
// 调用配对绑定接口,到广播中返回绑定结果
if (device.getBondState() == BluetoothDevice.BOND_NONE) {
Method creMethod = BluetoothDevice.class
.getMethod("createBond");
creMethod.invoke(device);
Logging.d(TAG, "createConnection()| createBound succ");
return true;
}
} catch (Exception ex) {
Logging.d(TAG, "createConnection()| createBound failed", ex);
}
return false;
}

public static void makeBluetoothVisible(Context context, int visibleSecond) {
if(null == context) {
return;
}
if(visibleSecond < 0) {
visibleSecond = 300;
}
Intent in=new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); in.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, visibleSecond); context.startActivity(in);
}

}
接着就是真正的蓝牙对等通信管理类,这类还有点进程同步问题,我就不改了:

package org.common.bluetooth;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

import org.common.log.Logging;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;

/**
* 蓝牙管理类
* @author qq
*
*/
public class BluetoothManager implements IBluetoothManager {

private static final String TAG = "BluetoothManager";

// /**通用的UUID*/
// private static final UUID UUID_COMMON = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

/**服务端的UUID*/
private static final UUID UUID_SERVER = UUID.fromString("d2ea0fdc-1982-40e1-98e8-9dcd45130b8e");

/**客户端的UUID*/
private static final UUID UUID_CLIENT = UUID_SERVER;

//*********************单例相关代码****************************//
private static BluetoothManager mInstance;
private Context mContext;

private BluetoothManager(Context context) {
mContext = context;
}

public static BluetoothManager getInstance(Context context) {
if(null == mInstance) {
synchronized (BluetoothManager.class) {
if(null == mInstance) {
mInstance = new BluetoothManager(context);
}
}
}
return mInstance;
}

//*********************************************************//
/**蓝牙模块被外面可见时间*/
private static final int BLUETOOTH_VISIBLE_TIME = 200;

private static final String BLUETOOTH_SOCKET_NAME = "BLUETOOTH_CONNECTION";

//系统蓝牙设备帮助类
private BluetoothAdapter mBluetoothAdapter;
//监听蓝牙设备的广播
private BluetoothReceiver mBluetoothReceiver;

//已发现的设备列表
private List<BluetoothDevice> mFoundDeviceList = new ArrayList<BluetoothDevice>();

//已绑定的设备列表
private List<BluetoothDevice> mBoundedDeviceList = new ArrayList<BluetoothDevice>();

/**判断设备是否匹配目标的接口*/
private IBluetoothEventHandler mBluetoothEventHandler;

//服务器端Socket,由此获取服务端BluetoothSocket
private BluetoothServerSocket mBluetoothServerSocket;

//作为服务端的socket
private BluetoothSocket mServerCommuicateBluetoothSocket;

//作为客户端的socket, 可能有多个客户端
private HashMap<String, BluetoothSocket> mClientCommunicateBluetoothSocketMap = new HashMap<String, BluetoothSocket>();

//当前正在进行绑定操作的设备列表
private CopyOnWriteArrayList<String> mBoundingDeviceFlagList = new CopyOnWriteArrayList<String>();

@Override
public void setBluetoothEventHandler(IBluetoothEventHandler eventHandler) {
Logging.d(TAG, "setBluetoothEventHandler()");
mBluetoothEventHandler = eventHandler;
}

@Override
public int enableBluetooth() {
Logging.d(TAG, "enableBluetooth()");

mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(null == mBluetoothAdapter) {
//不支持蓝牙设备
return BluetoothErrorCode.RESULT_ERROR_NOT_SUPPORT;
}

//各种广播状态监听
IntentFilter filter = new IntentFilter();

//蓝牙状态改变广播
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
//蓝牙绑定状态改变广播
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
//搜索蓝牙设备开始的广播
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
//搜索蓝牙设备结束的广播
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
//搜索到某个蓝牙设备的广播
filter.addAction(BluetoothDevice.ACTION_FOUND);

mBluetoothReceiver = new BluetoothReceiver();
mContext.registerReceiver(mBluetoothReceiver, filter);

//如果蓝牙没有启动,则启动之
if(!mBluetoothAdapter.isEnabled()) {
//在蓝牙设备状态改变的广播中继续未完成的搜索设备功能
mBluetoothAdapter.enable();
} else {
//搜索可以看到的设备
searchAvailableDevice();
//连接已绑定集合中合适的设备
//modified 连接设备改为由用户去触发
// connectBoundedDevice();
}

//使蓝牙设备可见,方便配对
BluetoothUtil.makeBluetoothVisible(mContext, BLUETOOTH_VISIBLE_TIME);

return BluetoothErrorCode.RESULT_SUCCESS;
}

@Override
public int searchAvailableDevice() {
//当蓝牙设备没有启动成功时返回false boolean result = mBluetoothAdapter.startDiscovery();

Logging.d(TAG, "searchAvailableDevice()| triggered? " + result);

//建立当前设备对外的监听
createServerSocket();

return result ? BluetoothErrorCode.RESULT_SUCCESS : BluetoothErrorCode.RESULT_ERROR;
}

@Override
public void connectBoundedDevice() {
List<BluetoothDevice> boundedList = getBoundedDevices();
if(null == boundedList || boundedList.size() <= 0) {
return;
}
if(null == mBluetoothEventHandler) {
return;
}
for(BluetoothDevice device : boundedList) {
if(null == device) {
continue;
}
if(mBluetoothEventHandler.isDeviceMatch(device)) {
createConnection(device);
}
}
}

private void createServerSocket() {
int resultCode = IBluetoothEventHandler.RESULT_FAIL;
try {
mBluetoothServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(
BLUETOOTH_SOCKET_NAME, UUID_SERVER);
Logging.d(TAG, "createServerSocket()| wait for a client request");

resultCode = IBluetoothEventHandler.RESULT_SUCCESS;
} catch (Exception ex) {
Logging.d(TAG, "createServerSocket() | error happened", ex);
}

if(null != mBluetoothEventHandler) {
mBluetoothEventHandler.onServerSocketConnectResult(resultCode);
}

//创建Server连接后进入监听数据进入的模式,有数据则对外回调数据
readDataFromServerConnection();
}

@Override
public List<BluetoothDevice> getFoundedDevices() {
Logging.d(TAG, "getFoundedDevices()");
return mFoundDeviceList;
}

public List<BluetoothDevice> getBoundedDevices() {
Logging.d(TAG, "getBoundedDevices()");
Set<BluetoothDevice> boundedDeviceSet = mBluetoothAdapter.getBondedDevices();
if(null != boundedDeviceSet) {
mBoundedDeviceList.clear();
mBoundedDeviceList.addAll(boundedDeviceSet);
}
return mBoundedDeviceList;
}

@Override
public void createConnection(BluetoothDevice device) {
Logging.d(TAG, "createConnection() device= " + device);
if(null == device) {
return;
}

if (null != mBluetoothAdapter) {
mBluetoothAdapter.cancelDiscovery();
}

String address = device.getAddress();
if(null == address) {
return;
}

//如果已经开始了创建连接的动作,则不再进行后续操作,否则会出现socket连接被覆盖,socket关闭等各种异常
if(BluetoothUtil.isConnectionBegined(address)) {
return;
}
BluetoothUtil.notifyBeginConnection(address);

switch (device.getBondState()) {
case BluetoothDevice.BOND_BONDED:
// 已经绑定了,则创建指向目标的客户端socket
createClientSocket(device);
break;
case BluetoothDevice.BOND_BONDING:
// 正在绑定,加了待处理列表
if(!isInBoundingList(address)) {
mBoundingDeviceFlagList.add(address);
}
break;
default:
boolean hasBound = BluetoothUtil.boundDeviceIfNeed(device);
if(hasBound) {
// 触发了绑定,所以加入绑定集合
mBoundingDeviceFlagList.add(address);
} else {
//do nothing
}
break;
}
}

private void createClientSocket(BluetoothDevice device) {
int resultCode = IBluetoothEventHandler.RESULT_FAIL;

BluetoothSocket clientBluetoothSocket = null;
try {
clientBluetoothSocket = device.createRfcommSocketToServiceRecord(UUID_CLIENT);
mClientCommunicateBluetoothSocketMap.put(device.getAddress(), clientBluetoothSocket);
Logging.d(TAG, "createClientSocket()| create a client socket success");

resultCode = IBluetoothEventHandler.RESULT_SUCCESS;
} catch (Exception ex) {
Logging.d(TAG, "createClientSocket()| error happened", ex);
}

if (null != mBluetoothEventHandler) {
mBluetoothEventHandler.onClientSocketConnectResult(device, resultCode);
}

//对外连接建立后需要开始监听从client链接写入的数据,并返回给外层
readDataFromClientConnection(device.getAddress());
}

//标识是否触发过对服务Socket的读监听,已经开启读监听则不再开启
private boolean mIsServerSocketReadTrigged = false;
private void readDataFromServerConnection() {
if(mIsServerSocketReadTrigged) {
Logging.d(TAG, "readDataFromServerConnection() has triggered, do nothing");
return;
}
mIsServerSocketReadTrigged = true;

Logging.d(TAG, "readDataFromServerConnection()");
Thread thread = new Thread(new Runnable() {

@Override
public void run() {
InputStream is = null;
try {
if(null == mServerCommuicateBluetoothSocket) {
mServerCommuicateBluetoothSocket = mBluetoothServerSocket.accept();
Logging.d(TAG, "readDataFromServerConnection()| connect success");
}

is = mServerCommuicateBluetoothSocket.getInputStream();
byte[] tempData = new byte[256];
while(true) {
//is的 read是阻塞的,来了数据才往下走
int bytesRead = is.read(tempData);

if(bytesRead == -1) {
continue;
}

if(null != mBluetoothEventHandler) {
mBluetoothEventHandler.onReadServerSocketData(
mServerCommuicateBluetoothSocket.getRemoteDevice(), tempData, bytesRead);
}
}
} catch (Exception e) {
Logging.d(TAG, "readDataFromServerConnection()| read data failed", e);
} finally {
//不能加is.close(),否则下次读失败
}
}
});
thread.start();
}

@Override
public void writeDataToServerConnection(final byte[] data) {
Logging.d(TAG, "writeDataToServerConnection()");
Thread thread = new Thread(new Runnable() {

@Override
public void run() {
OutputStream os = null;
try {
if(null == mServerCommuicateBluetoothSocket) {
mServerCommuicateBluetoothSocket = mBluetoothServerSocket.accept();
Logging.d(TAG, "writeDataToServerConnection()| connect success");
}

//TODO 增加多线程控制
os = mServerCommuicateBluetoothSocket.getOutputStream();
os.write(data);
os.flush();
Logging.d(TAG, "writeDataToServerConnection()| write success");
} catch (Exception e) {
Logging.d(TAG, "writeDataToServerConnection()| write data failed", e);
} finally {
//不能加os.close(),否则对方读失败
}
}
});
thread.start();
}

//此方法仅能执行一次,执行多次会在connect处报崩溃
private void readDataFromClientConnection(final String deviceAddress) {
Logging.d(TAG, "readDataFromClientConnection() deviceAddress= " + deviceAddress);

if(null == mClientCommunicateBluetoothSocketMap) {
Logging.d(TAG, "readDataFromClientConnection()| map is null");
return;
}
final BluetoothSocket clientSocket = mClientCommunicateBluetoothSocketMap.get(deviceAddress);
if(null == clientSocket) {
Logging.d(TAG, "readDataFromClientConnection()| socket is null");
return;
}

Thread thread = new Thread(new Runnable() {

@Override
public void run() {
InputStream is = null;
try {
// 如果调用两次connect,会抛出异常
clientSocket.connect();
Logging.d(TAG, "readDataFromClientConnection()| connect success");

is = clientSocket.getInputStream();
byte[] tempData = new byte[256];
while (true) {
// is的 read是阻塞的,来了数据就往下走
int bytesRead = is.read(tempData);

if (bytesRead == -1) {
continue;
}

if (null != mBluetoothEventHandler) {
mBluetoothEventHandler.onReadClientSocketData(
clientSocket.getRemoteDevice(), tempData, bytesRead);
}
}
} catch (Exception e) {
Logging.d(TAG, "readDataFromClientConnection()|read data failed", e);
} finally {
//不能调用is.close(),否则下次再读就失败了
}
}
});
thread.start();
}

@Override
public void writeDataToClientConnection(final String deviceAddress, final byte[] data) {
Logging.d(TAG, "writeDataToClientConnection() deviceAddress= " + deviceAddress);
Thread thread = new Thread(new Runnable() {

@Override
public void run() {
OutputStream os = null;
try {
if(null == mClientCommunicateBluetoothSocketMap) {
return;
}
BluetoothSocket clientSocket = mClientCommunicateBluetoothSocketMap.get(deviceAddress);
if(null == clientSocket) {
return;
}

if (!BluetoothUtil.isConnectionBegined(deviceAddress)) {
//标识已经开始了connect动作
//TODO 这里有个缺陷,如果这时候还没有触发建立连接操作,则以后都建立不了了
BluetoothUtil.notifyBeginConnection(deviceAddress);
//如果调用两次connect,会抛出异常
clientSocket.connect();
Logging.d(TAG, "readDataFromClientConnection()| connect success");
}

//可能第一个线程卡在Connect方法内,第二个线程运行到此处了
while(!BluetoothUtil.isConnected(clientSocket)) {
Thread.sleep(300);
}

//TODO 增加同步控制,否则两个线程会都运行到此处使用里面的outputStream
os = clientSocket.getOutputStream();
os.write(data);
os.flush();
Logging.d(TAG, "writeDataToClientConnection ()| success");
} catch (Exception e) {
Logging.d(TAG, "writeDataToClientConnection ()| write data failed", e);
} finally {
// if(null != os) {
//这里要注意, 不能close, 否则对方收不到消息
//报错:java.io.IOException: bt socket closed, read return: -1
// try {
// //os.close();
// os = null;
// } catch (Exception e) {
// Logging.d(TAG, "writeDataToClientConnection ()| close outputstream failed", e);
// }
// }
}
}
});
thread.start();
}

@Override
public void endConnection() {
Logging.d(TAG, "endConnection()");

if(null != mBluetoothAdapter) {
mBluetoothAdapter.cancelDiscovery();
}
try {
if(null != mServerCommuicateBluetoothSocket) {
mServerCommuicateBluetoothSocket.close();
mServerCommuicateBluetoothSocket = null;
}
Iterator<Entry<String, BluetoothSocket>> iterator = mClientCommunicateBluetoothSocketMap.entrySet().iterator();
while(iterator.hasNext()) {
Entry<String, BluetoothSocket> entry = iterator.next();
String deviceAddress = entry.getKey();
Logging.d(TAG, "endConnection()| free socket for :" + deviceAddress);
BluetoothSocket clientSocket = entry.getValue();
if(null != clientSocket) {
clientSocket.close();
clientSocket = null;
}
}
mClientCommunicateBluetoothSocketMap.clear();
} catch (Exception ex) {
Logging.d(TAG, "endConnection() failed", ex);
}
}

@Override
public void disableBluetooth() {
Logging.d(TAG, "disableBluetooth()");

if(null != mBluetoothAdapter) {
mBluetoothAdapter.disable();
}
}

private boolean isInBoundingList(String deviceAddress) {
if(null == deviceAddress) {
return false;
}
Logging.d(TAG, "deviceAddress = " + deviceAddress + " list=" + mBoundingDeviceFlagList);
for(String address : mBoundingDeviceFlagList) {
Logging.d(TAG, "address = " + address);
if(deviceAddress.equals(address)) {
return true;
}
}
return false;
}

private class BluetoothReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
if(null == intent) {
return;
}
String action = intent.getAction();
if(BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
Logging.d(TAG, "onReceive()| state change state= " + state);
if(BluetoothAdapter.STATE_ON == state) {
if(null != mBluetoothEventHandler) {
mBluetoothEventHandler.onBluetoothOn();
}
} else if(BluetoothAdapter.STATE_OFF == state) {
if(null != mBluetoothEventHandler) {
mBluetoothEventHandler.onBluetoothOff();
}
}
} else if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if(null == device) {
Logging.d(TAG, "onReceive()| device is null");
return;
}
Logging.d(TAG, "onReceive()| device= " + device.getName() + "(" + device.getAddress() + ")");

mFoundDeviceList.add(device);

if(null != mBluetoothEventHandler) {
mBluetoothEventHandler.onDeviceFound(device);
}

//自动连接因为对方没有应用没有起来而经常失败,这里不能用自动连接的方式,改为用户触发的方式比较好
// if(null != mBluetoothEventHandler && mBluetoothEventHandler.isDeviceMatch(device)) {
// createConnection(device);
// }
} else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
Logging.d(TAG, "onReceive()| discovery started");
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){
Logging.d(TAG, "onReceive()| discovery finished");
} else if(BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
// 绑定状态改变的广播
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Logging.d(TAG, "onReceive()| boundState change " + device + " list : " + mBoundingDeviceFlagList);
if( null == device || null == mBoundingDeviceFlagList) {
//异常不需要处理
Logging.d(TAG, "onReceive()| boundState change param is null");
return;
}
String address = device.getAddress();
if(!isInBoundingList(address)) {
//不是我们触发的绑定结果我们不需要处理
Logging.d(TAG, "onReceive()| boundState change no need handle");
return;
}

int boundState = device.getBondState();
Logging.d(TAG, "onReceive()| boundState= " + boundState);
switch (boundState) {
case BluetoothDevice.BOND_BONDED:
mBoundingDeviceFlagList.remove(address);
// 已经绑定
createClientSocket(device);
break;
default:
break;
}
}
}
}

public void destroy() {
Logging.d(TAG, "destroy()");
mInstance = null;

endConnection();

disableBluetooth();

if(null != mContext) {
mContext.unregisterReceiver(mBluetoothReceiver);
mBluetoothReceiver = null;
mContext = null;
}

mBluetoothAdapter = null;
mFoundDeviceList = null;
mBoundedDeviceList = null;
mBluetoothEventHandler = null;
mServerCommuicateBluetoothSocket = null;
mClientCommunicateBluetoothSocketMap = null;
mBluetoothServerSocket = null;
}
}
接下来是测试类:

package com.example.testbluetooth;

import java.util.List;

import org.common.bluetooth.BluetoothManager;
import org.common.bluetooth.IBluetoothManager.IBluetoothEventHandler;
import org.common.log.Logging;

import android.app.Activity;
import android.bluetooth.BluetoothDevice;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;

public class MainActivity extends Activity {
private static final String TAG = "MainActivity";

private BluetoothManager mBluetoothManager;

private String msg = "我是第?次通话 ";

private int i = 0;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

Log.d(TAG, "onCreate()");
setContentView(R.layout.activity_main);

findViewById(R.id.textview_connect).setOnClickListener(new OnClickListener() {

@Override
public void onClick(View view) {
List<BluetoothDevice> boundedList = mBluetoothManager.getBoundedDevices();
if(null != boundedList) {
for(BluetoothDevice device : boundedList) {
if(mBluetoothEventHandler.isDeviceMatch(device)) {
mBluetoothManager.createConnection(device);
}
}
}

Logging.d(TAG, "create connection for bounded devices");

List<BluetoothDevice> foundedList = mBluetoothManager.getFoundedDevices();
if(null != foundedList) {
for(BluetoothDevice device : foundedList) {
if(mBluetoothEventHandler.isDeviceMatch(device)) {
mBluetoothManager.createConnection(device);
}
}
}

Logging.d(TAG, "create connection for found devices");
}
});
findViewById(R.id.textview_send).setOnClickListener(new OnClickListener() {

@Override
public void onClick(View view) {
BluetoothDevice targetDevice = null;

List<BluetoothDevice> boundedList = mBluetoothManager.getBoundedDevices();
if(null != boundedList) {
for(BluetoothDevice device : boundedList) {
if(mBluetoothEventHandler.isDeviceMatch(device)) {
targetDevice = device;
break;
}
}
}

List<BluetoothDevice> foundedList = mBluetoothManager.getFoundedDevices();
if(null != foundedList) {
for(BluetoothDevice device : foundedList) {
if(mBluetoothEventHandler.isDeviceMatch(device)) {
targetDevice = device;
break;
}
}
}

if(null == targetDevice) {
Toast.makeText(getApplicationContext(), "未发现目标对象", Toast.LENGTH_SHORT).show();
return;
}

mBluetoothManager.writeDataToClientConnection(targetDevice.getAddress(),
("来自客户端 " + msg + (i++)).getBytes());
}
});

mBluetoothManager = BluetoothManager.getInstance(this);
mBluetoothManager.setBluetoothEventHandler(mBluetoothEventHandler);

mBluetoothManager.enableBluetooth();
}

private IBluetoothEventHandler mBluetoothEventHandler = new IBluetoothEventHandler() {

@Override
public void onDeviceFound(BluetoothDevice device) {
Logging.d(TAG, "onDeviceFound() device= " + device);
if (null == device) {
return;
}
}

@Override
public void onServerSocketConnectResult(int resultCode) {
}

@Override
public void onClientSocketConnectResult(final BluetoothDevice device, int resultCode) {
if (null == device || RESULT_SUCCESS != resultCode) {
return;
}
}

@Override
public boolean isDeviceMatch(BluetoothDevice device) {
if (null == device) {
return false;
}
if ("小米手机".equals(device.getName())) {
return true;
}
if ("AOSP".equals(device.getName())) {
return true;
}
return false;
}

@Override
public void onReadServerSocketData(BluetoothDevice remoteDevice, byte[] data, int length) {
Logging.d(TAG, "onReadServerSocketData()");
handleReadData(data, length);
//			mBluetoothManager.writeDataToClientConnection(remoteDevice.getAddress(), ("来自服务端 " + msg + (i++)).getBytes());
}

@Override
public void onReadClientSocketData(BluetoothDevice remoteDevice, byte[] data, int length) {
Logging.d(TAG, "onReadServerSocketData()");
handleReadData(data, length);
}

@Override
public void onBluetoothOn() {
mBluetoothManager.searchAvailableDevice();
//modified 连接设备改为由用户去触发
//			mBluetoothManager.connectBoundedDevice();
}

@Override
public void onBluetoothOff() {
// TODO Auto-generated method stub

}
};

private void handleReadData(byte[] data, int length) {
try {
byte[] dataCroped = new byte[length];
System.arraycopy(data, 0, dataCroped, 0, length);

final String str = new String(dataCroped, "utf8");
runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(MainActivity.this, str, Toast.LENGTH_LONG).show();
}
});
Logging.d(TAG, "IReadDataListener.onResult()| get data str= " + str);
} catch (Exception e) {
Logging.d(TAG, "error happened", e);
}
}

protected void onDestroy() {
super.onDestroy();
if (null != mBluetoothManager) {
mBluetoothManager.endConnection();
mBluetoothManager.disableBluetooth();
mBluetoothManager.destroy();
mBluetoothManager = null;
Logging.d(TAG, "destroy bluetooth manager");
}
}
}
其实贴代码的时候才是写Blog最爽的时候,哈哈!蹭蹭的就看着文章巨长无比。 

四、遇到的问题总结

1)IOException: read failed, socket might closed or timeout, read ret: -1

此错误源于要连接的服务端没有运行服务,简化点的说法就是Server的应用没有运行,或者创建的BluetoothServerSocket没有调用accept()。另外如果客户端尝试两次调用connect方法,也会遇到此问题。

2)IOException: bt socket closed, read return: -1

此错误源于我们通常写流完成了就close导致的,亲测,close后对方就读不到数据了。

3)对同一个目标设备多次调用connect ,可能会导致前面的连接无法通信

4)android 的isConnected方法一直返回false

因为connect方法是阻塞式的,仅在connect成功后才会将标志位 置位到isConnected,所以多线程的时候一定不能靠这个方法判断是否执行过connect方法,因为很可能某个connect调用正在阻塞等待连接中。

5)IOException: Broken pipe

第一个原因是因为我们多次调用了createRfcommSocketToServiceRecord,导致信道被破坏;

第二个原因是一方持有另一方已经关闭的连接并向此连接发数据,假设设备A和设备B建立通道 通信,当A退出时关闭连接,进入时又建立连接,此时B没退出,所以B持有的与A通信的连接都失效了,此时写数据就报此错误,本文的代码就有此问题,以后有机会再优化,应该可以监听ACTION_CONNECTION_STATE_CHANGED处理连接来解决此问题。

五、结束语

花了三天时间,各种调试,我终于还是摸清楚了蓝牙通信的机制,从某种意义上说,算是一雪前耻吧,至少,现在提起蓝牙,我不会内心一抖,不知所措了,以后做起蓝牙模块,好歹心里有个底了,希望看到这篇文章的人也能有个底,自己动手,总是能搞定的。

六、其他蓝牙连接相关文章
http://blog.csdn.net/gf771115/article/details/38236335 http://www.cnblogs.com/cxcco/archive/2012/01/15/2322783.html http://blog.csdn.net/jason0539/article/details/17782035 http://stackoverflow.com/questions/18657427/ioexception-read-failed-socket-might-closed-bluetooth-on-android-4-3?rq=1 http://www.cnblogs.com/wenjiang/p/3200138.html http://my.oschina.net/muchenshou/blog/201205?fromerr=PDlHVk5h http://blog.sina.com.cn/s/blog_631e3f2601012ixi.html http://stackoverflow.com/questions/25698585/bluetooth-connection-failed-java-io-ioexception-read-failed-socket-might-clos http://stackoverflow.com/questions/20028624/getting-java-io-ioexception-read-failed-socket-might-closed-or-timeout-on-blue
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息