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模塊還是比較穩定的。市面上的大部分機型都可以正常工作除了一些相對來說比較老的設備有一些問題。




《架構文摘》每天一篇架構領域重磅好文,涉及一線互聯網公司應用架構(高可用、高性 能、高穩定)、大數據、機器學習等各個熱門領域。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM