1. 程式人生 > >Android低功耗藍芽通訊

Android低功耗藍芽通訊

一、寫在前面的話

  • 一直想寫一篇關於藍芽與ble裝置通訊的部落格,但是一直也不知道從何下手,可能是之前思路不清晰吧,也就一直拖拖拖,拖到現在。最近又做到關於ble裝置的專案了,在此總結一下吧。(如有不到位或者不太對的地方,希望各位多多指教)

二、關於藍芽

  • 藍芽是一種短距的無線通訊技術,可實現固定裝置、移動裝置之間的資料交換。一般將藍芽3.0之前的BR/EDR藍芽稱為傳統藍芽,而將藍芽4.0規範下的BLE藍芽稱為低功耗藍芽。
  • 如圖:

    這裡寫圖片描述

  • BLE是Bluetooth low energy的意思,屬於藍芽低功耗協議,Android4.3以上及蘋果手機等現在都支援藍芽BLE,主要面向感測器應用市場,進行短時間小資料傳輸,如健康領域:手機監測血壓,體育:手機計步器等。

  • 低功耗藍芽通訊協議:

    這裡寫圖片描述

三、梳理整體邏輯(思路/步驟)

  1. 許可權問題:先判斷手機是否滿足android4.3以上版本,再判斷手機是否開啟藍芽。
  2. 搜尋藍芽:搜尋藍芽,回撥介面中檢視ble裝置相關資訊,一定時間停止掃描。
  3. 連線藍芽:首先獲取到ble裝置的mac地址,然後呼叫connect()方法進行連線。
  4. 獲取特徵:藍芽連線成功後,需要獲取藍芽的服務特徵等,然後開啟接收設定。
  5. 傳送訊息:writeCharacteristic()方法,傳送資料給ble裝置。
  6. 接收訊息:通過藍芽的回撥介面中onCharacteristicRead()方法,接收藍芽收的訊息。
  7. 釋放資源:斷開連線,關閉資源。

四、具體實現

1、許可權問題

step1、在AndroidManifest.xml中宣告許可權

<!-- 藍芽所需許可權 -->
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
  • 第一個許可權是允許程式連線到已配對的藍芽裝置。
  • 第二個許可權是允許程式發現和配對藍芽裝置。

  • 因為只有在API18(Android4.3)以上的手機才支援ble開發,所以還要宣告一個feature。

 <uses-feature
        android:name="android.hardware.bluetooth_le"
        android:required="true" />
  • required為true時,應用只能在支援BLE的Android裝置上安裝執行
  • required為false時,Android裝置均可正常安裝執行,需要在程式碼執行時判斷裝置是否支援BLE。

  • 注意:還得寫上定位許可權,要不然有的機型掃描不到ble裝置。

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

step2、獲取藍芽介面卡

  BluetoothManager  mBluetoothManager =(BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
  BluetoothAdapter  mBluetoothAdapter = mBluetoothManager.getAdapter();
  • 如果mBluetoothAdapter為空,是因為手機藍芽不支援與ble裝置通訊,換句話說就是安卓手機系統在4.3以下了。

step3、判斷手機藍芽是否被開啟

mBluetoothAdapter.isEnabled()
  • 如果返回true,這個時候就可以掃描了
  • 如果返回false,這時候需要開啟手機藍芽。 可以呼叫系統方法讓使用者開啟藍芽。
 Intent enable = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
 startActivity(enable);

2、搜尋藍芽

step1、開始掃描

//10s後停止搜尋
new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);
                }
            }, 1000 * 10);

UUID[] serviceUuids = {UUID.fromString(service_uuid)};
mBluetoothAdapter.startLeScan(serviceUuids, mLeScanCallback);
  • startLeScan中,第一個引數是隻掃描UUID是同一類的ble裝置,第二個引數是掃描到裝置後的回撥。
  • 因為藍芽掃描比較耗電,建議設定掃描時間,一定時間後停止掃描。

  • 如果不需要過濾掃描到的藍芽裝置,可用mBluetoothAdapter.startLeScan(mLeScanCallback);進行掃描。

step2、掃描的回撥

//藍芽掃描回撥介面
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback(){
        @Override
        public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
            if (device.getName() == null) {
                return;
            }
            Log.e("--->搜尋到的藍芽名字:", device.getName());
            //可以將掃描的裝置弄成列表,點選裝置連線,也可以根據每個裝置不同標識,自動連線。

        }
    };

3、連線藍芽

step1、獲取裝置的mac地址,然後連線。

  //獲取所需地址
  String mDeviceAddress = device.getAddress();
  BluetoothGatt mBluetoothGatt = device.connectGatt(context, false, mGattCallback);

step2、onConnectionStateChange()被呼叫

  • 連線狀態改變時,mGattCallback中onConnectionStateChange()方法會被呼叫,當連線成功時,需要呼叫 
    mBluetoothGatt.discoverServices();
    去獲取服務。

step3、onServicesDiscovered()被呼叫

  • 呼叫mBluetoothGatt.discoverServices();方法後,onServicesDiscovered()這個方法會被呼叫,說明發現當前裝置了。然後我們就可以在裡面去獲取BluetoothGattService和BluetoothGattCharacteristic。

  • 下面就是mGattCallback回撥方法。

    // BLE回撥操作
    private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {

        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status,int newState){
            super.onConnectionStateChange(gatt, status, newState);

            if (newState == BluetoothProfile.STATE_CONNECTED) {
                // 連線成功

                mBluetoothGatt.discoverServices();
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                // 連線斷開
               Log.d("TAG","onConnectionStateChange fail-->" + status);
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                //發現裝置,遍歷服務,初始化特徵
                initBLE(gatt);
            } else {
               Log.d("TAG","onServicesDiscovered fail-->" + status);
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status){
            super.onCharacteristicRead(gatt, characteristic, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                // 收到的資料
                byte[] receiveByte = characteristic.getValue();

            }else{
               Log.d("TAG","onCharacteristicRead fail-->" + status);
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic){
            super.onCharacteristicChanged(gatt, characteristic);
            //當特徵中value值發生改變
        }

        /**
         * 收到BLE終端寫入資料回撥
         * @param gatt
         * @param characteristic
         * @param status
         */
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
             // 傳送成功

            } else {
             // 傳送失敗
            }
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt,
                                      BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorWrite(gatt, descriptor, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {

            }
        }

        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
            super.onReadRemoteRssi(gatt, rssi, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {

            }
        }

        @Override
        public void onDescriptorRead(BluetoothGatt gatt,BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorRead(gatt, descriptor, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {

            }
        }
    };

4、獲取特徵

step1、ble裝置相關的UUID

    //寫通道uuid
    private static final UUID writeCharactUuid = UUID.fromString("0000fff6-0000-1000-8000-00805f9b34fb");
    //通知通道 uuid
    private static final UUID notifyCharactUuid =UUID.fromString( "0000fff7-0000-1000-8000-00805f9b34fb");
  • 不同的ble裝置的UUID不相同,請根據自己的裝置初始化UUID。

step2、獲取bluetoothGattCharacteristic(因為有的裝置可能存在雙服務的情況,所以這裡遍歷所有服務)

    //初始化特徵
    public void initBLE(BluetoothGatt gatt) {
        if (gatt == null) {
            return;
        }
        //遍歷所有服務
        for (BluetoothGattService BluetoothGattService : gatt.getServices()) {
            Log.e(TAG, "--->BluetoothGattService" + BluetoothGattService.getUuid().toString());

            //遍歷所有特徵
            for (BluetoothGattCharacteristic bluetoothGattCharacteristic : BluetoothGattService.getCharacteristics()) {
                Log.e("---->gattCharacteristic", bluetoothGattCharacteristic.getUuid().toString());

                String str = bluetoothGattCharacteristic.getUuid().toString();
                if (str.equals(writeCharactUuid)) {
                    //根據寫UUID找到寫特徵
                    mBluetoothGattCharacteristic = bluetoothGattCharacteristic;
                } else if (str.equals(notifyCharactUuid)) {
                    //根據通知UUID找到通知特徵
                    mBluetoothGattCharacteristicNotify = bluetoothGattCharacteristic;
                }
            }
        }
    }

step3、開啟通知

  • 設定開啟之後,才能在onCharacteristicRead()這個方法中收到資料。
    mBluetoothGatt.setCharacteristicNotification(mGattCharacteristicNotify, true);
  • 1

5、傳送訊息

    mGattCharacteristicWrite .setValue(sData);
    if (mBluetoothGatt != null) {
        mBluetoothGatt.setCharacteristicNotification(notifyCharactUuid , true);
        mBluetoothGatt.writeCharacteristic(mGattCharacteristicWrite );
    } 

6、接收訊息

  • 接收到資料後,mGattCallback 中的onCharacteristicRead()這個方法會被呼叫。
 @Override
        public void onCharacteristicRead(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status){
            super.onCharacteristicRead(gatt, characteristic, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                // 收到的資料
                byte[] receiveByte = characteristic.getValue();

            }else{
               Log.d("TAG","onCharacteristicRead fail-->" + status);
            }
        }

7、釋放資源

  • 斷開連線、關閉資源。
    public boolean disConnect() {
        if (mBluetoothGatt != null) {
            mBluetoothGatt.disconnect();
            mBluetoothGatt.close();
            mBluetoothGatt = null;
            return true;
        }
        return false;
    }

五、開發中踩過的坑

  1. 通知開啟後,才能讀到資料,否則讀不到。
  2. 傳送資料時,如果一包資料超過20位元組,需要分包傳送,一次最多傳送二十位元組。
  3. 接收資料時,一次最多也只接收20位元組的資料,需要將接收到的資料拼接起來,在資料的結尾弄一個特定的標識,去判斷資料是否接受完畢。
  4. 每次傳送資料或者資料分包傳送時, 操作間要有至少15ms的間隔。
  5. 最近公司來了個新的藍芽產品,發現獲取不到需要的特徵,後來打斷點,發現他們藍芽裝置的通知特徵根本沒有,是他們給錯協議了。。。所以建議各位開發的時候,如果一直連線失敗,也可以檢視一下寫特徵和通知特徵是否為空,是不是賣家搞錯了,協議和產品不匹配。(當然,這樣馬虎的賣家估計是少數)。
  6. 又補充來了!這個藍芽如果出現掃描不到的情況,那是因為手機沒有開啟定位許可權,清單檔案中寫上定位許可權,程式碼中在動態獲取下就OK了。

六、demo圖示

點我下載

這裡寫圖片描述 
這裡寫圖片描述 
這裡寫圖片描述

歡迎關注技術公眾號,微訊號搜尋ColorfulCode 程式碼男人

分享技術文章,投稿分享,不限技術種類,不限技術深度,讓更多人因為分享而受益。