基於fastble的藍牙開發

FastBle的Github項目地址在這,大家可以看看:[FastBle - GitHub]
https://github.com/Jasonchenlijian/FastBle

它的文檔也相對比較完整,大家可以查看官方文檔來使用它:https://github.com/Jasonchenlijian/FastBle/wiki

FastBle的使用

1.1 聲明權限

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

android.permission.BLUETOOTH : 這個權限允許程序連接到已配對的藍牙設備, 請求連接/接收連接/傳輸數據需要改權限, 主要用於對配對後進行操作;
android.permission.BLUETOOTH_ADMIN : 這個權限允許程序發現和配對藍牙設備, 該權限用來管理藍牙設備, 有了這個權限, 應用才能使用本機的藍牙設備, 主要用於對配對前的操作;
android.permission.ACCESS_COARSE_LOCATION和android.permission.ACCESS_FINE_LOCATION:Android 6.0以後,這兩個權限是必須的,藍牙掃描周圍的設備需要獲取模糊的位置信息。這兩個權限屬於同一組危險權限,在清單文件中聲明之後,還需要再運行時動態獲取。

1.2. 初始化及配置

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
 
    BleManager.getInstance().init(getApplication());
    BleManager.getInstance()
            .enableLog(true)
            .setReConnectCount(1, 5000)
            .setOperateTimeout(5000);
}

在使用之前,需要事先調用初始化init(Application app)方法。此外,可以進行一些自定義的配置,比如是否顯示框架內部日誌,重連次數和重連時間間隔,以及操作超時時間。

1.3. 掃描外圍設備

APP作爲中心設備,想要與外圍硬件設備建立藍牙通信的前提是首先得到設備對象,途徑是掃描。在調用掃描方法之前,你首先應該先處理下面的準備工作。

判斷當前Android設備是否支持BLE。
Android 4.3以後系統中加入了藍牙BLE的功能。

BleManager.getInstance().isSupportBle();
判斷當前Android設備的藍牙是否已經打開。
可以直接調用下面的判斷方法來判斷本機是否已經打開了藍牙,如果沒有,向用戶拋出提示。

BleManager.getInstance().isBlueEnable();
主動打開藍牙。
除了判斷藍牙是否打開給以用戶提示之外,我們也可以通過程序直接幫助用戶打開藍牙開關,打開方式有這幾種:
方法1:通過藍牙適配器直接打開藍牙。

BleManager.getInstance().enableBluetooth();

方法2:通過startActivityForResult引導界面引導用戶打開藍牙。

Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, 0x01);

需要注意的是,第一種方法是異步的,打開藍牙需要一段時間,調用此方法後,藍牙不會立刻就處於開啓狀態。如果使用此方法後緊接者就需要進行掃描,建議維護一個阻塞線程,內部每隔一段時間查詢藍牙是否處於開啓狀態,外部顯示等待UI引導用戶等待,直至開啓成功。使用第二種方法,會通過系統彈出框的形式引導用戶開啓,最終通過onActivityResult的形式回調通知是否開啓成功。

6.0及以上機型動態獲取位置權限。
藍牙打開之後,進行掃描之前,需要判斷下當前設備是否是6.0及以上,如果是,需要動態獲取之前在Manifest中聲明的位置權限。

配置掃描規則
掃描規則可以配置1個或多個,也可以不配置使用默認(掃描10秒)。掃描的時候,會根據配置的過濾選項,對掃描到的設備進行過濾,結果返回過濾後的設備。掃描時間配置爲小於等於0,會實現無限掃描,直至調用BleManger.getInstance().cancelScan()來中止掃描。

  BleScanRuleConfig scanRuleConfig = new BleScanRuleConfig.Builder()
          .setServiceUuids(serviceUuids)      // 只掃描指定的服務的設備,可選
          .setDeviceName(true, names)         // 只掃描指定廣播名的設備,可選
          .setDeviceMac(mac)                  // 只掃描指定mac的設備,可選
          .setAutoConnect(isAutoConnect)      // 連接時的autoConnect參數,可選,默認false
          .setScanTimeOut(10000)              // 掃描超時時間,可選,默認10秒
          .build();
  BleManager.getInstance().initScanRule(scanRuleConfig);

以上準備工作完成後,就可以開始進行掃描。

 BleManager.getInstance().scan(new BleScanCallback() {
      @Override
      public void onScanStarted(boolean success) {
      }
 
      @Override
      public void onLeScan(BleDevice bleDevice) {
      }
 
      @Override
      public void onScanning(BleDevice bleDevice) {
      }
 
      @Override
      public void onScanFinished(List<BleDevice> scanResultList) {
      }
  });

onScanStarted(boolean success): 會回到主線程,參數表示本次掃描動作是否開啓成功。由於藍牙沒有打開,上一次掃描沒有結束等原因,會造成掃描開啓失敗。
onLeScan(BleDevice bleDevice):掃描過程中所有被掃描到的結果回調。由於掃描及過濾的過程是在工作線程中的,此方法也處於工作線程中。同一個設備會在不同的時間,攜帶自身不同的狀態(比如信號強度等),出現在這個回調方法中,出現次數取決於周圍的設備量及外圍設備的廣播間隔。
onScanning(BleDevice bleDevice):掃描過程中的所有過濾後的結果回調。與onLeScan區別之處在於:它會回到主線程;同一個設備只會出現一次;出現的設備是經過掃描過濾規則過濾後的設備。
onScanFinished(List scanResultList):本次掃描時段內所有被掃描且過濾後的設備集合。它會回到主線程,相當於onScanning設備之和。

1.4. 設備信息

掃描得到的BLE外圍設備,會以BleDevice對象的形式,作爲後續操作的最小單元對象。它本身含有這些信息:
String getName():藍牙廣播名
String getMac():藍牙Mac地址
byte[] getScanRecord(): 被掃描到時候攜帶的廣播數據
int getRssi() :被掃描到時候的信號強度
後續進行設備連接、斷開、判斷設備狀態,讀寫操作等時候,都會用到這個對象。可以把它理解爲外圍藍牙設備的載體,所有對外圍藍牙設備的操作,都通過這個對象來傳導。

1.5. 連接、斷連、監控連接狀態

拿到設備對象之後,可以進行連接操作。

BleManager.getInstance().connect(bleDevice, new BleGattCallback() {
@Override
public void onStartConnect() {
}

@Override
public void onConnectFail(BleException exception) {
}

@Override
public void onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status) {
}

@Override
public void onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status) {
}

});

onStartConnect():開始進行連接。
onConnectFail(BleException exception):連接不成功。
onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status):連接成功並發現服務。
onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status):連接斷開,特指連接後再斷開的情況。在這裏可以監控設備的連接狀態,一旦連接斷開,可以根據自身情況考慮對BleDevice對象進行重連操作。需要注意的是,斷開和重連之間最好間隔一段時間,否則可能會出現長時間連接不上的情況。此外,如果通過調用disconnect(BleDevice bleDevice)方法,主動斷開藍牙連接的結果也會在這個方法中回調,此時isActiveDisConnected將會是true。

1.6. GATT協議

BLE連接都是建立在 GATT (Generic Attribute Profile) 協議之上。GATT 是一個在藍牙連接之上的發送和接收很短的數據段的通用規範,這些很短的數據段被稱爲屬性(Attribute)。它定義兩個 BLE 設備通過Service 和 Characteristic 進行通信。GATT 就是使用了 ATT(Attribute Protocol)協議,ATT 協議把 Service, Characteristic以及對應的數據保存在一個查找表中,次查找表使用 16 bit ID 作爲每一項的索引。

關於GATT這部分內容會在下面重點講解。總之,中心設備和外設需要雙向通信的話,唯一的方式就是建立 GATT 連接。當連接成功之後,外圍設備與中心設備之間就建立起了GATT連接。
上面講到的connect(BleDevice bleDevice, BleGattCallback bleGattCallback)方法其實是有返回值的,這個返回值就是BluetoothGatt。當然還有其他方式可以獲取BluetoothGatt對象,連接成功後,調用:

BluetoothGatt gatt = BleManager.getInstance().getBluetoothGatt(BleDevice bleDevice);
通過BluetoothGatt對象作爲連接橋樑,中心設備可以獲取外圍設備的很多信息,以及雙向通信。

首先,就可以獲取這個藍牙設備所擁有的Service和Characteristic。每一個屬性都可以被定義作不同的用途,通過它們來進行協議通信。下面的方法,就是通過BluetoothGatt,查找出所有的Service和Characteristic的UUID:

   List<BluetoothGattService> serviceList = bluetoothGatt.getServices();
    for (BluetoothGattService service : serviceList) {
        UUID uuid_service = service.getUuid();
 
        List<BluetoothGattCharacteristic> characteristicList= service.getCharacteristics();
        for(BluetoothGattCharacteristic characteristic : characteristicList) {
            UUID uuid_chara = characteristic.getUuid();
        }
    }

1.7. 協議通信

APP與設備建立了連接,並且知道了Service和Characteristic(需要與硬件協議溝通確認)之後,我們就可以通過BLE協議進行通信了。通信的橋樑,主要就是是通過 標準的或者自定義的Characteristic,中文我們稱之爲“特徵”。我們可以從 Characteristic 讀數據和寫數據。這樣就實現了雙向的通信。站在APP作爲中心設備的角度,常用於數據交互的通信方式主要有3種:接收通知、寫、讀,此外還有設置最大傳輸單元,獲取實時信號強度等通信操作。

接收通知

有兩種方式可以接收通知,indicate和notify。indicate和notify的區別就在於,indicate是一定會收到數據,notify有可能會丟失數據。indicate底層封裝了應答機制,如果沒有收到中央設備的迴應,會再次發送直至成功;而notify不會有central收到數據的迴應,可能無法保證數據到達的準確性,優勢是速度快。通常情況下,當外圍設備需要不斷地發送數據給APP的時候,比如血壓計在測量過程中的壓力變化,胎心儀在監護過程中的實時數據傳輸,這種頻繁的情況下,優先考慮notify形式。當只需要發送很少且很重要的一條數據給APP的時候,優先考慮indicate形式。當然,從Android開發角度的出發,如果硬件放已經考慮了成熟的協議和發送方式,我們需要做的僅僅是根據其配置的數據發送方式進行相應的對接即可。
打開notify

  BleManager.getInstance().notify(
          bleDevice,
          uuid_service,
          uuid_characteristic_notify,
          new BleNotifyCallback() {
              @Override
              public void onNotifySuccess() {
                  // 打開通知操作成功
              }
 
              @Override
              public void onNotifyFailure(BleException exception) {
                  // 打開通知操作失敗
              }
 
              @Override
              public void onCharacteristicChanged(byte[] data) {
                  // 打開通知後,設備發過來的數據將在這裏出現
              }
          });

關閉notify

 BleManager.getInstance().stopNotify(uuid_service, uuid_characteristic_notify);

打開indicate

  BleManager.getInstance().indicate(
          bleDevice,
          uuid_service,
          uuid_characteristic_indicate,
          new BleIndicateCallback() {
              @Override
              public void onIndicateSuccess() {
                  // 打開通知操作成功
              }
 
              @Override
              public void onIndicateFailure(BleException exception) {
                  // 打開通知操作失敗
              }
 
              @Override
              public void onCharacteristicChanged(byte[] data) {
                  // 打開通知後,設備發過來的數據將在這裏出現
              }
          });

關閉indicate

BleManager.getInstance().stopIndicate(uuid_service, uuid_characteristic_indicate);

這裏的通知操作用到了兩個關鍵的參數,uuid_service和uuid_characteristic_notify(或uuid_characteristic_indicate),就是上面提到的Service和Characteristic,此處以字符串的形式體現,不區分大小寫。

讀寫

  BleManager.getInstance().read(
          bleDevice,
          uuid_service,
          uuid_characteristic_read,
          new BleReadCallback() {
              @Override
              public void onReadSuccess(byte[] data) {
                  // 讀特徵值數據成功
              }
 
              @Override
              public void onReadFailure(BleException exception) {
                  // 讀特徵值數據失敗
              }
          });
 
  BleManager.getInstance().write(
          bleDevice,
          uuid_service,
          uuid_characteristic_write,
          data,
          new BleWriteCallback() {
              @Override
              public void onWriteSuccess(int current, int total, byte[] justWrite) {
                  // 發送數據到設備成功(分包發送的情況下,可以通過方法中返回的參數可以查看發送進度)
              }
 
              @Override
              public void onWriteFailure(BleException exception) {
                  // 發送數據到設備失敗
              }
          });

進行BLE數據相互發送的時候,一次最多能發送20個字節。如果需要發送的數據超過20個字節,有兩種方法,一種是主動嘗試拓寬MTU,另一種是採用分包傳輸的方式。框架中的write方法,當遇到數據超過20字節的情況時,默認是進行分包發送的。

設置最大傳輸單元MTU

  BleManager.getInstance().setMtu(bleDevice, mtu, new BleMtuChangedCallback() {
      @Override
      public void onSetMTUFailure(BleException exception) {
          // 設置MTU失敗
      }
 
      @Override
      public void onMtuChanged(int mtu) {
          // 設置MTU成功,並獲得當前設備傳輸支持的MTU值
      }
  });

獲取設備的實時信號強度Rssi

  BleManager.getInstance().readRssi(
          bleDevice,
          new BleRssiCallback() {
 
              @Override
              public void onRssiFailure(BleException exception) {
                  // 讀取設備的信號強度失敗
              }
 
              @Override
              public void onRssiSuccess(int rssi) {
                  // 讀取設備的信號強度成功
              }
          });

在BLE設備通信過程中,兩次操作之間最好間隔一小段時間,如100ms(具體時間可以根據自己實際藍牙外設自行嘗試延長或縮短)。舉例,onConnectSuccess之後,延遲100ms再進行notify,之後再延遲100ms進行write。
連接及連接後的過程中,時刻關注onDisConnected方法,然後做處理。
斷開後如果需要重連,也請延遲一段時間,否則會造成阻塞。

下面是自己整理的一個fastble的工具類,由於藍牙掃描、連接、讀、寫是在子線程中進行,所以這裏作了線程轉換,接收數據可在類中直接使用和界面刷新操作,:

import android.app.Activity;
import android.bluetooth.BluetoothGatt;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import com.clj.fastble.BleManager;
import com.clj.fastble.callback.BleGattCallback;
import com.clj.fastble.callback.BleNotifyCallback;
import com.clj.fastble.callback.BleScanCallback;
import com.clj.fastble.callback.BleWriteCallback;
import com.clj.fastble.data.BleDevice;
import com.clj.fastble.exception.BleException;
import com.clj.fastble.scan.BleScanRuleConfig;
import com.clj.fastble.utils.HexUtil;
import org.loop.fiveblemachine.utils.ParamsUtils;
import java.util.List;
import java.util.UUID;
/**
 * Created by Administrator on 2019/4/1 0001.
 */
public class BleClient {
    public static BleClient bleClient;
    private Handler mHandler = new Handler(Looper.getMainLooper());
    public static BleClient getInstence(){
        if (bleClient == null){
            bleClient = new BleClient();
        }
        return bleClient;
    }
    /**
     * 初始化
     * @param activity
     */
    public void initBle(Activity activity){
        BleManager.getInstance().init(activity.getApplication());
        BleManager.getInstance()
                .enableLog(true)
                .setReConnectCount(1, 5000)
                .setOperateTimeout(5000);
    }

    /**
     * 設置掃描規則
     * @param str_uuid
     */
    public void setRulesBle(String str_uuid){
        String[] uuids;
        if (TextUtils.isEmpty(str_uuid)) {
            uuids = null;
        } else {
            uuids = str_uuid.split(",");
        }
        UUID[] serviceUuids = null;
        if (uuids != null && uuids.length > 0) {
            serviceUuids = new UUID[uuids.length];
            for (int i = 0; i < uuids.length; i++) {
                String name = uuids[i];
                String[] components = name.split("-");
                if (components.length != 5) {
                    serviceUuids[i] = null;
                } else {
                    serviceUuids[i] = UUID.fromString(uuids[i]);
                }
            }
        }
        BleScanRuleConfig scanRuleConfig = new BleScanRuleConfig.Builder()
                .setServiceUuids(serviceUuids)      // 只掃描指定的服務的設備,可選
                .setScanTimeOut(2500)              // 掃描超時時間,可選,默認10秒
                .build();
        BleManager.getInstance().initScanRule(scanRuleConfig);
    }

    /**
     * 掃描設備
     *
     * @param scanCallback
     */
    public void ScanBle(final ScanCallback scanCallback) {
        scanBle(scanCallback);
    }

    private void scanBle(final ScanCallback scanCallback){
        BleManager.getInstance().scan(new BleScanCallback() {
            @Override
            public void onScanStarted(boolean success) {
                runOnMainThread(new Runnable() {
                    @Override
                    public void run() {
                        scanCallback.onStart();
                    }
                });
            }

            @Override
            public void onLeScan(BleDevice bleDevice) {
                super.onLeScan(bleDevice);
            }

            @Override
            public void onScanning(final BleDevice bleDevice) {
                runOnMainThread(new Runnable() {
                    @Override
                    public void run() {
                        scanCallback.onScanning(bleDevice);
                    }
                });
            }

            @Override
            public void onScanFinished(final List<BleDevice> scanResultList) {

            }
        });
    }
    /**
     * 取消掃描
     */
    public void cancleBleScan(){
        BleManager.getInstance().cancelScan();
    }

    /**
     * 是否連接
     */
    public boolean isConnected(BleDevice bleDevice){
        return BleManager.getInstance().isConnected(bleDevice);
    }
    public boolean isConnected(String bleDevice){
        return BleManager.getInstance().isConnected(bleDevice);
    }

    /**
     * 取消連接
     */
    public void disconnect(BleDevice bleDevice){
        BleManager.getInstance().disconnect(bleDevice);
    }


    /**
     * 連接回調
     * @param device
     * @param callback
     */
    public void ConnectBle(BleDevice device, ConnectCallback callback){
        connectBle(device.getMac(), callback);
    }
    public void ConnectBle(String deviceMac, ConnectCallback callback){
        connectBle(deviceMac, callback);
    }
    private void connectBle(String deviceMac, final ConnectCallback callback){

        BleManager.getInstance().connect(deviceMac, new BleGattCallback() {
            @Override
            public void onStartConnect() {
                runOnMainThread(new Runnable() {
                    @Override
                    public void run() {
                        callback.onStartConnect();
                    }
                });
            }

            @Override
            public void onConnectFail(BleDevice bleDevice, BleException exception) {
                runOnMainThread(new Runnable() {
                    @Override
                    public void run() {
                        callback.onFailedConnect();
                    }
                });
            }

            @Override
            public void onConnectSuccess(final BleDevice bleDevice, BluetoothGatt gatt, int status) {
                runOnMainThread(new Runnable() {
                    @Override
                    public void run() {
                        callback.onSuccessConnect(bleDevice);
                    }
                });
            }

            @Override
            public void onDisConnected(final boolean isActiveDisConnected, final BleDevice bleDevice, BluetoothGatt gatt, int status) {
                runOnMainThread(new Runnable() {
                    @Override
                    public void run() {
                        callback.onDisConnect(isActiveDisConnected, bleDevice);
                    }
                });
            }
        });
    }
    /**
     * 打開通知
     * @return
     */
    public void NotifyBle(BleDevice device, String Service_Uuid, String Notify_Uuid, NotifyCallback callback){
        notifyBle(device, Service_Uuid, Notify_Uuid, callback);
    }
    private void notifyBle(final BleDevice device, String Service_Uuid, String Notify_Uuid, final NotifyCallback callback){
        BleManager.getInstance().notify(
                device,
                Service_Uuid,
                Notify_Uuid,
                new BleNotifyCallback(){
                    @Override
                    public void onNotifySuccess() {
                        Log.d("BleActivity", "onNotifySuccess  " + device.getMac());
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    Thread.sleep(100);
                                    runOnMainThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            callback.onNotifySuccess();
                                        }
                                    });
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                        }).start();
                    }

                    @Override
                    public void onNotifyFailure(final BleException exception) {
                        runOnMainThread(new Runnable() {
                            @Override
                            public void run() {
                                callback.onFailureSuccess(exception);
                            }
                        });
                    }

                    @Override
                    public void onCharacteristicChanged(final byte[] data) {
                        Log.d("BleActivity", "接到通知: " + HexUtil.formatHexString(data, true));
                        runOnMainThread(new Runnable() {
                            @Override
                            public void run() {
                                callback.onCharacteristicChanged(data);
                            }
                        });
                    }
                }
        );
    }

    /**
     * 寫入數據
     * @return
     */
    public void WriteBle(BleDevice bleDevice, String service_uuid, String write_uuid,
                         byte[] cmd,  WriteCallback callback){
        writeBle(bleDevice, service_uuid, write_uuid, cmd, callback);
    }

    private void writeBle(BleDevice bleDevice, String service_uuid, String write_uuid,
                          byte[] cmd, final WriteCallback callback) {

        BleManager.getInstance().write(
                bleDevice,
                service_uuid,
                write_uuid,
                cmd,
                new BleWriteCallback() {
                    @Override
                    public void onWriteSuccess(final int current, final int total, final byte[] data) {
                        runOnMainThread(new Runnable() {
                            @Override
                            public void run() {
                                callback.onWriteSuccess(current, total, data);
                            }
                        });

                    }

                    @Override
                    public void onWriteFailure(final BleException exception) {
                        runOnMainThread(new Runnable() {
                            @Override
                            public void run() {
                                callback.onWriteFailed(exception);
                            }
                        });
                    }
                });

    }

    private boolean isMainThread() {
        return Looper.myLooper() == Looper.getMainLooper();
    }
    private void runOnMainThread(Runnable runnable) {
        if (isMainThread()) {
            runnable.run();
        } else {
            if (mHandler != null) {
                mHandler.post(runnable);
            }
        }
    }
}

文章參考:
https://blog.csdn.net/gaoxiaoweiandy/article/details/82958204

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章