Android低功耗藍芽通訊
一、寫在前面的話
- 一直想寫一篇關於藍芽與ble裝置通訊的部落格,但是一直也不知道從何下手,可能是之前思路不清晰吧,也就一直拖拖拖,拖到現在。最近又做到關於ble裝置的專案了,在此總結一下吧。(如有不到位或者不太對的地方,希望各位多多指教)
二、關於藍芽
- 藍芽是一種短距的無線通訊技術,可實現固定裝置、移動裝置之間的資料交換。一般將藍芽3.0之前的BR/EDR藍芽稱為傳統藍芽,而將藍芽4.0規範下的BLE藍芽稱為低功耗藍芽。
-
如圖:
-
BLE是Bluetooth low energy的意思,屬於藍芽低功耗協議,Android4.3以上及蘋果手機等現在都支援藍芽BLE,主要面向感測器應用市場,進行短時間小資料傳輸,如健康領域:手機監測血壓,體育:手機計步器等。
-
低功耗藍芽通訊協議:
三、梳理整體邏輯(思路/步驟)
- 許可權問題:先判斷手機是否滿足android4.3以上版本,再判斷手機是否開啟藍芽。
- 搜尋藍芽:搜尋藍芽,回撥介面中檢視ble裝置相關資訊,一定時間停止掃描。
- 連線藍芽:首先獲取到ble裝置的mac地址,然後呼叫connect()方法進行連線。
- 獲取特徵:藍芽連線成功後,需要獲取藍芽的服務特徵等,然後開啟接收設定。
- 傳送訊息:writeCharacteristic()方法,傳送資料給ble裝置。
- 接收訊息:通過藍芽的回撥介面中onCharacteristicRead()方法,接收藍芽收的訊息。
- 釋放資源:斷開連線,關閉資源。
四、具體實現
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;
}
五、開發中踩過的坑
- 通知開啟後,才能讀到資料,否則讀不到。
- 傳送資料時,如果一包資料超過20位元組,需要分包傳送,一次最多傳送二十位元組。
- 接收資料時,一次最多也只接收20位元組的資料,需要將接收到的資料拼接起來,在資料的結尾弄一個特定的標識,去判斷資料是否接受完畢。
- 每次傳送資料或者資料分包傳送時, 操作間要有至少15ms的間隔。
- 最近公司來了個新的藍芽產品,發現獲取不到需要的特徵,後來打斷點,發現他們藍芽裝置的通知特徵根本沒有,是他們給錯協議了。。。所以建議各位開發的時候,如果一直連線失敗,也可以檢視一下寫特徵和通知特徵是否為空,是不是賣家搞錯了,協議和產品不匹配。(當然,這樣馬虎的賣家估計是少數)。
- 又補充來了!這個藍芽如果出現掃描不到的情況,那是因為手機沒有開啟定位許可權,清單檔案中寫上定位許可權,程式碼中在動態獲取下就OK了。
六、demo圖示
歡迎關注技術公眾號,微訊號搜尋ColorfulCode 程式碼男人
分享技術文章,投稿分享,不限技術種類,不限技術深度,讓更多人因為分享而受益。