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

学更好的别人,

做更好的自己。

------《微卡智享》

本文长度为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实现的,直接贴上来。

kotlin 复制代码
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中

kotlin 复制代码
//特征值写入回调
        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中

kotlin 复制代码
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监听消息(二)------电话及短信监听

相关推荐
长亭外的少年2 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿4 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神5 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛5 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法6 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter7 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快8 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl9 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
麦田里的守望者江9 小时前
KMP 中的 expect 和 actual 声明
android·ios·kotlin
Dnelic-9 小时前
解决 Android 单元测试 No tests found for given includes:
android·junit·单元测试·问题记录·自学笔记