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

学更好的别人,

做更好的自己。

——《微卡智享》

b7f449ca84565b185d2ab00e377c53cb.jpeg

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

前言

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

af3ebcd3051504a95c4a3ca27c8fb14a.png

实现效果

a60e46f91bcd2ec456e69d55c60f5643.gif

1d7c4596777bf2e2bd625426290178b8.jpeg

2429f0b9147023bb799a5fe9a3ecbfa9.jpeg

代码实现

9dda727b556660f26b410dc13f8c424f.png

微卡智享

01

修改MTU值

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

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

89214a46188ec19977f2c3d929001299.png

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

7f2374594987ddda5c62e51fc9450b43.png

4be3e8b4e62dcaafd65c7e9b46d0a76f.png

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

02

分包发送数据和接收处理

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

b2341d51c2bd24e8221e0f20e2aae98a.png

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

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

9303dd81bbd8bc3dcb1dd3b1eb06918b.png

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

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

f2d5685688e6510faa9c8bb7fb858bf7.png

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

9a12504573b20304c3d68737b70cd49c.png

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

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毫秒

a9282d3ea8a7f30deab55c7df73439fc.png

接收再组装数据

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

9783a4a3f13e0eb5d113189f75db13e9.png

97c01578bbf04c9e12fe51df973c0d7e.png

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

7aad7e737bed64aecc53af0644aa4d35.png

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

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

5d2c8832aebcb9afeb1ba0b461f66b3a.png

//特征值写入回调
        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中

501f35a1e21e1723d14df84456f0d8fb.png

559c0af66a902693bab76b822cd62fa2.png

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

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

961f1393510d44f9986a796579e18a63.png

064108909f75b3c0c7afebcac253a58f.png

往期精彩回顾

3f58cf14ca8e6d28243710a304a5512b.jpeg

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

 

d8b39ff337ca214300f3363739a592ce.jpeg

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

 

80f1ea58372f159f491fb4bfa3bff062.jpeg

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

猜你喜欢

转载自blog.csdn.net/Vaccae/article/details/131778205