前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android BlueToothBLE入门(三)——数据的分包发送和接收(源码已更新)

Android BlueToothBLE入门(三)——数据的分包发送和接收(源码已更新)

作者头像
Vaccae
发布2023-08-22 08:10:15
2.2K0
发布2023-08-22 08:10:15
举报
文章被收录于专栏:微卡智享

学更好的别人,

做更好的自己。

——《微卡智享》

本文长度为3675,预计阅读12分钟

前言

接上篇《Android BlueToothBLE入门(二)——设备的连接和通讯(附Demo源码地址)》最后提到过蓝牙BLE通讯每次默认发送的数据为20字节,如果我们要处理大的数据时,需要修改MTU的值,还有就是分包数据发送,本篇就专门来看看怎么实现的分包数据的发送和接收。

实现效果

代码实现

微卡智享

01

修改MTU值

修改MTU值其实是非常简单的,本身有个函数直接调用就可以申请,直接在BlueToothGatt下面有个requestMtu方法,其中参数size就是要申请的MTU值。

前面说过,BLE通讯默认是20字节,最大也只有512字节,所以既然申请MTU,那就往最大申请即可,代码中还是在当时BlueToothBLEUtil的类中先定义一个mtuSize,用于记录当前的mtu值,后面根据这个值的大小来实现分包处理的。

申请Mtu时我这里放到了发现服务返回后直接再做申请,那就是修改Gatt的回调方法里面onServicesDiscovered

最开始是连接成功后,发现服务并直接申请修改Mtu,在测试过程中有时候会服务没有返回刷不出来,所以改到发现服务后再申请。

02

分包发送数据和接收处理

申请MTU比较简单,现在是这篇文的重点了,分包的方式其实也有多种,我这边采用的是每个数据包中前4个字节来定义总包数和当前包数,后面的是当前包的数据,如下图所示。

上面可以看到,1-2字节是代表总包数,3-4字节是当前包数,5-512字节是当前包的数据。

其实这里主要要说为什么是前4个字节来记录总包数和当前包,1个byte的数字范围是-128到127,总共就256个数字存储,考虑到每个包最大512字节,如果数据量特别大,拆分的包数大于256就有问题了,而正常的int类型存储需要4个byte,总包数和当前包如果都使用int存储就直接减少了8个字节,所以这里我采用的是2个byte存储,最大范围是65535,这个分包数应该就够了。

两个字节和int类型的相互转化函数

接下来是分包和截取数据的相关处理了,通过ByteArray转换为list后,再进行chunked根据每个包实际大小生成list,再进行组包,转成Array<ByteArray>输出。

每个包的数据截取,通过ByteArray中的slice进行获取,截取后再进行转换即可获取总包数和当前包数。

bytearray相关的处理这里新建了一个Class实现的,直接贴上来。

代码语言:javascript
复制
package vac.test.bluetoothbledemo.repository


object BLEByteArrayUtil {

    //计算发送的数据库生成数组
    fun calcSendbyteArray(byteArray: ByteArray): Array<ByteArray?> {
        //根据当前的传输MTU值计算要分的包数
        //分包格式前前两个byte是总包数,当前包数,
        //为了节省字节,前4个字节为总包数2个,当前包数2个,
        //采用short的取值范围65536,分包如果超过这个总包数,就不做传输了
        val everybytelen = BlueToothBLEUtil.mtuSize - 4
        val totalpkgs =
            byteArray.size / everybytelen + if (byteArray.size % everybytelen > 0) 1 else 0

        val listbyte = byteArray.toList().chunked(everybytelen)
        val arybytes = arrayOfNulls<ByteArray>(totalpkgs)
        //定义总包数
        val totalbytepkgs = inttobytes2bit(totalpkgs)
        for(i in 0 until totalpkgs) {
            //转换当前包数
            val curbytepkgs = inttobytes2bit(i)
            val curbytes = totalbytepkgs.plus(curbytepkgs).plus(listbyte[i])
            arybytes[i] = curbytes
        }
        return arybytes
    }


    //region 处理接收的数组
    //获取当前ByteArray的总包数
    fun getTotalpkgs(bytes: ByteArray):Int{
        val totalbytes = bytes.slice(0..1)
        return bytestoint2bit(totalbytes.toByteArray())
    }

    //获取当前ByteArray的当前包数
    fun getCurpkgs(bytes: ByteArray):Int{
        val curbytes = bytes.slice(2..3)
        return bytestoint2bit(curbytes.toByteArray())
    }

    //获取当前ByteArray的实际数据包
    fun getByteArray(bytes: ByteArray):ByteArray{
        val curdata = bytes.slice(4 until bytes.size)
        return curdata.toByteArray()
    }
    //endregion

    //Int类型转ByteArray,范围是65536,只用两个字节
    private fun inttobytes2bit(num: Int): ByteArray {
        val byteArray = ByteArray(2)
        val lowH = ((num shr 8) and 0xff).toByte()
        val lowL = (num and 0xff).toByte()
        byteArray[0] = lowH
        byteArray[1] = lowL
        return byteArray
    }

    //ByteArray类型转Int,范围是65536,只用两个字节
    private fun bytestoint2bit(bytes: ByteArray): Int {
        val lowH = (bytes[0].toInt() shl 8)
        val lowl = bytes[1].toInt()

        val resint = if (lowH + lowl < 0) {
            65536 + lowH + lowl
        } else {
            lowH + lowl
        }
        return resint
    }

} 

分包发送数据

在原来的BlueToothBLEUtil中再加入分写发送的函数,每个包发送完后间隔50毫秒

接收再组装数据

还是BlueToothBLEUtil中,首先定义了一个HashTable,根据通讯的设备地址为key生成数组。

接收的当前包数据先调用前面写的函数获取到总包数,当前包数和当前包的数据,根据总包数定义总包数的数组,如果hashtable里面有直接获取到后更新对应的当前包数据,因为发送时是按顺序发送的,所以在接收的时候判断当前包数+1是否等于总包数,相等即说明所有的数据包接收完成。

当接收完后从hashtable中获取到Array<ByteArray>数组,然后将数组组合成一个ByteArray返回,并且在hasttable中删除即可。

而数据接收到处理在Server中就写在BluetoothGattServerCallback回调的onCharacteristicWriteRequest中

代码语言:javascript
复制
        //特征值写入回调
        override fun onCharacteristicWriteRequest(
            device: BluetoothDevice, requestId: Int,
            characteristic: BluetoothGattCharacteristic, preparedWrite: Boolean,
            responseNeeded: Boolean, offset: Int, value: ByteArray
        ) {
            super.onCharacteristicWriteRequest(
                device,
                requestId,
                characteristic,
                preparedWrite,
                responseNeeded,
                offset,
                value
            )
            Log.i("pkg","${requestId}  ${value}")
            //刷新该特征值
            characteristic.value = value
            // 响应客户端
            if (ActivityCompat.checkSelfPermission(
                    requireContext(),
                    Manifest.permission.BLUETOOTH_CONNECT
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                return
            }
//            mBluetoothGattServer.sendResponse(
//                device, requestId, BluetoothGatt.GATT_SUCCESS,
//                offset, value
//            )


            BlueToothBLEUtil.sendResponse(
                device,requestId,offset,value
            )

            //处理接收的数据
            val res = BlueToothBLEUtil.dealRecvByteArray(device.address, value)

            //接收完毕后进行数据处理
            if(res) {
                //获取接收完的数据
                val recvByteArray = BlueToothBLEUtil.getRecvByteArray(device.address)

                var readstr = String(recvByteArray)
                lifecycleScope.launch {
                    serverViewModel.serverIntent.send(
                        ServerIntent.Info(
                            "${device.address} 请求写入特征值:  UUID = ${characteristic.uuid} " +
                                    "写入值 = ${readstr}"
                        )
                    )

                    lifecycleScope.async {
                        //模拟数据处理,延迟100ms
                        delay(100)

                        val sb = StringBuilder()
                        for(i in 1..10){
                            sb.append("服务端收到了客户端发的消息,这里是返回的消息,第${i}条 ")
                        }

                        val readbytearray = sb.toString().toByteArray()
                        characteristic.value = readbytearray

                        //回复客户端,让客户端读取该特征新赋予的值,获取由服务端发送的数据
                        BlueToothBLEUtil.notifyCharacteristicChangedSplit(device, characteristic, readbytearray)
                    }
                }
            }
        }

相应的Client客户端在BluetoothGattCallback回调的onCharacteristicChanged中

代码语言:javascript
复制
        override fun onCharacteristicChanged(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            value: ByteArray
        ) {
            super.onCharacteristicChanged(gatt, characteristic, value)

            lifecycleScope.launch {
                //处理接收的数据
                val res = BlueToothBLEUtil.dealRecvByteArray(gatt.device.address, value)

                //接收完毕后进行数据处理
                if(res) {
                    //获取接收完的数据
                    val recvByteArray = BlueToothBLEUtil.getRecvByteArray(gatt.device.address)

                    val str = "返回消息:${String(recvByteArray)}"
                    connectViewModel.connectIntent.send(
                        ConnectIntent.CharacteristicNotify(str, characteristic)
                    )
                }

            }
        }

这样数据分包的发送和接收就实现了,效果就是文章开头的GIf视频中,源码还是上次的Demo中,已更新至当前版本了。

源码地址

https://github.com/Vaccae/AndroidBLEDemo.git

点击原文链接可以看到“码云”的源码地址

往期精彩回顾

Android BlueToothBLE入门(二)——设备的连接和通讯(附Demo源码地址)

Android BlueToothBLE入门(一)——低功耗蓝牙介绍

Android监听消息(二)——电话及短信监听

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-07-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微卡智享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 分包发送数据
  • 接收再组装数据
相关产品与服务
短信
腾讯云短信(Short Message Service,SMS)可为广大企业级用户提供稳定可靠,安全合规的短信触达服务。用户可快速接入,调用 API / SDK 或者通过控制台即可发送,支持发送验证码、通知类短信和营销短信。国内验证短信秒级触达,99%到达率;国际/港澳台短信覆盖全球200+国家/地区,全球多服务站点,稳定可靠。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档