我已经创建了 BLE 发送器类,用于通过蓝牙 LE 发送大型 ByteArray 发送流程逻辑如下:
- 编写描述符以启用对发送数据的特征的通知
- 通过以下方式通知外设有关数据发送过程 写入相应的特征(数据大小: block 大小: block 数)
- 等待外设通知chunk 0发送数据发送特性
- 收到通知后,开始以 20 字节为单位(BLE 限制)发送第一个 1000 字节的 block ,其中每个 block 包含 block 号和 18 字节的数据,发送 1000 字节后,发送发送数据的校验和 block
- 外设通过校验和验证数据并通知下一个 block 的描述符
我的问题是:有没有更好的方法? 我发现多次写入特性需要至少 20 毫秒的延迟。有什么办法可以避免这种情况吗?
更改了实现而不是 20 毫秒,我正在等待回调 onCharacteristicWrite 作为 Emil建议。并且还更改了准备方法以减少 18 字节 block 发送之间的计算时间:
class BluetoothLEDataSender(
val characteristicForSending: BluetoothGattCharacteristic,
val characteristicForNotifyDataSend: BluetoothGattCharacteristic,
private val config: BluetoothLESenderConfiguration = BluetoothLESenderConfiguration(),
val bluetoothLeService: WeakReference<BluetoothLeService>) : HandlerThread("BluetoothLEDataSender") {
data class BluetoothLESenderConfiguration(val sendingIntervalMillis: Long = 20L, val chunkSize: Int = 1000, val retryForFailureInSeconds: Long = 3)
private val toaster by lazy { Toast.makeText(bluetoothLeService.get()!!,"",Toast.LENGTH_SHORT) }
companion object {
val ACTION_DATA_SEND_FINISHED = "somatix.com.bleplays.ACTION_DATA_SEND_FINISHED"
val ACTION_DATA_SEND_FAILED = "somatix.com.bleplays.ACTION_DATA_SEND_FAILED"
}
lateinit var dataToSend: List<BlocksQueue>
val messageHandler by lazy { SenderHandler()}
var currentIndex = 0
public fun notifyDataState(receivedChecksum: String) {
val msg = Message()
msg.arg1 = receivedChecksum.toInt()
messageHandler.sendMessage(msg)
}
inner class BlocksQueue(val initialCapacity:Int):ArrayBlockingQueue<ByteArray>(initialCapacity)
inner class BlockSendingTask:Runnable{
override fun run() {
executeOnUiThread({ toaster.setText("Executing block: $currentIndex")
toaster.show()})
sendNext()
}
}
public fun sendMessage(messageByteArray: ByteArray) {
start()
dataToSend = prepareSending(messageByteArray)
bluetoothLeService.get()?.setEnableNotification(characteristicForSending,true)
val descriptor = characteristicForSending.getDescriptor(DESCRIPTOR_CONFIG_UUID)
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
bluetoothLeService.get()?.writeDescriptor(descriptor)
characteristicForNotifyDataSend.value = "${messageByteArray.size}:${config.chunkSize}:${dataToSend.size}".toByteArray()
toaster.setText(String(characteristicForNotifyDataSend.value))
toaster.show()
messageHandler.postDelayed({bluetoothLeService.get()?.writeCharacteristic(characteristicForNotifyDataSend)}, config.sendingIntervalMillis)
}
private fun prepareSending(messageByteArray: ByteArray): ArrayList<BlocksQueue> {
with(config)
{
var chunksNumber = messageByteArray.size / config.chunkSize
chunksNumber = if (messageByteArray.size == chunksNumber * config.chunkSize) chunksNumber else chunksNumber + 1
val chunksArray = ArrayList<BlocksQueue>()
(0 until chunksNumber).mapTo(chunksArray) {
val start = it * chunkSize
val end = if ((start + chunkSize) > messageByteArray.size) messageByteArray.size else start + chunkSize
val sliceArray = messageByteArray.sliceArray(start until end)
listOfCheckSums.add(sliceArray.checkSum())
var capacity = sliceArray.size / 18
capacity = if(sliceArray.size - capacity*18 == 0) capacity else capacity + 1
//Add place for checksum
val queue = BlocksQueue(capacity+1)
for(i in 0 until capacity){
val start1 = i *18
val end1 = if((start1 + 18)<sliceArray.size) start1 +18 else sliceArray.size
queue.add(sliceArray.sliceArray(start1 until end1))
}
queue.add(sliceArray.checkSum().toByteArray())
queue
}
return chunksArray
}
}
fun sendNext(){
val currentChunk = dataToSend.get(currentIndex)
val peek = currentChunk.poll()
if(peek != null)
{
if(currentChunk.initialCapacity > currentBlock+1)
{
val indexByteArray = if(currentBlock>9) "$currentBlock".toByteArray() else "0${currentBlock}".toByteArray()
characteristicForSending.value = indexByteArray + peek
}
else{
characteristicForSending.value = peek
}
bluetoothLeService.get()?.writeCharacteristic(characteristicForSending)
currentBlock++
}
else
{
Log.i(TAG, "Finished chunk $currentIndex")
currentBlock = 0
}
}
private val TAG= "BluetoothLeService"
@SuppressLint("HandlerLeak")
inner class SenderHandler:Handler(looper){
private var failureCheck:FailureCheck? = null
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
currentIndex = msg.arg1
if(currentIndex < dataToSend.size)
{
if (currentIndex!= 0 && failureCheck != null)
{
removeCallbacks(failureCheck)
}
failureCheck = FailureCheck(currentIndex)
post(BlockSendingTask())
postDelayed(failureCheck,TimeUnit.MILLISECONDS.convert(config.retryForFailureInSeconds,TimeUnit.SECONDS))
}
else {
if (currentIndex!= 0 && failureCheck != null)
{
removeCallbacks(failureCheck)
}
val intent= Intent(ACTION_DATA_SEND_FINISHED)
bluetoothLeService.get()?.sendBroadcast(intent)
}
}
private inner class FailureCheck(val index:Int):Runnable{
override fun run() {
if (index==currentIndex){
val intent= Intent(ACTION_DATA_SEND_FAILED)
bluetoothLeService.get()?.sendBroadcast(intent)
}
}
}
}
}
最佳答案
等待 20 毫秒是怎么回事?使用特征写入来抽取数据的首选方法是首先使用“Write Without Response”(https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#WRITE_TYPE_NO_RESPONSE),然后执行 Write,然后等待 onCharacteristicWrite 回调,然后立即执行下一个 Write。您需要等待 onCharacteristicWrite 回调,因为 API 不允许您同时有多个待处理的命令/请求。
关于android - 在 Android 上通过 BLE API 发送大文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48250732/