Android BLE藍芽詳細解讀

艾神一不小心發表於2018-03-26

前言:

對BLE藍芽感興趣的朋友可以加入我們討論群:

QQ:494309361(Android藍芽開發小縱隊)

Android BLE藍芽詳細解讀
隨著物聯網時代的到來,越來越多的智慧硬體裝置開始流行起來,比如智慧手環、心率檢測儀、以及各式各樣的智慧傢俱和玩具類產品。安卓4.3(API 18)為BLE的核心功能提供平臺支援和API,App可以利用它來發現裝置、查詢服務和讀寫特性。相比傳統的藍芽,BLE更顯著的特點是低功耗。本文主要講解Android低功耗藍芽的api使用以及藍芽掃描、連線、傳送資料、接收資料等一系列操作,並主要介紹本人封裝的BleLib藍芽庫,非常適合藍芽初學者使用,只需要一行程式碼注入就OK了,而且用法也極其簡單,下面會專門講解BleLib庫的使用。

目錄

  • 原生API的詳細講解

  • BleLib庫的優點

  • 如何使用該庫

  • BleLib庫的詳細分析

廢話不說,先來看下Demo中的效果圖:

Demo預覽圖.gif

一、原生API的詳細講解

在BLE協議中,有兩個角色,周邊(Periphery)和中央(Central);周邊是資料提供者,中央是資料使用/處理者,一箇中央可以同時連線多個周邊,但是一個周邊某一時刻只能連線一箇中央。 首先使用藍芽就不得不說BluetoothGatt和BluetoothGattCallback這兩個類,該類繼承自BluetoothProfile,BluetoothGatt作為中央來使用和處理資料,通過BluetoothGatt可以連線裝置(connect),發現服務(discoverServices),並把相應地屬性返回到BluetoothGattCallback,BluetoothGattCallback返回中央的狀態和周邊提供的資料。

1. 藍芽開發流程:

我們藍芽操作的主要目的就是為了拿到中央BluetoothGatt這個物件,進而進行接下來的所有一系列操作,如下:

1.先拿到BluetoothManager bluetoothManager 
        = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);

2.再拿到BluetoothAdapt btAdapter = bluetoothManager.getAdapter();

3.開始掃描:btAdapter.startLeScan( BluetoothAdapter.LeScanCallback);

4.從LeScanCallback中得到BluetoothDevice 
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {…..}

5.用BluetoothDevice得到BluetoothGatt:gatt = device.connectGatt(this, true, gattCallback);
複製程式碼

這時總算拿到中央BluetoothGatt了,它有很多的方法,呼叫這些方法,你就可以通過BluetoothGattCallback和周邊BluetoothGattServer互動了。

2. 主要類的大致理解:
  • BluetoothProfile: 一個通用的規範,按照這個規範來收發資料。

  • BluetoothManager:通過BluetoothManager來獲取BluetoothAdapter

    如:BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
    複製程式碼
  • BluetoothAdapter:一個Android系統只有一個BluetoothAdapter ,通過BluetoothManager 獲取

    BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();
    複製程式碼
  • BluetoothGattDescriptor:可以看成是描述符,對Characteristic的描述,包括範圍、計量單位等。

  • BluetoothGattService:服務,Characteristic的集合。

  • BluetoothGattCallback:已經連線上裝置,對裝置的某些操作後返回的結果。這裡必須提醒下,已經連線上裝置後的才可以返回,沒有返回的認真看看有沒有連線上裝置。

    private BluetoothGattCallback GattCallback = new BluetoothGattCallback() {
    
      // 這裡有9個要實現的方法,看情況要實現那些,用到那些就實現那些
    
      public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState){};
    
      public void onCharacteristicWrite(BluetoothGatt gatt, 
                          BluetoothGattCharacteristic characteristic, int status){
    
                          };            
    
              };
     BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
    
     BluetoothGatt gatt = device.connectGatt(this, false, mGattCallback);
    複製程式碼
3. 上面所說的9個要實現的方法,所對應藍芽互動的主要對應關係:

Android BLE藍芽詳細解讀

(1) notification對應onCharacteristicChanged;

gatt.setCharacteristicNotification(characteristic, true);
複製程式碼

該方法一般是在發現服務後,進行設定的,設定該方法的目的是讓硬體在資料改變的時候,傳送資料給app,app則通過onCharacteristicChanged方法回撥給使用者,從引數中可獲取到回撥回來的資料。

(2) readCharacteristic對應onCharacteristicRead;

gatt.readCharacteristic(characteristic);
複製程式碼

(3) writeCharacteristic對應onCharacteristicWrite;

gatt.wirteCharacteristic(mCurrentcharacteristic);
複製程式碼

(4) 連線藍芽或者斷開藍芽 對應 onConnectionStateChange;

bluetoothDevice.connectGatt(this, false, mGattCallback);
或
gatt.disconnect();(斷開連線後務必記得gatt.close();)
複製程式碼

(5) readDescriptor對應onDescriptorRead;

gatt.readDescriptor(descriptor);
複製程式碼

(6) writeDescriptor對應onDescriptorWrite;

gatt.writeDescriptor(descriptor);
複製程式碼

(7) readRemoteRssi對應onReadRemoteRssi;

gatt.readRemoteRssi();
複製程式碼

(8) executeReliableWrite對應onReliableWriteCompleted;

 gatt.executeReliableWrite();
複製程式碼

(9) discoverServices對應onServicesDiscovered

gatt.discoverServices();
複製程式碼
開啟藍芽所具備的許可權:
 <uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
複製程式碼

如果 android.hardware.bluetooth_le設定為false,可以安裝在不支援的裝置上使用,判斷是否支援藍芽4.0用以下程式碼就可以了,如:

  if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
          Toast.makeText(this, “裝置不支援藍芽4.0”, Toast.LENGTH_SHORT).show();
          finish();
    }
複製程式碼

對藍芽的啟動關閉操作:

1、利用系統預設開啟藍芽對話方塊

if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
  }
複製程式碼

2、後臺開啟藍芽,不做任何提示,這個也可以用來自定義開啟藍芽對話方塊啦

mBluetoothAdapter.enable();
複製程式碼

3、後臺關閉藍芽

mBluetoothAdapter.disable();
複製程式碼

二、BleLib庫的優點

  • 最簡潔的植入(近乎一行程式碼)

Android BLE藍芽詳細解讀

private void initBle() {
    mBle = Ble.options()
            .setLogBleExceptions(true)//設定是否輸出列印藍芽日誌(非正式打包請設定為true,以便於除錯)
            .setThrowBleException(true)//設定是否丟擲藍芽異常
            .setAutoConnect(true)//設定是否自動連線
            .setConnectFailedRetryCount(3)
            .setConnectTimeout(10 * 1000)//設定連線超時時長(預設10*1000 ms)
            .setScanPeriod(12 * 1000)//設定掃描時長(預設10*1000 ms)
            .setUuid_service(UUID.fromString("0000fee9-0000-1000-8000-00805f9b34fb"))//主服務的uuid
            .setUuid_write_cha(UUID.fromString("d44bc439-abfd-45a2-b575-925416129600"))//可寫特徵的uuid
            .create(getApplicationContext());
 } 
複製程式碼
  • 最大程度簡化了程式碼量

Android BLE藍芽詳細解讀

有對比才有傷害,那就來看下原生api呼叫藍芽流程和該庫之間的對比:

例如掃描裝置
原生API寫法:
private void scanLeDevice(final boolean enable) {  
        if (enable) {  
        // 經過預定掃描期後停止掃描  
        mHandler.postDelayed(new Runnable() {  
            @Override  
            public void run() {  
                mScanning = false;  
                mBluetoothAdapter.stopLeScan(mLeScanCallback);  
            }  
        }, SCAN_PERIOD);  
            mScanning = true;  
            mBluetoothAdapter.startLeScan(mLeScanCallback);  
        } else {  
        mScanning = false;  
        mBluetoothAdapter.stopLeScan(mLeScanCallback);  
        }  
        ...  
    }  
       
然後在mLeScanCallback的回撥中拿到掃描結果:

    // Device scan callback.  
    private BluetoothAdapter.LeScanCallback mLeScanCallback =  
        new BluetoothAdapter.LeScanCallback() {  
    @Override  
    public void onLeScan(final BluetoothDevice device, int rssi,  
            byte[] scanRecord) {  
        runOnUiThread(new Runnable() {  
           @Override  
           public void run() {  
               ...
           }  
       });  
    }  
複製程式碼
BleLib中掃描的寫法:
mBle.startScan(scanCallback);
回撥結果:
BleScanCallback<BleDevice> scanCallback = new BleScanCallback<BleDevice>() {
        @Override
        public void onLeScan(final BleDevice device, int rssi, byte[] scanRecord) {
                ...
            }
        }
    };
複製程式碼
  • 提供了獨一無二的OTA升級介面(即藍芽硬體進行更新升級的介面)
這絕對是其他藍芽庫所沒有的,具體API請看下面的庫使用步驟
複製程式碼

三、如何使用該庫

首先buidl.gradle中新增依賴(最新版本請參閱Demo中的README檔案):

compile 'cn.com.superLei:blelibrary:2.5.2-beta'
複製程式碼
1. 初始化藍芽(動態授權藍芽操作許可權、開啟藍芽、判斷裝置是否支援藍芽等操作請看DEMO)
    private void initBle() {
        mBle = Ble.options()
                .setLogBleExceptions(true)//設定是否輸出列印藍芽日誌(非正式打包請設定為true,以便於除錯)
                .setThrowBleException(true)//設定是否丟擲藍芽異常
                .setAutoConnect(true)//設定是否自動連線
                .setConnectFailedRetryCount(3)//設定連線失敗的重試次數
                .setConnectTimeout(10 * 1000)//設定連線超時時長(預設10*1000 ms)
                .setScanPeriod(12 * 1000)//設定掃描時長(預設10*1000 ms)
                .setUuid_service(UUID.fromString("0000fee9-0000-1000-8000-00805f9b34fb"))//主服務的uuid
                .setUuid_write_cha(UUID.fromString("d44bc439-abfd-45a2-b575-925416129600"))//可寫特徵的uuid
                .create(getApplicationContext());
     } 
複製程式碼
2. 掃描周邊裝置
mBle.startScan(scanCallback);
//掃描回撥
BleScanCallback<BleDevice> scanCallback = new BleScanCallback<BleDevice>() {
        @Override
        public void onLeScan(final BleDevice device, int rssi, byte[] scanRecord) {
            ...
            //獲取到藍芽裝置物件,根據自身需求進行操作(庫中已進行相同裝置的過濾)
        }
    };
複製程式碼
3.開始連線
mBle.connect(device, connectCallback);               
//連線回撥
private BleConnCallback<BleDevice> connectCallback = new BleConnCallback<BleDevice>() {
        @Override
        public void onConnectionChanged(BleDevice device) {
            if (device.isConnected()) {
                //連線成功之後設定通知(切記,很重要)
                setNotify(device);
            }
            Log.e(TAG, "onConnectionChanged: " + device.isConnected());
        }

        @Override
        public void onConnectException(BleDevice device, int errorCode) {
            super.onConnectException(device, errorCode);
            Toast.makeText(BleActivity.this, "連線異常,異常狀態碼:" + errorCode, Toast.LENGTH_SHORT).show();
        }
    };
複製程式碼

連線異常狀態碼可參閱該專案的wiki

4.設定通知及回撥
private void setNotify(BleDevice device) {
         /*連線成功後,設定通知*/
        mBle.startNotify(device, new BleNotiftCallback<BleDevice>() {
            @Override
            public void onChanged(BleDevice device, BluetoothGattCharacteristic characteristic) {
                Log.e(TAG, "onChanged: 表示返回硬體MCU發來的資料"+Arrays.toString(characteristic.getValue()));
            }

            @Override
            public void onReady(BleDevice device) {
                Log.e(TAG, "onReady: 表示一切準備就緒,可以進行讀寫(傳送資料或者讀取資料)的標誌");
            }

            @Override
            public void onServicesDiscovered(BluetoothGatt gatt) {
                Log.e(TAG, "onServicesDiscovered is success ");
            }

            @Override
            public void onNotifySuccess(BluetoothGatt gatt) {
                Log.e(TAG, "onNotifySuccess is success ");
            }
        });
    }
複製程式碼

當收到onChanged(BluetoothGattCharacteristic characteristic)回撥時,則說明藍芽裝置的資料發生改變了,通知程式作出改變。還有很多回撥,他們對應的情況不懂得可以參考上面的原生API的詳細講解。

5.讀取遠端Rssi
mBle.readRssi(mBle.getConnetedDevices().get(0), new BleReadRssiCallback<BleDevice>() {
                    @Override
                    public void onReadRssiSuccess(int rssi) {
                        super.onReadRssiSuccess(rssi);
                        Log.e(TAG, "onReadRssiSuccess: " + rssi);
                        Toast.makeText(BleActivity.this, "onReadRssiSuccess:"+ rssi, Toast.LENGTH_SHORT).show();
                    }
                });
複製程式碼
6.主動讀取資料
public void read(BleDevice device) {
        boolean result = mBle.read(device, new BleReadCallback<BleDevice>() {
            @Override
            public void onReadSuccess(BluetoothGattCharacteristic characteristic) {
                super.onReadSuccess(characteristic);
                byte[] data = characteristic.getValue();
                Log.w(TAG, "onReadSuccess: " + Arrays.toString(data));
            }
        });
        if (!result) {
            Log.d(TAG, "讀取資料失敗!");
        }
複製程式碼
7.寫入資料
boolean result = mBle.write(device, changeLevelInner(), new BleWriteCallback<BleDevice>() {
            @Override
            public void onWriteSuccess(BluetoothGattCharacteristic characteristic) {
                Toast.makeText(BleActivity.this, "傳送資料成功", Toast.LENGTH_SHORT).show();
            }
        });
        if (!result) {
            Log.e(TAG, "changeLevelInner: " + "傳送資料失敗!");
        }
複製程式碼
8.傳送大資料包(如:檔案等)
try {
        //獲取整個檔案的總位元組
        byte[]data = toByteArray(getAssets().open("WhiteChristmas.bin"));
        //傳送大資料量的包(引數請查閱Demo Code)
        mBle.writeEntity(mBle.getConnetedDevices().get(0), data, 20, 50, new BleWriteEntityCallback<BleDevice>() {
            @Override
            public void onWriteSuccess() {
                L.e("writeEntity", "onWriteSuccess");
            }

            @Override
            public void onWriteFailed() {
                L.e("writeEntity", "onWriteFailed");
            }
        });
    } catch (IOException e) {
        e.printStackTrace();
    }
複製程式碼
9.設定MTU(BLE4.2)
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
        //此處第二個引數  不是特定的   比如你也可以設定500   但是如果裝置不支援500個位元組則會返回最大支援數
        mBle.setMTU(mBle.getConnetedDevices().get(0).getBleAddress(), 250, new BleMtuCallback<BleDevice>() {
            @Override
            public void onMtuChanged(BleDevice device, int mtu, int status) {
                super.onMtuChanged(device, mtu, status);
                ToastUtil.showToast("最大支援MTU:"+mtu);
            }
        });
    }else {
        ToastUtil.showToast("裝置不支援MTU");
    }
複製程式碼
10.OTA升級
//找到你需要升級檔案的路徑(一般情況都是儲存再伺服器上,一旦有更新會自動提示,然後APP下載並儲存到本地,生成對應的file物件)
File file = new File(...);
//讀寫SD卡許可權,此處略(6.0及以上需新增)
OtaManager mOtaManager = new OtaManager(BleActivity.this);
boolean result = mOtaManager.startOtaUpdate(file, (BleDevice) mBle.getConnetedDevices().get(0), mBle);
Log.e("OTA升級結果:", result + "");
複製程式碼

四、BleLib庫封裝的詳細分析

分析之前先來張BleLib庫API的結構圖供大家參考(下圖是1.x庫的結構,API名稱部分與當前有點不同):

BleLib庫結構圖.png

Android BLE藍芽詳細解讀

1、我們先來看一下該庫的結構,以及每個類的作用。如下圖:

結構.png

Ble:

該類提供了幾乎所有你需要用到的方法,包括藍芽掃描、連線、斷開、藍芽當前連線狀態等等,管理了藍芽操作的所有介面和方法。

BleDevice:

該類的主要是來描述並記錄藍芽的屬性和狀態,如記錄藍芽名稱、藍芽MAC地址、藍芽別名(即修改之後的名稱)、藍芽連線狀態等。

Android BLE藍芽詳細解讀

BleStatus:

該類是藍芽狀態類,定義了藍芽掃描、連線、通知使能、傳送、接收等狀態的常量值(連線異常等狀態碼可參考該類)

BluetoothLeService:

該類是最重要的一個核心藍芽處理類,主要是藍芽操作中用到的各個方法的實現類,是整個藍芽的核心功能實現,Ble是對外提供所有藍芽操作介面的管理類。

在此要注意一些細節,比如大多數裝置掃描的時候會重複掃描到相同藍芽裝置,必須要進行過濾,開發應用時,必須還要進行產品過濾,比如通過裝置的廣播包過濾,或者通過裝置名過濾都是可以的,如下(注意:要根據自己產品提供的廣播包進行過濾,下圖是我們自己產品的):

  /**
 * Verify the product broadcast parameters
 * @param data Parameter data
 * @return Whether the match
 */
public static boolean matchProduct(byte[] data) {
    if (data == null || data.length <= 0) {
        return false;
    }
    int i = 0;
    do {
        // Read packet size
        int len = data[i++] & 0xff;
        if (len > 0) {
            // Read packet data
            byte[] d = new byte[len];
            int j = 0;
            do {
                d[j++] = data[i++];
            } while (j < len);
            // Authentication Type and Length
            if (d.length > BROADCAST_SPECIFIC_PRODUCT.length && (d[0] & 0xFF) == BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA) {
                // Matching product parameters
                boolean passed = true;
                for (int x = 0; x < BROADCAST_SPECIFIC_PRODUCT.length; x++) {
                    passed = passed && d[x + 1] == BROADCAST_SPECIFIC_PRODUCT[x];
                }
                //Match successful
                if (passed) {
                    return true;
                }
            }
        }

    } while (i < data.length);
    return false;
}
複製程式碼

OK,要注意的細節問題已經介紹的差不多了,如果感興趣的朋友可以去應用該庫到自己的專案中。文章末尾順帶宣傳下自己的一個Android開源庫分享的小程式,當前版本暫時只有Android的開源庫分享,後續會增加Java、iOS、前端等不同語言的分類,感興趣的可以去關注下我們的小程式,小程式原始碼地址:GitClub。請掃描這個二維碼登陸GitClub小程式參觀。

Geek Reader小程式二維碼

重中之重:附BleLib庫GitHub地址

QQ:494309361 (Android藍芽開發小縱隊)

相關文章