安卓低功耗藍牙的使用

近期一個項目須要用到低功耗藍牙的開發,因爲以前沒有藍牙開發的經驗,發現網上關於藍牙開發的資料很少,不是隨便描述一下就是已通過時的,在此整理一篇低功耗藍牙的入門資料,可以完成使用藍牙的接受和發送數據。html

低功耗藍牙 (BLE,Bluetooth Low Energy的簡稱) 從Android 4.3 開始支持,現在愈來愈多外設都是使用低功耗藍牙來傳輸數據的,與經典藍牙本質上沒有太多的區別,有不少類似之處,工做流程都是:發現設備 --> 配對/綁定設備 --> 鏈接設備 --> 數據傳輸。可是,低功耗藍牙在安卓開發中的使用和經典藍牙是徹底不一樣的,若是按照以前很熟悉的經典藍牙開發思惟來作,說不定還會踩坑。。。java

官方相關的開發指南:
經典藍牙
低功耗藍牙
低功耗藍牙使用實例項目android

基本概念

先來了解一些關於低功耗藍牙的基本概念:git

  • Generic Attribute Profile (GATT)——全稱叫作通用屬性配置文件,GATT按照層級定義了三個概念,服務(Service)、特徵(Characteristic)和描述(Descriptor)。一個 Service 包含若干個 Characteristic,一個 Characteristic 包含若干個 Descriptor。
  • Characteristic——能夠理解爲一個類,包含了一個 value 和零至多個對該 value 的描述。
  • Descriptor——對 Characteristic 的描述,例如範圍和計量單位等。
  • Service——Characteristic的集合。

這些概念不用深刻去探究,有必定了解開發的時候不至於一無所知就夠了,想要具體瞭解低功耗藍牙這裏有篇不錯的文章github

低功耗藍牙開發步驟

1.聲明權限

使用藍牙功能首先須要聲明相關的權限,好比:app

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

同時,也就能夠經過藍牙特性配置來限制支持藍牙功能的設備使用APP:ide

<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

或者經過在代碼中判斷,不過如今基本沒有什麼手機不支持藍牙功能了吧。post

// Use this check to determine whether BLE is supported on the device. Then
// you can selectively disable BLE-related features.
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    finish();
}

須要注意的是,官方說明 Android 5.0 及以上設備使用藍牙時還須要定位權限,須要注意的是開發的時候若是是在 Android 6.0 及以上設備的須要動態獲取定位權限,不然藍牙功能也是沒法使用的。ui

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> 
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<manifest ... >
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    ...
    <!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
    <uses-feature android:name="android.hardware.location.gps" />
    ...
</manifest>
2.初始化藍牙適配器
private BluetoothAdapter mBluetoothAdapter;
...
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
        (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
3.開啓藍牙

在開始掃描發現藍牙設備以前須要確保手機的藍牙功能打開。this

if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    // 申請打開藍牙
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

而後在 onActivityResultI方法中判斷用戶是否贊成開啓藍牙功能。

4.發現設備
private static final long SCAN_PERIOD = 10000;
private void scanLeDevice(final boolean enable) {
      // Stops scanning after a pre-defined scan period.
      mHandler.postDelayed(new Runnable() {
          @Override
          public void run() {
              mScanning = false;
              mBluetoothAdapter.stopLeScan(mLeScanCallback);
          }
      }, SCAN_PERIOD);
      mScanning = true;
      mBluetoothAdapter.startLeScan(mLeScanCallback);
}

/**
* 發現設備的回調
*/
private BluetoothAdapter.LeScanCallback mLeScanCallback =
        new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi,
            byte[] scanRecord) {

    }
};
5.鏈接設備
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);

鏈接設備的方法須要傳入三個參數,第一個是 context,第二個是 boolean,表示是否自動鏈接,第三個是鏈接的回調接口,其中有幾個很重要的方法。

private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            String intentAction;
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                mConnectionState = STATE_CONNECTED;
                // 開始查找服務,只有找到服務纔算是真的鏈接上
                mBluetoothGatt.discoverServices();
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                intentAction = ACTION_GATT_DISCONNECTED;
                mConnectionState = STATE_DISCONNECTED;
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {

            } else {

            }
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic characteristic,
                                         int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
              byte[] data = characteristic.getValue();
        }
    };

鏈接設備成功後須要在回調方法中發現服務mBluetoothGatt.discoverServices(),發現服務後會回調onServicesDiscovered方法,發現服務成功纔算是真正的鏈接上藍牙設備。onCharacteristicWrite方法是寫操做結果的回調,onCharacteristicChanged方法是狀態改變的回調,在該方法中可以獲取藍牙發送的數據。不過,在接收數據以前,咱們必須對Characteristic設置監聽纔可以接收到藍牙的數據。

6.Characteristic監聽設置
public void setNotification() {
 
    BluetoothGattService service = mBluetoothGatt.getService(SERVICE_UUID);
    if (service == null) {
        L.e("未找到藍牙中的對應服務");
        return;
    }
    BluetoothGattCharacteristic characteristic= service.getCharacteristic(CharacteristicUUID);
    if (characteristic== null) {
        L.e("未找到藍牙中的對應特徵");
        return;
    }
    //設置true爲啓用通知,false反之
    mBluetoothGatt.setCharacteristicNotification(characteristic, true);
 
    //下面爲開啓藍牙notify功能,向CCCD中寫入值1
    BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CCCD);
    descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    mBluetoothGatt.writeDescriptor(descriptor);
}

先經過 UUID 找到咱們須要進行數據傳輸的service,在找到咱們想要設置監聽的Characteristic的 UUID 找到響應的characteristic對象,找到響應的characteristic後調用setCharacteristicNotification方法啓用通知,則該characteristic狀態發生改變後就會回調onCharacteristicChanged方法,而開啓藍牙的 notify 功能須要向 UUID 爲 CCCD 的 descriptor 中寫入值1,其中 CCCD 的值爲:

public static final UUID CCCD = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
7.向藍牙發送數據

往藍牙發送數據,能夠理解爲給藍牙的characteristic設置數據。

public void writeRXCharacteristic(byte[] value) {
        if (mBluetoothGatt == null) {
            return;
        }
        BluetoothGattService service= mBluetoothGatt.getService(SERVICE_UUID);
        BluetoothGattCharacteristic characteristic= service.getCharacteristic(UUID);
        characteristic.setValue(value);
        mBluetoothGatt.writeCharacteristic(characteristic);
    }

咱們能夠寫入Stringbyte[]的數據,通常爲byte[]。其中咱們須要與哪一個service的characteristic進行數據傳輸能夠聯繫硬件工程師或者查看藍牙設備供應商提供的說明得到。咱們也能夠經過mBluetoothGatt.getServices()mBluetoothGatt.getgetCharacteristics()方法獲取藍牙設備的全部
service 和某個 service 中的全部 characteristic。

8.數據分包處理

低功耗藍牙一次性只能發送 20 個字節的數據,超過 20 個字節的沒法發送,所以須要對發送的數據進行分包處理,在此給出數據分包的一個示例,是在別人 github 中看到的:

//存儲待發送的數據隊列
    private Queue<byte[]> dataInfoQueue = new LinkedList<>();
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            send();
        }
    };

    /**
     * 向characteristic寫數據
     *
     * @param value
     */
    public void writeCharacteristic(BluetoothGattCharacteristic characteristic, byte[] value) {
        this.mCharacteristic = characteristic;
        if (dataInfoQueue != null) {
            dataInfoQueue.clear();
            dataInfoQueue = splitPacketFor20Byte(value);
            handler.post(runnable);
        }
        // characteristic.setValue(value);
        // mBluetoothGatt.writeCharacteristic(characteristic);
    }

    private void send() {
        if (dataInfoQueue != null && !dataInfoQueue.isEmpty()) {
            //檢測到發送數據,直接發送
            if (dataInfoQueue.peek() != null) {
                this.mCharacteristic.setValue(dataInfoQueue.poll());//移除並返回隊列頭部的元素
                mBluetoothGatt.writeCharacteristic(mCharacteristic);
            }
            //檢測還有數據,延時後繼續發送,通常延時100毫秒左右
            if (dataInfoQueue.peek() != null) {
                handler.postDelayed(runnable, 200);
            }
        }
    }

    //數據分包處理
    private Queue<byte[]> splitPacketFor20Byte(byte[] data) {
        Queue<byte[]> dataInfoQueue = new LinkedList<>();
        if (data != null) {
            int index = 0;
            do {
                byte[] surplusData = new byte[data.length - index];
                byte[] currentData;
                System.arraycopy(data, index, surplusData, 0, data.length - index);
                if (surplusData.length <= 20) {
                    currentData = new byte[surplusData.length];
                    System.arraycopy(surplusData, 0, currentData, 0, surplusData.length);
                    index += surplusData.length;
                } else {
                    currentData = new byte[20];
                    System.arraycopy(data, index, currentData, 0, 20);
                    index += 20;
                }
                dataInfoQueue.offer(currentData);
            } while (index < data.length);
        }
        return dataInfoQueue;
    }

這篇文章簡單介紹了安卓進行低功耗藍牙開發的流程以及提到了一些注意事項,看完本文基本就可以進行低功耗藍牙開發,除此意外,強烈推薦去 Github 看一下低功耗藍牙使用實例項目,例子不難理解,看懂了 Demo 能對低功耗藍牙的使用有更深的瞭解。

本文原文地址:http://electhuang.com/2017/07/10/android-ble/

相關文章
相關標籤/搜索