1. 程式人生 > >android BLE從入門到精通開發

android BLE從入門到精通開發

目前智慧家居都被看成是下一個科技爆發點,而智慧家居里面使用的技術,響應最高的就算是BLE了,下面,我們說一下android怎麼開發BLE,和要注意的一些問題:

1.首先,得知道,android是從android4.3版本才開始支援BLE的,所以,開發的前提就是要知道系統的支援:

if (android.os.Build.VERSION.SDK_INT < 18) {
    // 說明sdk不夠高版本
}

2.宣告許可權,BLE,就是低功耗藍芽的英文縮寫,所以,就是要開啟藍芽的許可權,在manifest新增:

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

3.獲取介面卡,這裡需要注意,和經典藍芽有個點區別(千萬別搞錯了):

mBluetoothManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();

4.檢視手機藍芽是否開啟,如果未開啟,則需要主動開啟(不推薦),或者提示使用者開啟(推薦):

if (!mBluetoothAdapter.isEnabled()) {
    startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 0);  // 彈對話方塊的形式提示使用者開啟藍芽
    //mBluetoothAdapter.enable(); // 強制開啟,不推薦使用
}

// 註冊個廣播監聽這個值,就可以獲取到藍芽的開關狀態
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { // 藍芽開關發生變化
    // 這裡可以直接使用mBluetoothAdapter.isEnabled()來判斷當前藍芽狀態
return; }

5.接下來就是掃描BLE,這裡有2個部分,android4.3-5.0的版本,和5.0+的版本:

// 4.3-5.0版本
mBluetoothAdapter.stopLeScan(lescancallback);   // 停止掃描
mBluetoothAdapter.startLeScan(lescancallback);  // 開始掃描
private LeScanCallback lescancallback = new LeScanCallback() {
    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        // device 就是掃描到的藍芽物件,裡面各種跟藍芽有關的資訊
        // rssi訊號強度,這個值是個負數,範圍一般為0到-100,負數越大,代表訊號越弱,一般如果超過-90,連線會出現不理想的情況
        // scanRecord廣播資料,裡面的資料就是藍芽裝置希望手機在連線它之前,讓手機知道的資訊(稍後的篇章講解廣播資料的組成格式,並且如何解析)
    }
};

// 5.0+版本
BluetoothLeScanner scaner = mBluetoothAdapter.getBluetoothLeScanner();  // android5.0把掃描方法單獨弄成一個物件了
scaner.stopScan(mScanCallback);   // 停止掃描
scaner.startScan(mScanCallback);  // 開始掃描
private ScanCallback mScanCallback = new ScanCallback() {
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        super.onScanResult(callbackType, result);
        // callbackType:確定這個回撥是如何觸發的
        // result:包括4.3版本的藍芽資訊,訊號強度rssi,和廣播資料scanRecord
    }
    @Override
    public void onBatchScanResults(List<ScanResult> results) {
        super.onBatchScanResults(results);
        // 批量回調,一般不推薦使用,使用上面那個會更靈活
    }
    @Override
    public void onScanFailed(int errorCode) {
        super.onScanFailed(errorCode);
        // 掃描失敗,並且失敗原因
    }
};

因為有2種回撥方法,最好在開發的時候,重新打包封裝成一個類,根據系統不同,而分別呼叫不同的方法,因為google會想到推出新方法,那固然會在以後的某個版本把老方法取消。
需要注意的是:

  • 在掃描前,最好先呼叫一次停止掃描
  • 而且掃描和停止掃描裡面的引數物件必須是同一個,所以,這裡不能用匿名的方式來建立掃描回撥
  • 不要長時間開始掃描,停止掃描,開始掃描,這樣的迴圈,掃描是很耗電的,部分機型這樣會導致藍芽宕機
  • 當掃描到自己需要的裝置時,停止掃描。

6.連線BLE,大部分手機可以通過MAC來直接連線,不需要掃描,小部分一定要先掃描到才可以進行連線(目前所知型號紅米1s):

mBluetoothAdapter.stopLeScan(lescancallback);     // 停止掃描,連線前,必須停止掃描,不然,失敗率很高
device = mBluetoothAdapter.getRemoteDevice(mac);  // 通過mac地址獲取藍芽物件,或者可以直接用掃描到的物件,效果都是一樣
mBluetoothGatt = device.connectGatt(context, false, mGattCallback);
// BLE連接回調
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, final int status, final int newState) {
        super.onConnectionStateChange(gatt, status, newState);
            handl.post(new Runnable() {
                public void run() {
        if (status != BluetoothGatt.GATT_SUCCESS) { // 連線失敗判斷
            return;
        }
        if (newState == BluetoothProfile.STATE_CONNECTED) { // 連線成功判斷
            mBluetoothGatt.discoverServices(); // 發現服務
            return;
        }
        if (newState == BluetoothProfile.STATE_DISCONNECTED) {  // 連線斷開判斷
            return;
        }
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, final int status) {
        super.onServicesDiscovered(gatt, status);
        if (status != BluetoothGatt.GATT_SUCCESS) { // 發現服務失敗
            return;
        }
        //不是失敗的情況就是成功
    }

    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, final int status) {
        super.onDescriptorWrite(gatt, descriptor, status);
        if (status != BluetoothGatt.GATT_SUCCESS) {  // 寫Descriptor失敗
            return;
        }
        //不是失敗的情況就是成功
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        super.onCharacteristicChanged(gatt, characteristic);
        //BLE裝置主動向手機發送的資料時收到的資料回撥
        characteristic.getValue(); // 通過這個方法來提取收到的資料
    }
    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicWrite(gatt, characteristic, status);
        if (status != BluetoothGatt.GATT_SUCCESS) {  // 寫資料失敗
            return;
        }
    }
// 還有很多其他回撥方法,這裡就不一一介紹了
};

需要注意的是:

  • BLE連線成功並不意味著可以通訊,需要發現服務後,才可以正常通訊,不要問我為什麼,因為這就是BLE協議
  • 以手機開啟藍芽為起始點,對某個裝置第一次連線,速度會比較慢,這個速度主要取決於BLE裝置的廣播頻率(不懂廣播頻率?就問你們韌體工程師吧),第二次以後,會快很多,因為手機會快取很多資訊,比如雖然需要重新發現服務,但是實際上,手機並沒有去發現服務,而是用了快取的服務。所以,會比較快
  • 中間可能會有回撥很多錯誤,記住一點,不要去深究是什麼原因,因為android文件上面沒有說,所以,最好的解決方法就是,一旦出現錯誤,就斷開,重新連線,因為BLE底層會對失敗進行多次重試,如果告訴你出錯誤了,也就是說其實底層已經重試了很多次了,還是錯誤。所以,直接斷開,重連就行了

7.獲取通訊特徵值:BLE連線成功後,就需要發現服務,發現服務後,就需要發現特徵值,這個特徵值才是真正BLE通訊需要用到的東西。發現服務後,服務裡面有很多BluetoothGattService,這個BluetoothGattService,你可以看做是一個個的資料夾,裡面包含了很多檔案(特徵值BluetoothGattCharacteristic)

private static final String DATA_SERVICE_UUID = "0000f1f0-0000-1000-8000-00805f9b34fb";
BluetoothGattService data_service = mBluetoothGatt.getService(UUID.fromString(DATA_SERVICE_UUID));  // 先獲取BluetoothGattService
BluetoothGattCharacteristic txd_charact = data_service.getCharacteristic(UUID.fromString(TXD_CHARACT_UUID)); // 再通過BluetoothGattService獲取BluetoothGattCharacteristic特徵值

8.手機往BLE裝置寫入資料:獲取到特徵值後,就可以正式的進行通訊了,手機往裝置寫資料:

txd_charact.setValue(senddatas);  // 往通道寫資料

注意:因為大部分BLE只支援最大20byte的通訊,所以,如果有大於20byte的資料需要傳送,請拆成多個20byte以內的陣列,進行分包傳送(當然後面我們會講如何一次性發送超過20byte的方法)
9.BLE裝置主動往手機寫資料:這個功能叫做notify、indecation(用這種方式的比較少,微信BLE就是用這種方式),漢語叫可通知屬性(不是所有的通道都有這個屬性),要主動去開啟(有些不規範的BLE裝置已經預設開啟,可以省略這步)。成功與否,需要看回調(看上面第6點)

if (0 != (charact.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY)) { // 檢視是否帶有可通知屬性notify
    mBluetoothGatt.setCharacteristicNotification(charact, true);
    BluetoothGattDescriptor descriptor = charact.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); // 這包資料什麼意思,可以不用管,反正是固定這包資料就是了。
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    mBluetoothGatt.writeDescriptor(descriptor);
} else if (0 != (charact.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE)) {  // 檢視是否帶有indecation屬性
    mBluetoothGatt.setCharacteristicNotification(charact, true);
    BluetoothGattDescriptor descriptor = charact.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
    descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
    mBluetoothGatt.writeDescriptor(descriptor);
}

到這裡還不夠,上面的只是設定BLE裝置可以主動向手機發送資料,手機還需要開啟是否接收BLE裝置發來的資料:這裡設定成功與否沒有回撥,運行了這段程式碼,就設定成功了

mBluetoothGatt.setCharacteristicNotification(charact, enable);  // 設定手機是否接收BLE裝置發來的資料

10.斷開連線:也有相應的回撥(看上面第6點)

mBluetoothGatt.disconnect();

需要注意的是:android的BLE有很多問題,如果一定時間(一般5秒,根據BLE裝置不同而不同)內沒有回撥斷開,則需要強制關一下手機藍芽,至於是強制開是強制開,還是用提示使用者的方式,根據自己的需求而定