引入
蓝牙4.0为低功耗蓝牙,从安卓4.4版本开始支持。安卓中蓝牙的基本使用在官方的指南中有详细的说明,地址如下:https://developer.android.google.cn/guide/topics/connectivity/bluetooth-le.html
- 结合项目中的开发需求,经常需要在一个app中连接多种设备,如果设备的传输协议一致的话,那么设备与app之间传输指令可以公用一套代码。可是现实情况复杂,经常项目开发到一半就需要兼容新的设备。如果在项目的一开始,就可以做一个高扩展的兼容多设备的sdk,那么不管是以后其他项目使用,还是再添加新设备兼容,都将是一个不错的选择。
android提供的api简介
- 获取蓝牙的管理adapter(在获取之前,记得检查权限)
private BluetoothAdapter mBluetoothAdapter;
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
- 检查蓝牙是否可用,返回true,表明支持4.0低功耗蓝牙。
mBluetoothAdapter.isEnabled()
- 扫描蓝牙设备
- BluetoothAdapter.LeScanCallback为扫描的回调函数。扫描到了设备,会在onLeScan中回调(不过要留意的是,同一个设备可能会被多次返回,在显示时,记得通过mac码进行筛选)。
- 开启和停止扫描的方法也很方便。
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
//device 为扫描到的设备
}
};
mBluetoothAdapter.startLeScan(mLeScanCallback);
mBluetoothAdapter.stopLeScan(mLeScanCallback);
-
下来就是连接设备了, 其中 import android.bluetooth.BluetoothGattCallback;是一个抽象类。与蓝牙设备的连接回调都在这个类中。分别是:
- onConnectionStateChange 连接状态改变。当判断这个方法的入参数符合以下两个条件时,则表示已经连接成功(蓝牙设备给手机传输数据时是通过设备的通道传输数据的,但是连接上之后,设备的通道此时并没有打开,此时可进行通道打开操作)。
status == GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED
- onServicesDiscovered 发现服务
- onCharacteristicRead 读取字符
- onCharacteristicWrite 写入指令成功
- onCharacteristicChanged 蓝牙回传数据处理
- onDescriptorRead 读的描述返回
- onDescriptorWrite 写的描述返回
- onReliableWriteCompleted
- onReadRemoteRssi 手机距离设备的远近
BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(address);
BluetoothGatt bluetoothGatt = remoteDevice.connectGatt(appContext, isAutoConnect, mBluetoothGattCallback);
- 发送数据给蓝牙设备(其中服务通道,数据通道,指令,是根据蓝牙设备协议确定的)
String serveGallery = "xxx-xxx-xxx-xxx";//服务通道
String gallery = "xxx-xxx-xxx-xxx";//数据通道
BluetoothGattCharacteristic bluetoothGattCharacteristic = bleGattCharMap.get(serveGallery).get(gallery);
bluetoothGattCharacteristic.setValue(new byte[]{ (byte) 0XBE, 0X02,0X03, (byte) 0XED}); //byte 为指令
bluetoothGatt.writeCharacteristic(bluetoothGattCharacteristic);
- 发送数据成功后,返回的数据会在上面实例的BluetoothGattCallback类的回调方法中回应,再根据业务需求做相应的处理。
以上是android api中提供的蓝牙的基本使用流程。
现在已经大致知道了怎么使用android api 进行蓝牙连接,可是我们也知道一个蓝牙设备会有好多服务通道(大通道)和下面的数据通道(小通道),每个设备会有很多指令集,往往实际应用中,集成一个蓝牙硬件,光是浏览协议中提供了哪些指令和功能,就已经够花费时间了,更别说集成了。最后终于根据协议,一个一个解析接入完了。这个时候要是老板和产品过来说又要加设备,那之前的代码是不是能够满足再加一个的需求。另外,下一次要再另外一个项目中集成这个设备,是不是又要重新解析接入一遍所有的协议指令。这些问题,光想想,都脑壳疼了。最好有个蓝牙的sdk,直接导入进来可以开发使用,不用在意输入输出指令的解析,那就太好了。于是,就有了下来要讨论的问题。
具体实现
- 因为要撸这样的一个框架,代码非常多,而且每个蓝牙设备的协议,都不尽相同,所以我尽量按照讲一下整个思路。
- 设计一个BleManager的管理类,全权处理app中有关蓝牙操作的所有事宜。
- BleManager与app中的activity或者service 利用java提供的Obervable观察者模式,将所有的数据返回到服务里面或者UI层(我个人的思路是在blemanager和UI层之间在有一个service做衔接,这样就可以在app运行期间,不管在哪个界面,都可以去与蓝牙交互,蓝牙设备有信息,可以直接通知到UI层。)。
连接过程
BleManager设计为单例。提供一个init(Context appContext)方法,用于初始化BluetoothAdapter对象,设置为manager的成员变量。
初始化之后是扫描,创建一个扫描的回调,这个回调返回一个list集合或只返回一个设备。
BleManager中有四个主要方法,分别为开始扫描,停止扫描,连接设备,设置指令。
然后是连接设备,当用户点击了一个设备条目,我们会拿到这个设备的名字,mac等信息。我们的框架在设计时,需要一个Driver的接口,表示一个所有设备的父类。
创建一个driver包,在包下面有一个driverimpl的实现包。
每一种设备,都有一种设备名字,根据实际情况,可能是手环,心率带等等。那么对应的就需要手环driver和心率带driver。下图中用DriverXXX表示。
-
创建一个Driver工厂专门生成具体的driver。在连接之前,生成一个具体的driver,保存成manager的成员变量。
//连接设备。 public void onConnectDervice(Context appContext, boolean isAutoConnect, MBleDeviceInfo mBleDevice, BleReciveListener listener) { //mBleDevice是对系统driver的提取,增加了type类型。在扫描时进行的包装 String type = mBleDevice.getType(); driver = DriverFactory.createDriver(type); BleDevice remoteDevice = bleAdapter.getRemoteDevice(mBleDevice.getAddress()); mBluetoothGattCallback = new MBluetoothGattCallback(driver, listener); bluetoothGatt = remoteDevice.connectGatt(appContext, isAutoConnect, mBluetoothGattCallback); }
- 需要解释一下MBluetoothGattCallback这个类,他是继承系统api中的BluetoothGattCallback,而BleReciveListener 为在调用层实例化,用于返回蓝牙连接的结果。将driver传入,以方便与将蓝牙的回调分发到具体的设备drvier中进行分析处理。下面贴一个转到driverxxx的代码。这样就可根据具体的设备做具体的数据解析了。
@Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { driver.getBleConnactListener() .onConnectionStateChange(gatt, status, newState, listener); }
```
BleConnactListener mBleConnactListener = new BleConnactListener() {
//返回的接口方法实现。
//连接状态
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState, BleManager.BleReciveListener listener) {
Logger.i(TAG, "BleConnactListener : 连接状态" + Thread.currentThread());
if (status == GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED) {
listener.onConnResult(true);
} else {
//连接失败。
listener.onConnResult(false);
}
}
}
```
* 注:如果要打开通道,可以在连接成功的回调之前去打开,打开完成,再刷新UI,显示连接成功。因为,有些时候,通道不打开,是没有办法与蓝牙交互数据的。
发送指令###
-
发送指令,调用Manager的setData()方法。其中参数分别表示指令。
//向设备传输数据。 public void setData(Perform perfrom) { BleCommand command = driver.createCommand(perfrom); driver.sendData(bluetoothGatt, command); }
- 交给drvier去构造一个command命令,里面封装有这条指令的服务通道,数据通道,byte数组等信息。
-
createCommand过程
- 这个方法是传到具体的Driverxxx中实现的,方法中,对于具体指令的创建,交给了一个工厂类。
//@param performCommand 所需要创建指令的类型 //@param perfrom 包装参数的父类接口。此处应该传入具体的实现类。 //@return @Override public BleCommand createCommond(Perform perfrom) { BleCommand cmd = null; switch (performCommand) {//这里要分指令进行。所以要用switch。 case MConstant.PerformCommand.ONE_DAY://设置步距 if (perfrom instanceof PerfromSetStepDistance) { cmd = new CommandXXXOneDay(perfrom); } break; } return cmd; }
CommandXXXOneDay 这是一个指令类。里面封装了具体的这条指令信息:指令的服务通道,数据通道,byte数据等信息。到这里为止,指令创建完成。
发送指令,还是通过driver来。具体的实现和上一篇中相似。
@Override public void sendData(BleGatt bluetoothGatt, BleCommand command) { String serveGallery = command.getServeGallery(); String gallery = command.getGallery(); BluetoothGattCharacteristic bluetoothGattCharacteristic =bleGattCharMap.get(serveGallery).get(gallery); bluetoothGattCharacteristic.setValue(command.getBytes()); bluetoothGatt.writeCharacteristic(bluetoothGattCharacteristic); }
-
接收成功的返回
- 蓝牙数据的回传,在onCharacteristicChanged回调方法中。在下面方法的switch-case中根据蓝牙协议解析具体数据。解析完成,用listener回传到调用层。
@Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, final BleManager.BleReciveListener listener) { Logger.i(TAG, "BleConnactListener : 蓝牙回传数据接收处理"); //TODO 解析数据。 byte[] data = characteristic.getValue(); if (data != null && data.length > 0) { StringBuilder stringBuilder = new StringBuilder(data.length); for (byte byteChar : data) { stringBuilder.append(String.format("%02X ", byteChar)); } String lastData = stringBuilder.toString(); String uuid = characteristic.getUuid().toString(); switch (uuid) { case MConstant.Uuid.W311N.charUuid0: listener.receiveData(...); break; } } }
- 返回的数据,依旧是解析好,封装成具体的bean对象传出去。
总结###
以上,就是大致的思路。
- 这个框架,可以让调用层不关心具体的指令构建。需要做什么事情,直接传Perform具体实现类(但是这个实现类需是sdk提供的)。
- bleManager还可以对外提供支持那些设备的方法。