Android BLE藍牙詳細解讀
随着物聯網時代的到來,越來越多的智能硬體裝置開始流行起來,比如智能手環、心率檢測儀、以及各式各樣的智能家具和玩具類産品。安卓4.3(API 18)為BLE的核心功能提供平台支援和API,App可以利用它來發現裝置、查詢服務和讀寫特性。相比傳統的藍牙,BLE更顯著的特點是低功耗。本文主要講解Android低功耗藍牙的api使用以及藍牙掃描、連接配接、發送資料、接收資料等一系列操作,并主要介紹本人封裝的BleLib藍牙庫,非常适合藍牙初學者使用,隻需要一行代碼注入就OK了,而且用法也極其簡單,下面會專門講解BleLib庫的使用。
代碼位址如下:
http://www.demodashi.com/demo/15062.html
随着物聯網時代的到來,越來越多的智能硬體裝置開始流行起來,比如智能手環、心率檢測儀、以及各式各樣的智能家具和玩具類産品。安卓4.3(API 18)為BLE的核心功能提供平台支援和API,App可以利用它來發現裝置、查詢服務和讀寫特性。相比傳統的藍牙,BLE更顯著的特點是低功耗。本文主要講解Android低功耗藍牙的api使用以及藍牙掃描、連接配接、發送資料、接收資料等一系列操作,并主要介紹本人封裝的BleLib藍牙庫,非常适合藍牙初學者使用,隻需要一行代碼注入就OK了,而且用法也極其簡單,下面會專門講解BleLib庫的使用。
目錄
-
原生API的詳細講解
-
BleLib庫的優點
-
如何使用該庫
-
BleLib庫的詳細分析
廢話不說,先來看下Demo中的效果圖:
一、原生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個要實作的方法,所對應藍牙互動的主要對應關系:
(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庫的優點
- 最簡潔的植入(近乎一行代碼)
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());
}
- 最大程度簡化了代碼量
有對比才有傷害,那就來看下原生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請看下面的庫使用步驟
三、如何使用該庫
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名稱部分與目前有點不同):
1、我們先來看一下該庫的結構,以及每個類的作用。如下圖:
Ble:
該類提供了幾乎所有你需要用到的方法,包括藍牙掃描、連接配接、斷開、藍牙目前連接配接狀态等等,管理了藍牙操作的所有接口和方法。
BleDevice:
該類的主要是來描述并記錄藍牙的屬性和狀态,如記錄藍牙名稱、藍牙MAC位址、藍牙别名(即修改之後的名稱)、藍牙連接配接狀态等。
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 BLE藍牙詳細解讀
代碼位址如下:
http://www.demodashi.com/demo/15062.html
注:本文著作權歸作者,由demo大師代發,拒絕轉載,轉載需要作者授權