Android BlueToothBLE入门(三)

您所在的位置:网站首页 手机蓝牙传输有问题 Android BlueToothBLE入门(三)

Android BlueToothBLE入门(三)

2024-07-13 17:58| 来源: 网络整理| 查看: 265

学更好的别人,

做更好的自己。

——《微卡智享》

本文长度为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中的slice进行获取,截取后再进行转换即可获取总包数和当前包数。

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

代码语言:javascript复制package vac.test.bluetoothbledemo.repository object BLEByteArrayUtil { //计算发送的数据库生成数组 fun calcSendbyteArray(byteArray: ByteArray): Array { //根据当前的传输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(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返回,并且在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监听消息(二)——电话及短信监听



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3