美文网首页androidBLE开发安卓蓝牙开发
安卓 蓝牙 android BLE 开发 (下)

安卓 蓝牙 android BLE 开发 (下)

作者: NikouKarter | 来源:发表于2017-11-06 16:36 被阅读432次

    在经过一段时间的项目运行后,我发现在前面一篇文章即 "安卓 蓝牙 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~~~,欢迎大家指出错误。

    相关文章

      网友评论

        本文标题:安卓 蓝牙 android BLE 开发 (下)

        本文链接:https://www.haomeiwen.com/subject/nwdlmxtx.html