Android ble (藍牙低功耗)使用注意事項(轉)


一、如何定義ble中service uuid?

  • 藍牙標准規范里面定義了很多已經定義過的service uuid,如果沖突了會造成很多意外的問題。
  • 藍牙的service uuid的格式如下
    UUID.fromString("00001234-0000-1000-8000-00805f9b34fb")
  • 在Android可以簡單的采用這個原則:1、利用這個字符串【00002903-0000-1000-8000-00805f9b34fb】用第5-8位的數字做變化,其他數字保持不變。比如
    UUID.fromString("00007777-0000-1000-8000-00805f9b34fb")
    UUID.fromString("00009999-0000-1000-8000-00805f9b34fb")

二、ble中心設備開啟掃描,設置所關心的serviceuuid。

bluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
        List<ScanFilter> filters = new ArrayList<>(); ScanFilter filter = new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString("00007777-0000-1000-8000-00805f9b34fb");) .build(); filters.add(filter); ScanSettings scanSettings = new ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) .build(); bluetoothLeScanner.startScan(filters, scanSettings, scanCallback); 

new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString("00007777-0000-1000-8000-00805f9b34fb");

三、ble外圍設備可以在廣播的時候設定AdvertiseData的magic number【manufacturerId 和 manufacturerSpecificData】。這樣即使定義service uuid跟別人的有沖突,也可以在中心過濾該magic number來找到符合自己需求的外圍設備

  • 外圍構建AdvertiseData
AdvertiseData.Builder()
                .setIncludeDeviceName(true) .addServiceUuid(ParcelUuid.fromString("00007777-0000-1000-8000-00805f9b34fb")) .addManufacturerData(0x7777, new byte[]{0x07, 0x07}) .build(); 
  • 中心處理AdvertiseData中的
final ScanCallback scanCallback = new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult result) { super.onScanResult(callbackType, result); ScanRecord scanRecord = result.getScanRecord(); SparseArray<byte[]> mandufacturerData = scanRecord.getManufacturerSpecificData(); 

此時可以根據mandufacturerData來匹配自己設定的外圍設備

四、什么時候ble的中心和外圍才算真正的連接上?(可以開始傳輸數據了)

在BluetoothGattCallback中的關於此問題有三步回調
1、public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)

  • 這是ble中心和外圍連接后,最先觸發的回調。newstate等於BluetoothProfile.STATE_CONNECTED僅僅表示中心設備連接上了,這個時候需要去調用BluetoothGatt去發現服務。

  • 注意case1,在new state為BluetoothProfile.STATE_DISCONNECTED時,務必關掉BluetoothGatt,因為每次調用mBluetoothGatt = device.connectGatt(SpeakerApp.appContext, false, mGattCallBack);都會生成新的對象,而不會去主動關閉老的對象

  • 注意case2,133問題,iPhone 和 某些Android手機作為旁支會出現藍牙初始連接就是133,此情況下應該立刻重新掃描連接。133問題鏈接

//iPhone 和 某些Android手機作為旁支會出現藍牙初始連接就是133,此情況下立刻重試 if (status == 133) { RLog.d(TAG, "發生設備初始連接133情況,需要重新掃描連接設備"); mBluetoothGatt.close(); //!!!需要去增加代碼進行重新掃描重連 return; } if (newState == BluetoothProfile.STATE_CONNECTED) { mBluetoothGatt = gatt; mBluetoothGatt.discoverServices(); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { mBluetoothGatt.close(); mBluetoothGatt = null; } 

2、 public void onServicesDiscovered(BluetoothGatt gatt, int status)
mBluetoothGatt.discoverServices()執行后得到的callback,如果狀態為GATT_SUCCESS,則可以獲取ble旁支發起廣播的service和descriptor,把廣播設為enable

mCharacteristic = service.getCharacteristic(UUID.fromString("00007770-0000-1000-8000-00805f9b34fb")); if (mCharacteristic == null) { RLog.e(TAG, "Can't find target characteristic."); return; } gatt.setCharacteristicNotification(mCharacteristic, true); BluetoothGattDescriptor descriptor = mCharacteristic.getDescriptor(UUID.fromString("00007777-0000-1000-8000-00805f9b34fb")); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); gatt.writeDescriptor(descriptor); 

3、public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status)
只有這一步status == BluetoothGatt.GATT_SUCCESS,才可以真正的傳輸數據,如果在第一步或者第二步就開始傳輸數據,會在某些特定的case下導致未知的bug或者空指針錯誤

所以,在中心設備跟外圍開始連接后,你可以設定一個超時時間,在超時時間過后,依然沒能回調onDescriptorWrite並獲得BluetoothGatt.GATT_SUCCESS,則此次過程失敗,你可以根據實際情況進行重連或者提示錯誤

五、mtu-20字節問題

mtu20的來源:GATT是基於ATT Protocol的,而它的 core spec里面定義了ATT的默認MTU為23個bytes,除去ATT的opcode一個字節以及ATT的handle2個字節之后,剩下的20個字節便是留給GATT的了

如果要傳輸大於20字節的數據怎么辦?

1、 系統mtu可以支持修改到512字節,完成大數據量的傳輸。但是由於涉及到中心和旁支都需要修改,會造成很大的局限性和底層修改量,而且會觸發比如某些設備第一次修改不生效,另一個設備一次連接中只能修改一次等bug,非常不可取,十分不建議。

2、分包傳輸,自己設計協議分包傳輸是最可取的方案,需要注意的是在分包后,每一個包之間寫入數據需要設置間隔,比如100ms。

六、寫數據之前做校驗,判斷獲取的characteristic是否滿足可讀,可廣播,或者需要回復等約定。

  return ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0 || (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0); 

七、丟數據包問題

在做好5和6的基礎上,依然會在一些設備上出現,由於系統原因,ble剛開始的發送第一個數據出現丟包,請對此做出特殊處理。

八、解析數據

  • 中心端mtu分包發給外圍后,外圍可以在
    public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) 接收到數據並還原成原始數據

  • 外圍端mtu分包發給中心端后,中心端可以在 public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) 接收到數據並還原成原始數據

  • 注意,對於一些藍牙設備,總有一些特殊的狀態,對於接受到的數據一定要進行正確性校驗

九、other坑

  • ble中的PROPERTY_WRITE_NO_RESPONSE不可信任,google的有些版本並沒有去讀取這個屬性值,而是直接設置為需要résponse,穩妥的方式最好設置為必須回復

  • 在項目中如果有多個ble或 ble + 經典藍牙連接,在一些臨界情況(比如設備重啟,crash閃退重啟),a ble連接可能需要移除b ble(或 b經典藍牙)連接產生的設備,否則會導致a ble一直連接不上。

 BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice("另一個ble 或者 藍牙設備mac值"); if (remoteDevice.getBondState() == BluetoothDevice.BOND_BONDED) { try { Method removeBond = remoteDevice.getClass().getDeclaredMethod("removeBond"); removeBond.invoke(remoteDevice); RLog.d(TAG , "成功移除系統bug"); } catch (Exception e) { RLog.e(TAG , "反射異常"); } } 
 

作者:RDuwan.鏈接:




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM