zoukankan      html  css  js  c++  java
  • Android蓝牙低功耗(BLE)模块设计

    在阅读这篇文章之前你应该对GATT和Android蓝牙框架有一定的了解。这里不会向你解释ServiceCharacteristics等蓝牙知识。这里只是我写下我对Android Ble的再次封装来适应APP的业务需求。

    BLE模块

    在开发时APP需要连接多个Ble设备,可能很多人会想Ble这种长时间运行的程序应该写进Android Service里面。对的写入Service是必须的,但是写入的方法也是对APP有很大的影响的。如果你把所有的Ble连接、数据交互都写入Service中一但Service被杀APP的BLE模块就失效你想再次去连接只能自己开启Service或等到Android调试Service。我的实现方法BLE模块不依赖Service仅仅只是在Service中运行即使Service被杀BLE模块还在对APP不会有任何影响。

    抽象GATT

    定义IGattClient接口来抽象出BLE的连接的管理。GATT的行为大致可分为:

    1. 连接设备
    2. 发现服务
    3. 断开连接
    4. 关闭GATT

    实际开发过程中我将连接到发现服务合并在一起了,因为如果连接成功但是发现服务没有成功时GATT也是不可用的。还有断开连接这个功能我在使用过程中也用的非常少一般都是关闭GATT释放资源。同时定义一些通用的错误信息如:蓝牙不可用、没有扫描到设备等错误信息。

    package im.xingzhe.ble.base;
    
    import android.bluetooth.BluetoothDevice;
    import android.bluetooth.BluetoothGatt;
    
    public interface IGattClient {
        int     ERROR_SHIFT = 8;    //100000000
        int     STATE_SHIFT = 0;
    
    //    int     STATE_IDLE = -1;
        int     STATE_CONNECTING = BluetoothGatt.STATE_CONNECTING ;
        int     STATE_CONNECTED =  BluetoothGatt.STATE_CONNECTED ;
        int     STATE_DISCONNECTING = BluetoothGatt.STATE_DISCONNECTING ;
        int     STATE_DISCONNECTED = BluetoothGatt.STATE_DISCONNECTED ;
        int     STATE_SERVICES_DISCOVERED =  0x8;
        int     ERROR_NONE = 0;
        int     ERROR_BLUETOOTH = 0x1 <<  ERROR_SHIFT ;
        int     ERROR_TIMEOUT =   0x2 <<  ERROR_SHIFT;
        int     ERROR_CONNECT_LOSE =   0x8 <<  ERROR_SHIFT;
        int     ERROR_NOT_FOUND_DEVICE =   0x10 <<  ERROR_SHIFT;  //4096
        int     ERROR_DEVICE_BUSY =   0x11 <<  ERROR_SHIFT;
        int     ERROR_UNKNOWN =   0x12 <<  ERROR_SHIFT;
    
    
        BluetoothDevice getBluetoothDevice();
        String   getName();
        String   getAddress();
        BluetoothGatt getBluetoothGatt();
        int     getLastError();
        void connect();
        void discoverServices();
        void disconnect();
        void close();
        int getConnectionState();
        void registerConnectionListener(ConnectionListener listener);
        void unregisterConnectionListener(ConnectionListener listener);
    
        interface ConnectionListener {
            void onStateChanged(IGattClient client, int newState);
        }
    }
    
    

    定义接口后就是如何来实现以上接口的问题。在实现过程中是利用Android Handler机制来实现的,每一个GattClient中都有一个Handler来处理连接、发现服务、断开连接和关闭Gatt。由于篇幅原因IGattClient实现代码我就不全部贴出来了只拿出部分来讲解一下。

    定义通用Handler

    通过Handler机制来同步处理Gatt基本操作这样维护起来也比较方便同样可以保持设备的状态不会乱掉。

    package im.xingzhe.ble.base;
    
    import android.os.Handler;
    import android.os.Looper;
    import android.os.Message;
    
    public class GattClientHandler extends Handler {
    
        AbsGattClient mClientRef;
        public GattClientHandler(AbsGattClient client, Looper looper) {
            super(looper);
            this.mClientRef = client;
        }
        @Override
        public void handleMessage(Message msg) {
            AbsGattClient client = mClientRef;
            try{
                if( client != null)
                    client .handleMessage(msg);
            } catch (Exception exception){
               client.e(exception);
            }
        }
    }
    

    同步状态与发现服务

    由于IGattClient要维护自己的设备,有时候这些状态是由程序主动发起的也有可能由于设备信号不足导致的。不管怎样这些状态Android BLE框架中都会有回调。Android通过BluetoothGattCallback来回调Gatt状态,其中onConnectionStateChange这个方法是用来告诉我们蓝牙设备连接已经改变。我们应该这个方法中刷新IGattClient中维护的状态。这里我定义了BaseBluetoothGattCallback来同步状态。

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    public class BaseBluetoothGattCallback<CLIENT extends AbsBleDevice> extends BluetoothGattCallback {
    
        public CLIENT mClientRef;
    
        public BaseBluetoothGattCallback(CLIENT client) {
            this.mClientRef = client;
        }
    
        @Override
        public final void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            if(mClientRef != null){
                try{
                    mClientRef.syncState(status, newState);
                }catch (Exception e){
                    mClientRef.e(e);
                }
            }
        }
    
    
        @Override
        public final void onServicesDiscovered(BluetoothGatt gatt, int status) {
             if(mClientRef != null){
                 try{
                     mClientRef.handleServicesDiscovered(status);
                 }catch (Exception e){
                     mClientRef.e(e);
                 }
             }
        }
    

    在BaseBluetoothGattCallback中会回调AbsGattClientsyncStatehandleServicesDiscovered 方法。如果你要问我既然Android中已经维护了状态为什么我们的实现中还要自己去维护。我只能说因国内的Android机型太多系统大都是深度定制。比如说:你把设备电池下了或使信号丢失,正常情况下statusBluetoothGatt.GATT_SUCCESSnewState会是BluetoothProfile.STATE_DISCONNECTED然而有些手机并不会这样给你回调。

     public void syncState(int status, int newState) {
            d(String.format("onConnectionStateChange: status->%d, newState->%d", status, newState));
            /*
                不应该依赖系统API去判断一个连接是成功还是失败,使用AbsGattClient内部维护的状态码来
                决定操作
             */
            if (status == BluetoothGatt.GATT_SUCCESS
                    && newState == mTargetState) {
                //是期望的状态
                refreshGattClientState(mTargetState, mTargetState);
            } else {
                int currentState = mCurrentState; //保存当前状态
                int targetState = mTargetState;
                mError = ERROR_UNKNOWN;
                if (currentState == STATE_CONNECTING) {
                    mError = ERROR_DEVICE_BUSY;
                } else if (newState == STATE_DISCONNECTED) {
                    closeBluetoothGatt();
                    refreshGattClientState(STATE_DISCONNECTED, STATE_DISCONNECTED);
                    if ((targetState == currentState)
                            && (/*currentState == STATE_CONNECTED || */currentState == STATE_SERVICES_DISCOVERED)) {
                         /*
                           本地记录是已连接状态但可能由于信号或设备休眠导致失去连接
                        */
                        mError = ERROR_CONNECT_LOSE;
                        printError();
                        if (isAutoConnection()) {
                            reconnect();
                        }
                    }
                }
            }
    
            wakeup();
        }
    

    连接处理

    我将设备的连接和其他行为设计成等待的机制如:设备连接时会首先调用mBluetoothDevice.connectGatt(mAppContext, false, mGattCallback);然后阻塞initLocalScheduler();中启动的线程同时等待syncState方法的的唤醒。当前也要有超时机制不然整个设备没法用了。

    protected synchronized void initLocalScheduler() {
            if (mLocalHandler != null) {
                return;
            }
    
            HandlerThread ht = new HandlerThread(getName() == null ? getAddress() : getName());
            ht.setUncaughtExceptionHandler(this);
            ht.start();
            mLocalHandler = new GattClientHandler(this, ht.getLooper());
     }
    
    protected void _connect() {
            d("try to connect to " + getName());
            mError = ERROR_NONE;
            mNotify = false;
     
            refreshGattClientState(STATE_CONNECTING, STATE_CONNECTED);
            //检测蓝牙是否可用
            if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
                refreshGattClientState(STATE_DISCONNECTED, STATE_DISCONNECTED);
                mError = ERROR_BLUETOOTH;
                return;
            }
    
            if (mBluetoothDevice == null) {
                if (mDeviceAddress != null) {
                    mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(mDeviceAddress);
                }
    
                if (mBluetoothDevice == null) {
                    mError = ERROR_NOT_FOUND_DEVICE;
                    refreshGattClientState(STATE_DISCONNECTED, STATE_DISCONNECTED);
                    return;
                }
            }
            mConnectingDevices.incrementAndGet();
            mBluetoothGatt = mBluetoothDevice.connectGatt(mAppContext, false, mGattCallback);
            if (mBluetoothGatt == null) {
                mError = ERROR_BLUETOOTH;
                refreshGattClientState(STATE_DISCONNECTED, STATE_DISCONNECTED);
            } else {
                waitForRemoteDevice(DEFAULT_CONNECT_TIMEOUT * mConnectingDevices.get());
                if (mCurrentState == STATE_CONNECTED) {
                    _discoverServices();
                    if (mCurrentState == STATE_SERVICES_DISCOVERED) {
                        //连接成功后清除下Message
                        mLocalHandler.removeMessages(OP_CONNECT);
                        mLocalHandler.removeMessages(OP_RECONNECT);
                        mConnectingDevices.decrementAndGet();
                        return;
                    }
                }
                closeBluetoothGatt();
            }
    
            mConnectingDevices.decrementAndGet();
            refreshGattClientState(STATE_DISCONNECTED, STATE_DISCONNECTED);
        }
    
      private void waitForRemoteDevice(int millis) {
            d("waitForRemoteDevice: " + (millis));
            try {
                long start = SystemClock.uptimeMillis();
                synchronized (mLock) {
    
                    //如果返回太快,将会导致唤醒失败
                    if (!mNotify)
                        mLock.wait(millis);
    
                    if (!mNotify) {
                        mError = ERROR_TIMEOUT;
                    } else {
                        //mCurrentState会在别处更新
                    }
                }
                d("waitForRemoteDevice return: " + (SystemClock.uptimeMillis() - start));
            } catch (InterruptedException e) {
                e.printStackTrace();
                refreshGattClientState(STATE_DISCONNECTED, STATE_DISCONNECTED);
            }
    
        }
    

    抽象BEL设备

    IGattClient抽象完成后接下来就是定义BLE设备。我只贴出AbsBleDevcie代码其中只是实现了一些蓝牙标准中的Service的处理。

    public abstract class AbsBleDevice extends AbsGattClient implements IBleDevice {
    
        public static UUID CLIENT_CHARACTERISTIC_CONFIG2 = UUID.fromString(BLEAttributes.CLIENT_CHARACTERISTIC_CONFIG2);
        public static UUID BLE_BATTERY_SERVICE = UUID.fromString(BLEAttributes.BLE_BATTERY_SERVICE);
        public static UUID BLE_BATTERY_CHARACTERISTIC = UUID.fromString(BLEAttributes.BLE_BATTERY_CHARACTERISTIC);
        public static UUID BLE_DEVICE_INFORMATION_SERVICE = UUID.fromString(BLEAttributes.BLE_DEVICE_INFORMATION_SERVICE);
        public static UUID BLE_DEVICE_INFORMATION_FIRMWARE_CHARACTERISTIC = UUID.fromString(BLEAttributes.BLE_DEVICE_INFORMATION_FIRMWARE_CHARACTERISTIC);
    
        private static final int    OP_READ_BATTERY = 0x1;
        private static final int    OP_READ_FIRMWARE = 0x2;
        private static final int    OP_SET_NOTIFICATION = 0x3;
     
        private Device  mLocalDevice; 
    
        public AbsBleDevice(Device localDevice){
            this.mLocalDevice = localDevice;
        }
     
        @CallSuper
        @Override
        protected void onStateChanged(int currentState, int targetState) {
            if(currentState == STATE_SERVICES_DISCOVERED) {
                onServicesDiscovered();
            }else if(currentState == STATE_DISCONNECTED){
                onDeviceDisconnected();
            }
    
        } 
        protected void onServicesDiscovered(){
    
        }
    
        protected void onDeviceDisconnected(){
    
        }
     
    
        public Device   getLocalDevice(){
            return this.mLocalDevice;
        }
    
    
        public    int   getDeviceType(){
            return mLocalDevice != null ? mLocalDevice.getType(): Device.TYPE_UNKNOW;
        }
    
        public boolean  isServicesDiscovered( ){
            return getConnectionState() == STATE_SERVICES_DISCOVERED;
        }
    
     
        public void readFirmwareVersion(){
            if(isServicesDiscovered()){
                Message message = mLocalHandler.obtainMessage(OP_READ_FIRMWARE);
                mLocalHandler.sendMessageDelayed(message, 500);
            }
    
        }
    
        private void _readFirmwareVersion(){
            BluetoothGattService deviceInfoService = mBluetoothGatt.getService(BLE_DEVICE_INFORMATION_SERVICE);
            if(deviceInfoService != null){
                BluetoothGattCharacteristic firmwarmCh = deviceInfoService.getCharacteristic(BLE_DEVICE_INFORMATION_FIRMWARE_CHARACTERISTIC);
                mBluetoothGatt.readCharacteristic(firmwarmCh);
            }
        }
    
    
        private void _readBattery(){
            BluetoothGattService batteryService =  mBluetoothGatt.getService(BLE_BATTERY_SERVICE);
            if(batteryService != null) {
                BluetoothGattCharacteristic batteryCharacteristic = batteryService.getCharacteristic(BLE_BATTERY_CHARACTERISTIC);
                mBluetoothGatt.readCharacteristic(batteryCharacteristic);
            }
        }
    
    
        public void readBattery(){
            if(isServicesDiscovered()){
                Message msg =  mLocalHandler.obtainMessage(OP_READ_BATTERY);
                mLocalHandler.sendMessageDelayed(msg, 300);
            }
        }
    
        protected void _setCharacteristicNotification2(BluetoothGattCharacteristic characteristic, boolean enabled) {
            if(characteristic == null)
                return;
    
            BluetoothGatt gatt = getBluetoothGatt();
            gatt.setCharacteristicNotification(characteristic, enabled);
            final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG2);
            if (descriptor != null) {
                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                mBluetoothGatt.writeDescriptor(descriptor);
    
            }
        }
        
        public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {
                if(isServicesDiscovered()){
                    Message message = mLocalHandler.obtainMessage(OP_SET_NOTIFICATION, enabled ? 1: 0, 0, characteristic);
                    mLocalHandler.sendMessage(message);
                }
        }
    
    
    
        @Override
        protected void handleMessage(Message message) {
            super.handleMessage(message);
    
    
            switch (message.what){
                case OP_READ_BATTERY:
                    _readBattery();
                    break;
                case OP_READ_FIRMWARE:
                    _readFirmwareVersion();
                    break;
                case OP_SET_NOTIFICATION:
                    _setCharacteristicNotification2((BluetoothGattCharacteristic) message.obj, message.arg1 != 0);
                    break;
            }
        }
     
    }
    

    写在最后

    其实整个实现过程是比较波折的,但是经过慢慢的摸索现在的我手上这个APP的BLE模块还是比较稳定的。市面上的大部分机型都可以正常工作除了一些相对来说比较老的设备有一些问题。




    《架构文摘》每天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性 能、高稳定)、大数据、机器学习等各个热门领域。

  • 相关阅读:
    精品教程Android程序的建立以及发布
    项目源码Android团购类客户端
    精品教程Android4.1开发环境搭建
    项目源码Android本地视频播放器
    精品教程Android组件详解
    精品教程NDK环境搭建(2)NDK的安装与配置
    ios 块语法的本质
    ios 属性易错点
    ios 打印结构体的方法
    ios开发 UITableCell 可重用问题
  • 原文地址:https://www.cnblogs.com/xwgblog/p/5722528.html
Copyright © 2011-2022 走看看