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

news2024/11/27 0:50:58

学更好的别人,

做更好的自己。

——《微卡智享》

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监听消息(二)——电话及短信监听

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/769999.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

qt 32位编译 内存溢出 无法 运行在win7 32位

项目在 编译32位系统 内存溢出 设置成了x64 但是 最后在xp32位系统运行提示 在下载了n个dll之后发现这种状况无穷无尽&#xff0c;后来在查阅资料时发现可以直接打开qt安装目录下的“vcredist”文件夹&#xff0c;将对应位数的程序拷到win7电脑上&#xff0c;直接运行&…

优化营商环境:构建智能营销平台,助力企业经营发展

对于企业来说&#xff0c;没有了客户&#xff0c;就像身体没有了血液&#xff0c;将失去生命力和活力&#xff0c;续存难发展更难。区域产业又是由一个个企业集聚而形成&#xff0c;企业的成败也就决定着区域产业的兴衰。 在当今竞争激烈的商业环境中&#xff0c;传统的销售手段…

基于python的爬虫实现

定义 爬虫&#xff08;Web crawler&#xff09;&#xff0c;也被称为网络爬虫、网络蜘蛛或网络机器人&#xff0c;是一种自动化程序&#xff0c;用于浏览互联网并收集网页内容。 基本原理 爬虫的工作原理是通过发送HTTP请求从网页服务器获取网页的内容&#xff0c;然后解析网…

何时使用Windbg静态分析?何时使用Windbg动态调试?

目录 1、概述 2、使用Windbg静态分析dump文件 2.1、异常捕获模块自动生成dump文件 2.2、从Windows任务管理器中导出dump文件 2.3、从正在动态调试的Windbg中使用命令导出dump文件 2.4、使用Windbg静态分析dump文件的一般步骤 3、使用Windbg动态调试目标进程 3.1、程序发…

Win10 配置NDK安装2023.7.19版本

NDK安装流程 1. 下载&#xff1a;2. 安装&#xff1a;3. 测试&#xff1a; 在大多数情况下&#xff0c;使用 Android SDK 管理器安装 NDK 会更轻松。本文单独安装NDK&#xff0c;但后续也可以使用管理器进行管理。 1. 下载&#xff1a; 地址 Fig.1 最新稳定版本 2. 安装&…

精益生产的五大管理工具:提升效率,降低成本!

在制造业的世界里&#xff0c;精益生产是一种以追求在制造过程的各个方面减少浪费为中心的方法。为了实现这一目标&#xff0c;有几个经常使用的管理工具。这些工具使制造商能够识别和消除生产过程中任何效率低下或浪费的资源。本文将讨论精益生产中使用的一些关键管理工具&…

Android 进程与进程之间的通信--Messager 详细教程,两个app实现

Messenger是一种轻量级的IPC方案,它的底层实现其实就是AIDL.跨进程通信使用Messenger时,Messenger会将所有服务调用加入队列,然后服务端那边一次处理一个调用,不会存在同时调用的情况.而AIDL则可能是多个调用同时执行,必须处理多线程问 步骤详情 一、服务端 public class MyM…

如何使用DiskPart命令行格式化分区?

想要格式化磁盘分区&#xff0c;您可以使用磁盘管理工具&#xff0c;或在Windows文件资源管理器中右键单击驱动器并选择“格式化”。如果您更想使用命令行来格式化磁盘&#xff0c;那么Windows自带的DiskPart将是首选。 DiskPart有很多优点&#xff0c;例如&#xff0c;如果您想…

《无畏契约》游戏分析

文章目录 介绍游戏继承性《守望先锋》游戏美术对比游戏机制对比 《CSGO》游戏美术对比游戏机制对比 《英雄联盟》游戏美术对比游戏机制对比 《无畏契约》的优点《无畏契约》的缺点该游戏值得学习之处总结 介绍 《无畏契约&#xff08;VALORANT&#xff09;》是一款由拳头游戏&…

Vue组件的基本使用

Vue中想用组件总共分几步&#xff1a; 1.创建组件 2.注册组件 3.使用组件 1.创建组件 //1.创建school组件&#xff0c;这里的school并不是组件名&#xff0c;只是一个中转变量名const school Vue.extend({// el:#root, //组件定时&#xff0c;一定不要写el配置项&#xff0…

[python][深度学习]diffusers加载模型每次都联网如何离线加载

diffusers模块很好用&#xff0c;唯一缺点就是没把离线加载模型做好。一般都是联网自动下载后&#xff0c;以后离线加载一下就行了&#xff0c;没想到每次都远程下载一堆东西而且经常容易断网。因此研究离线加载势在必行&#xff0c;经过N次下载之后终于成功下载模型 下载后发现…

【电路原理学习笔记】第5章:串联电路:5.1 电阻的串联

第5章&#xff1a;串联电路 5.1 电阻的串联 图5-1a展示了申联于A点和B点之间的2个电阻。图5-1b和图5-1c分别展示了3和4个电阻相串联的情况。当然&#xff0c;串联电路中可以有任意数量的电阻。 对于图51所示各电路&#xff0c;当电压源连接在A点和B点之间时&#xff0c;电流…

【数据结构】链表及无头单向非循环链表实现

目录 1.顺序表的问题 2.链表的概念、结构及分类 3.无头单向非循环链表实现 3.1创建节点 3.2头插数据 3.3头删数据 3.4尾插 3.5尾删 3.6链表销毁 3.7查找一个元素 3.8在pos之前插入 3.9在pos之后插入 3.10删除pos位置 3.11删除pos之后的位置 1.顺序表的问题 顺…

第一百一十天学习记录:C++实战:自我设计用单链表、多态和文件操作写一个公会人员管理系统

实现程序界面展示&#xff1a; 主界面&#xff1a; 程序输入非正常字符情况保护 添加会员信息 删除会员信息 查找会员信息 变更会员会阶 显示所有会员 排序会员信息 查看种族职业 保存信息的txt文件 工程文件目录 main.cpp代码 #include "allmember.h" #include &q…

2023年7月广州/深圳软考中级系统集成项目管理工程师招生

系统集成项目管理工程师是全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff08;简称软考&#xff09;项目之一&#xff0c;是由国家人力资源和社会保障部、工业和信息化部共同组织的国家级考试&#xff0c;既属于国家职业资格考试&#xff0c;又是职…

FFmpeg 命令行实现居中高清上下模糊播放效果

FFmpeg 命令行实现居中高清上下模糊播放效果。 1、16:9 的横屏原视频&#xff0c;以 16:9 竖屏上下模糊播放 以该效果播放视频的命令如下&#xff1a; ffplay -i horizontal_test_video_169.mp4 -vf \ "split[a][b]; \ [a]crop(ih/16*9):ih,scaleiw/10:-1,gblursigma5…

GreatSQL通过错误日志信息判断数据库实例是如何关闭的

背景概述 在一次客户的数据库实例连接不上了&#xff0c;需要我们排查一下原因&#xff0c;通过查看数据库实例进程已经不存在了&#xff0c;在错误日志中没有发现其他报错信息&#xff0c;发现有shutdown的字样出现&#xff0c;怀疑是某个用户手动关闭了实例。我们通过以下测…

华为认证的题库,不仅能考试,还能帮你提升技能

1、OSPF协议在哪种状态下确定DD报文的主从关系&#xff1f; A. 2-way B.Exchange C. ExStart D. Full 2、在VRP操作系统中&#xff0c;如何进入OSPF区域0的视图&#xff1f;A. [Huawei-ospf-1]area 0 B.[Huawei]ospf area 0 C. [Huawei-ospf-1]area 0 enable D. [Huawe…

珀莱雅、华熙生物、贝泰妮、丸美股份一季报PK,谁是“卷王”?

国货美妆有多“卷”&#xff1f; 618落幕&#xff0c;各大电商平台公布了美妆销售数据。据统计&#xff0c;618期间天猫、京东、抖音、快手四大平台美妆销售总额超过610亿元。 近日&#xff0c;四家国货美妆企业&#xff0c;珀莱雅、华熙生物、贝泰妮、丸美股份分别公布了202…

经济和行政手段使双高企业降低能耗总量和能耗强度,提高能源利用效率-安科瑞黄安南

摘要 2022年6月29日工信部、发改委、财政部、生态环境部、国资委、市场监管总局六部门联合下发《关于印发工业能效提升行动计划的通知》&#xff08;工信部联节〔2022〕76号&#xff0c;以下简称《行动计划》&#xff09;&#xff0c;主要目的是为了提高工业领域能源利用效率&…