在经过一段时间的项目运行后,我发现在前面一篇文章即 "安卓 蓝牙 android BLE 开发 (上)"一文中描述的一些事物并非十分清楚,所以在本文当中,欲分享一下这段时间的一些使用心得。
在上一篇文章中,我们已经描述了何为蓝牙BLE和BLE的一些基本操作,如扫描,链接,描述符操作读写等等,那么在这篇文章当中,我想整理一下如何更好的使用BLE和一些BLE操作的设计思路。
我们使用BLE的目的,就是用于在手机和BLE设备之间进行数据通信,那么显然,我们关心的是数据,如何操作数据成为了我们的设计目的。Github上已经有了不少BLE的框架,我也把排名靠前的几个BLE框架运行并使用了一番,就解决问题的角度来说,基本可以说没有什么太大问题,都可以使用并正确得到数据。但我们知道,每一个项目都有其特殊的功能需求,我们也需要针对各个项目对BLE的读写行进修改,我们依然可以选择依赖公共库来解决项目问题,但难保有一天,你使用的BLE公共库不再更新或Android版本进行了更新导致一系列问题的发生,可能会导致你很难排查问题,万一是公共库当中出了问题,那么修改起来就相对麻烦,所以最后在我的项目上,我并没有选择依赖公共库,而是根据一些数据的基本需求去设计一个对项目更加合适的BLE封装。
言归正传,我们想使用BLE对设备进行数据通信,举个简单的例子,我们想在Activity当中通过一个方法去让BLE执行一个命令:
class MainActivity {
.......
public void doSomething(Callback callback){
}
}
这应该是我们前端最想看到的解决方案,只要调用一下doSomething,BLE就会把执行结果通过callback告诉我们是成功还是失败,我想应该没有比这个更加简单的方法了。那么现在我们知道我们至少需要一个Callback的接口来提供BLE回调。
interface Callback {
void onSuccess(Object o);
void onFailed(Object o);
}
那么该由谁去管理BLE的所有操作?显然我们一个能够控制BLE的管理者,那么我们创建一个:
class BluetoothController {
}
在上文当中,我们介绍了如何从后台获得binder并利用binder操作BLE,这次,我们设计成一个蓝牙的操作者,在Activity或Fragment中通过Service的binder获取BluetoothController,我们就可以直接通过BluetoothController 对BLE下达一系列的命令,例如:
public class BackgroundService extends Service {
private IBluetoothControl bluetoothController;
private Binder binder;
@Override
public void onCreate() {
super.onCreate();
binder = new Binder();
initBluetooth();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
private void initBluetooth() {
BluetoothAdapter bluetoothAdapter = getBluetoothAdapter();
if (bluetoothAdapter != null) {
bluetoothAdapter.enable();
bluetoothController = new ImplBluetoothController(bluetoothAdapter);
}else {
bluetoothController = new ImplBluetoothController();
}
}
private BluetoothAdapter getBluetoothAdapter(){
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
return bluetoothManager != null ? bluetoothManager.getAdapter() : null;
}
class Binder extends android.os.Binder {
IBluetoothControl getBluetoothController() {
return bluetoothController;
}
}
}
........
public interface IBluetoothControl {
//operations control.
void setAdapter(BluetoothAdapter adapter);
void setDeviceMacAddress(String address);
void doSomething(Callback callback);
}
........
public class ImplBluetoothController implements IBluetoothControl {
private BluetoothAdapter bluetoothAdapter;
private BluetoothDevice bluetoothDevice;
public ImplBluetoothController() {
}
public ImplBluetoothController(BluetoothAdapter bluetoothAdapter) {
this.bluetoothAdapter = bluetoothAdapter;
}
@Override
public void setAdapter(BluetoothAdapter adapter) {
bluetoothAdapter = adapter;
}
@Override
public void setDeviceMacAddress(String address) {
bluetoothDevice = bluetoothAdapter.getRemoteDevice(address);
}
@Override
public void doSomething(Callback callback) {
}
}
通过以上代码,我们应该就可以在Activity或Fragment当中获得BluetoothController的实例并调用doSomething(Callback callback)方法了,当然并非一定要从后台获得,你也可以把BluetoothController做成单例,或其他你认为更加合适你的项目的形式存在。
我在这里省略了BluetoothController如何连接,如何扫描(如项目需要)等等,因为前面一篇文章当中已经介绍过。因为项目各有需求,上面的代码并没有扫描过程,没有扫描获得Device等操作,所以我们就需要传入一个BluetoothDevice或BluetoothDevice的MacAddress给BluetoothController,让BluetoothController控制Device的链接,这些动作,这里全部省略了,我们注重的地方是doSomething(Callback callback)这个方法的设计。
我们要通过Device去链接BLE设备,就需要提供几个参数,一个context,一个是boolean(是否自动连接),最后一个是BluetoothGattCallback,在我们Activity中调用的doSomething方法并没有传入context对象,我们这里有几种方法去获得context,一种就是在BluetoothController创建的时候在BackgroundService中传入,一种就是在当前调用doSomething的Activity当中传入,说到这个Context,很多刚刚接触安卓的人或许会感到有些迷惘,常常选择的是使用getApplication获得APP的Context,因为Context一旦使用不当,就容易造成内存泄漏的问题。最终我还是选择了在Activity中传入当前Acticity的context对象,大家可根据自己项目需求做其他修改,没有固定写法,只有更好的写法。那么现在我们的doSomething方法应该改成:
......
public void doSomething(Context context, Callback callback){
}
我们知道,Ble的全部数据都是通过BluetoothGattCallback进行回调的,所以全部数据都可以在这里获得,我们决定重写BluetoothGattCallback,建立我们自己的GattCallback,进行自己的数据处理。
class GattCallback extends BluetoothGattCallback {
}
重写BluetoothGattCallback 里面的方法,就可以进行我们的数据处理了,但,如何做到更好的数据处理?GattCallback的全部数据都是onConnectionStateChange,onServicesDiscovered, onCharacteristicChanged等等的方法当中获得,那么不如我们创建一个数据处理者,专门处理这些Gatt回调的数据。
public interface IBluetoothEventHandler {
void startEvent(BluetoothGatt gatt);
<T> void onHandlerEvent(BluetoothGatt gatt, T t);
boolean getResult();
}
这样一来,我们就可以通过组合不同的BluetoothEventHandler来实现不同的业务逻辑了,例如,我手中的一个项目是共享单车的单车锁,现在这个锁可通过蓝牙,NFC,4G等途径开锁,我们这里只讨论蓝牙开锁,所以我们需要创建一个开锁的BluetoothEventHandler去处理开锁业务逻辑,我们暂且命名为UnlockEventHandler。
public class UnlockEventHandler implements IBluetoothEventHandler {
}
我们知道,需要进行蓝牙读写,需要BluetoothGattCharacteristic或BluetoothGattDescriptor对象,所以这个BluetoothEventHandler在创建的时候就应该指定各个对应的BluetoothGattCharacteristic的作用,这个可以和你们自己公司的硬件设计讨论,你们需要用哪个特征符做哪些操作,这里就不再多说了。这里暂定我们现在有2个BluetoothGattCharacteristic,一个叫private BluetoothGattCharacteristic commandControl; 另外一个叫private BluetoothGattCharacteristic notifyControl;
public class UnlockEventHandler implements IBluetoothEventHandler {
private BluetoothGattCharacteristic commandControl;
private BluetoothGattCharacteristic notifyControl;
private boolean result = false;//这个变量是用来衡量UnlockEventHandler 是否成功执行完自己的所有命令并得到正确的结果。
private UnlockEventHandler(UnlockEventHandler.Builder builder) {
this.commandControl = builder.commandControl;
this.notifyControl = builder.notifyControl;
}
@Override
public <T> void onHandlerEvent(BluetoothGatt gatt, T t) {
if (t instanceof BluetoothGattCharacteristic) {
handleGattCharacteristic(gatt, (BluetoothGattCharacteristic) t);
}
if (t instanceof BluetoothGattDescriptor) {
handleGattDescriptor(gatt, (BluetoothGattDescriptor) t);
}
}
@Override
public void startEvent(BluetoothGatt gatt) {
//这里是实现我的逻辑,大家不必多看,你要做什么东西,自由发挥吧。
//处理你自己的逻辑。
}
private void handleGattDescriptor(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) {
//处理你自己的逻辑。
//。。。。。。。这里是一大堆命令。
}
private void handleGattCharacteristic(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
byte[] value = characteristic.getValue();
//处理你自己的逻辑。
//。。。。。。。这里是一大堆命令。
//如果这里你已经得到了你想要的结果,就可以把成员变量result设置为了true了,
//记得完成所有读写操作后,执行gatt.disconnect()来正确关闭链接,否则GattCallback无法正确回调,
//这个问题在上一篇文章当中说过。
}
@Override
public boolean getResult() {
return result;
}
static class Builder {
private BluetoothGattCharacteristic commandControl;
private BluetoothGattCharacteristic notifyControl;
Builder setCommandControl(BluetoothGattCharacteristic commandControl) {
this.commandControl = commandControl;
return this;
}
Builder setNotifyControl(BluetoothGattCharacteristic notifyControl) {
this.notifyControl = notifyControl;
return this;
}
UnlockEventHandler build() {
return new UnlockEventHandler(this);
}
}
}
好了,我们已经创建好了一个BluetoothEventHandler,我们又考虑,我们的项目不单单只有开锁一个动作,还有其他很多很多的动作,无所谓,只要实现了IBluetoothEventHandler ,并创建一个简单工厂的方法用于生成不同的EventHandler来实现不同的回调逻辑即可。例如:
class EventHandlerFactory {
static final int ACTION_UNLOCK = 0x01; //开锁
static final int ACTION_LOCK = 0x02; //关锁
.......//其他。。。
static IBluetoothEventHandler create(BluetoothGattCharacteristic commandControl, BluetoothGattCharacteristic notifyControl, int action){
switch (action) {
case ACTION_UNLOCK:
return new UnlockEventHandler.Builder()
.setCommandControl(commandControl)
.setNotifyControl(notifyControl)
.build();
case ACTION_LOCK:
return new LockEventHandler.Builder()
.setCommandControl(commandControl)
.setNotifyControl(notifyControl)
.build();
default:
return null;
}
}
}
有了各种组合,我们就可以在我们自己的GattCallback当中自由使用了。例如,当前我们需要执行Unlock的逻辑,那么在我们GattCallback当中,就用我们的工厂创造一个UnlockEventHanlder处理者。
class GattCallback extends BluetoothGattCallback {
private IBluetoothEventHandler eventHandler;
private Callback callback;
private int action;
GattCallback(Callback callback, int action) {
this.callback = callback;
this.action = action;
}
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (status == BluetoothGatt.GATT_SUCCESS) {
switch (newState) {
case BluetoothProfile.STATE_CONNECTED:
Log.i("shoppingCar", "gatt connected and try to discover services...");
gatt.discoverServices();
break;
case BluetoothProfile.STATE_DISCONNECTED:
Log.i("shoppingCar", "gatt disconnected...");
//这就是为什么在eventHandler里面一定要设置result的结果并调用gatt.disconnect方法的理由,
//这么做也遵循了ble的操作规范,一次链接,一串命令,用完之后就关闭。
if(eventHandler.getResult()){
callback.onSuccess("operation success, callback.onSuccess is called...");
}else {
callback.onFailed("operation failed, callback.onFailed is called...");
}
gatt.close();
eventHandler = null;
callback = null;
break;
default:
break;
}
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
BluetoothGattCharacteristic commandControl = null;
BluetoothGattCharacteristic notifyControl = null;
List<BluetoothGattService> services = gatt.getServices();
for (BluetoothGattService service : services) {
List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
for (BluetoothGattCharacteristic characteristic : characteristics) {
String uuid = characteristic.getUuid().toString();
if (uuid.equals(CommandSet.UUID_CHARACTERISTIC_COMMAND_CONTROL)) {
commandControl = characteristic;
} else if (uuid.equals(CommandSet.UUID_CHARACTERISTIC_NOTIFY_CONTROL)) {
notifyControl = characteristic;
}
}
}
if (commandControl != null && notifyControl != null) {
Log.i("shoppingCar", "gatt found commandControl = " + commandControl.getUuid().toString());
Log.i("shoppingCar", "gatt found notifyControl = " + notifyControl.getUuid().toString());
//当gatt真实的发现了设备服务后,我们才去创建相应的eventHandler ,action
//就是控制事件的参数,不同的action,工厂就会创建不同的eventHandler 来实现不同逻辑。
eventHandler = EventHandlerFactory.create(commandControl, notifyControl, action);
Log.i("shoppingCar", "eventHandler is created...");
eventHandler.startEvent(gatt);
Log.i("shoppingCar", "eventHandler is start to handle...");
}else {
Log.i("shoppingCar", "commandControl or notifyControl is null...");
Log.i("shoppingCar", "eventHandler can not be created...");
Log.i("shoppingCar", "gatt is going to disconnect...");
gatt.disconnect();
}
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
eventHandler.onHandlerEvent(gatt, characteristic);
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
eventHandler.onHandlerEvent(gatt, characteristic);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
eventHandler.onHandlerEvent(gatt, descriptor);
}
}
//你的其他实现。。。。。。
}
这么一来,我们在利用BluetoothDevice调用connect的时候,就会变成
public class ImplBluetoothController implements IBluetoothControl {
private BluetoothAdapter bluetoothAdapter;
private BluetoothDevice bluetoothDevice;
private BluetoothGatt gatt;
public ImplBluetoothController() {
}
public ImplBluetoothController(BluetoothAdapter bluetoothAdapter) {
this.bluetoothAdapter = bluetoothAdapter;
}
@Override
public void setAdapter(BluetoothAdapter adapter) {
bluetoothAdapter = adapter;
}
@Override
public void setDeviceMacAddress(String address) {
bluetoothDevice = bluetoothAdapter.getRemoteDevice(address);
}
@Override
public void unlock(Context context, Callback callback) {
if (isEnable() && bluetoothDevice != null) {
//我们自己创建的GattCallback对象需要一个从Activity等传入来的Callback,和一个指定
//相应action的int参数用于创建相对应的EventHandler。
gatt = bluetoothDevice.connectGatt(context, false, new GattCallback(callback, EventHandlerFactory.ACTION_UNLOCK));
} else {
callback.onFailed("bluetooth is disable or device is null.");
}
}
public void cancelEvent(){
if(this.gatt != null){
gatt.disconnect();
}
}
private boolean isEnable() {
return bluetoothAdapter.isEnabled();
}
}
我们的类还是非常简单,我们可能还需要一个Timer去控制蓝牙链接的超时操作,还可以为我们的GattCallback添加setter方法动态改变eventHandler来实现动态业务逻辑的切换,还可能需要其他的等等。今天只是简单的说了一下ble操作的一种设计思路,利用组合eventHanlder处理BLE回调数据。
现在我们就可以在Activity中舒服的调用各种doSomething(Context context,Callback callback)去做各种业务逻辑了,有新增的业务逻辑,就创建新的EventHandler,实现新的方法接口。
好了,基本思路已经说完,具体的实现还是大家自由发挥,我的思路只是其中一种解决方案,或许你有更多更好的解决方案,这里也请老司机指教,如果可以不吝赐教,我的邮箱nikou.karter@outlook.com~~~,欢迎大家指出错误。
网友评论