android - 在 Android 上通过 BLE API 发送大文件

标签 android bluetooth kotlin bluetooth-lowenergy android-ble

我已经创建了 BLE 发送器类,用于通过蓝牙 LE 发送大型 ByteArray 发送流程逻辑如下:

  1. 编写描述符以启用对发送数据的特征的通知
  2. 通过以下方式通知外设有关数据发送过程 写入相应的特征(数据大小: block 大小: block 数)
  3. 等待外设通知chunk 0发送数据发送特性
  4. 收到通知后,开始以 20 字节为单位(BLE 限制)发送第一个 1000 字节的 block ,其中每个 block 包含 block 号和 18 字节的数据,发送 1000 字节后,发送发送数据的校验和 block
  5. 外设通过校验和验证数据并通知下一个 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/

相关文章:

android - 添加/删除 PagerAdapter/ViewPager 问题 Android 的 View

android - 具有十进制值的 SeekBar

java - 在 Kotlin 中将 Dp 转换为 Px - 这种转换永远不会成功

kotlin - Kotlin 有复制函数吗?

android - 如何强制 listView 的高度大小,以便每一行都可见?

android - 当设备屏幕被锁定时,UDP/RTCP 数据包未到达设备/应用程序

android - 由于 list ,arduino 和 android 设备连接错误

java - Android通过蓝牙向Arduino发送数据

java - 在 Java 中为字节数组实现缓冲区

android - 如何将菜单项显示为 fragment 中工具栏上的操作