以面向对象的思维,搭建Android与多ble蓝牙设备并发通讯小框架
此框架支持多种不同类型的ble设备,同时连接、收发数据,互不干扰。比如APP同时连两个LED蓝牙灯、两个手环、一个蓝牙加热器,当然连接单个ble设备,或者只连接一种ble设备同样适用本框架。
前言
小白请绕道百度,本文适合有一定Android、ble蓝牙、面向对象基础的同学进阶探讨,只讲关键技术点,细节自行脑补
看过很多蓝牙demo、开源库,没发现真正以面向对象的思维写的,把自己的一套框架开源出来,希望对看到的有缘人有用,特别是面向对象思维方面。不是说定义了类,就叫面向对象,希望你能领悟
(连接不可超过7个,极少数手机不可超过5个)
github源码:https://github.com/ruigeyun/Android-DualBle
转载引用请注明出处,尊重劳动者,让开源发扬光大! 原创--老凯瑞的博客园 https://www.cnblogs.com/littlecarry/p/11889982.html
以面向对象之名
一、理解业务需求:ble与Android APP通讯的基本内容
(一)蓝牙连接处理基本流程
如下图,来自 https://www.jianshu.com/p/1c42074b1430?from=groupmessage ,感谢作者
对上图补充:
0、app连接ble成功后,能获取ble的service uuid,而这个service uuid代表不同类型的设备。(扫描到设备后,大部分设备解析其广播数据也能获取service uuid)
1、APP与ble可以通讯后,APP发送认证密码给ble,认证通过后,ble同步自身信息给APP,最终才进入正常业务交互
2、APP与ble,断连后,自动重连
3、APP可主动断开ble,之后可主动连接ble回来
4、APP可删除ble,之后可再扫描连接回来
5、接收到的蓝牙数据包,需要把数据缓存后拼接成完整数据包,极有可能一次收到的数据包不是完整的
6、蓝牙数据分发到对应的业务接口
(二)Android APP与蓝牙多设备连接注意的点:
1、设备一个一个连,连接成功一个再一个,如果同时连多个,可能一个都连不上。具体原因没有深究
2、如果一个设备被你连过,然后一系列操作后,无法再扫描到,用其他工具APP也扫描不到,说明这个设备被你连着,没有彻底的释放掉!如何完全释放ble,具体看源码,其中部分我也是参考了网上著名的蓝牙框架 fastble:https://www.jianshu.com/p/795bb0a08beb ,感谢作者
3、对APP对ble的每一步操作间,必须加延时,否则会有意想不到的问题。具体看源码
4、ble被断开后,必须延时1-2秒,再去连接他(不通过扫描直接连的情况),否则会有意想不到的问题
二、分析整个系统:
架构,是模块及模块之间的交互
(一)整个蓝牙业务系统分成的模块:APP与ble连接交互模块、APP与ble数据交互模块、APP对所有ble整合管理模块、其他能动辅助模块
1、APP与ble连接的交互:(1)APP扫描ble,必定有一个负责扫描的类;(2)扫描连接所有的ble,需要一个类专门负责连接的类;(3)ble自身的各种状态以及数据交互,必定就有个ble类来描述这些自身属性;(3)ble连接成功后,密码验证、数据同步、掉线重连,这些ble必须自发的行为,需要一个类来描述这些蓝牙设备自发业务;
2、APP与ble数据交互:(1)一个格式完整的数据包,以及这个数据包属于哪个ble,必须由一个数据包类描述;(2)接收到数据,对数据拼包、过滤得到一个有效包的过程,需要一个缓存类描述;(3)完整的数据包最终对外分发,需要一个数据分发类描述;
3、APP与ble整合管理:(1)统一调配各个ble间的关系(连接、断开、删除,发数据等),需要一个调配服务中心类描述;(2)对整个蓝牙框架的管理,扫描、连接、数据处理等整合起来,需要一个框架管理类描述。并且这个类作为此框架对外的门面,所有对外的操作,都得通过它,达到隐藏这个框架的其他复杂细节的目的
4、其他能动辅助:各种工具、日志调试
(二)最终提取到的对象:
1、APP与ble建立连接:扫描器,连接器,ble蓝牙属性设备,蓝牙设备自发业务(重连、认证、同步)
2、APP与ble数据交互:接收数据拼包缓存,数据包,数据分发器
3、APP与ble整合管理:设备间调配服务中心,框架管理类
4、其他能动辅助:工具、日志
这里最关键的一个对象设计:ble蓝牙属性设备(BLELogicDevice),每个蓝牙设备提炼成一个对象,APP每连接一个设备,就开辟一个此对象。每个对象分配一个mDeviceId,这个mDeviceId非常重要,它标志每一个蓝牙设备,一般通过这个mDeviceId来操作蓝牙设备。每个对象都有 BluetoothGattCallback 数据交互接口,这样每个对象跟自己对应的ble设备单独交互,互不相干。从一大堆扫描、回调、管理中解耦出来。每个设备对象从回调方法onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)拿到数据,把数据缓存到一个自身的数据缓存区(每个设备对象都有一个缓存区对象),在缓存中拼接成数据包,数据都携带mDeviceId作为标志,对外分发。
另一个关键对象:设备间调配服务中心(BLEServerCentral),所有设备挂在其链表中,其负责维护各个设备对象的状态(连接、断开、删除等),控制APP与各设备数据交
三、设计小结
1、面向对象的思维:需求分析、系统构思、细化流程、提炼对象、对象整合,最终把整个系统完整描述清楚。根据自己的设计粒度,每一个类都去描绘一个事物,负有单一的职责,这是建立一个类的最基本原则。不是随意定义了类,然后一大堆if else逻辑,面向过程的思维解决问题。如果你的代码中,if else if 超过三层,说明你的代码耦合度过高了,需要拆分整合了
2、以上只是写了关键的设计思路,源码有很多拓展的地方,有缘人可以自己阅读,代码其实没多少行,慢慢仔细看一下就明白了,不懂的可以在博客留言,我尽可能答复
3、我把这个module做成了库,自己运行下makeClockJar,就可以导出jar包,接口如何使用参考源码
4、源码蓝牙接收特征配置成通知方式,其他方式自行拓展。
5、要添加很多新的行为,其实是很容易拓展的,比如添加一个配置特征,专门配置蓝牙参数的。你读得懂源码,很容易添加
四、库的用法
demo里面,有具体的栗子,仔细阅读下,很多注释的,应该容易理解
1、建立自己的蓝牙设备对象,蓝牙对象必须继承BLEAppDevice,自定义一个唯一的设备类型id(DEVICE_TYPE_ID),至少包含服务、发送、接收三种uuid,以及重写三个抽象方法,把对应三种uuid分别写进去。构造方法必须如下的方式,固定两个参数,并且调用父类的构造方法。demo中有三种蓝牙设备,手环(BraceletDevice),蓝牙控制led的设备(LedDevice)、蓝牙控制加热器设的备(HeaterDevice),,添加自己的新特征,如led灯颜色,heater定时时间。
public class LedDevice extends BLEAppDevice {
private final String TAG = "BLELedDevice"; public static final Integer DEVICE_TYPE_ID = 1002;
public static final UUID SERVICE_UUID = UUID.fromString("0000ff**-0000-1000-8000-00805f9b34fb");
private final UUID RX_CHAR_UUID = UUID.fromString("0000ff**-0000-1000-8000-00805f9b34fb");
private final UUID TX_CHAR_UUID = UUID.fromString("0000ff**-0000-1000-8000-00805f9b34fb"); @Override
public UUID getServiceUUID() {
return SERVICE_UUID;
}
@Override
public UUID getRxUUID() {
return RX_CHAR_UUID;
}
@Override
public UUID getTxUUID() {
return TX_CHAR_UUID;
} public String dualColor = "";
public String hardwareVersion = "";
public int powerState = 0;
public String nickname = ""; public LedDevice(BluetoothDevice device, DataParserAdapter adapter) {
super(device, adapter); }
}
2、建立自己蓝牙设备的数据包结构对象(可选),继承DataParserAdapter,重写相应方法。框架内部根据你定义的结构,自动帮你把蓝牙回应的数据包提炼出来(主要是处理断包、粘包问题),最终的数据包通过onDeviceRespSpliceData(BLEPacket message)方法回调给你。当然你也可以不用架构的处理算法,自己拼包,在DataCircularBuffer 类中,pushOriginalDataToBuffer(byte[] originalData)方法,是各个蓝牙设备数据推过来的入口,在这里接入自己的算法。
如果不建立DataParserAdapter对象,则默认为null,蓝牙回应的数据,通过onDevicesRespOriginalData(BLEPacket message) 方法回调给你。
3、建立自己的蓝牙管理对象,继承BLEBaseManager,重写必要的、可选的方法。蓝牙的各种信息交换,都是通过这个类回调给你。很重要!仔细阅读BLEServerListener接口里的方法说明,重写自己需要的方法。
(1)必须重写 onGetDevicesServiceUUID()方法,把自己定义的设备类型ID和设备的service uuid,用map写进去。框架连接上设备后,读取设备的service uuid,根据这个map分辨出是那种类型的设备。
(2)必须重写BLEAppDevice onCreateDevice(BluetoothDevice bluetoothDevice, int deviceType)方法,框架识别设备类型后,回调给你,你根据设备类型,创建设备对象实例。
(3)onAddScanDevice(BluetoothDevice bluetoothDevice)方法,框架扫描到设备,就会回调这个方法。
(4)onAddNewDevice(BLEAppDevice device)方法,框架连接成功一个设备,各种状态完备后,回调这个方法。
这些方法在BLEServerListener接口都有详细说明
public class BLEManager extends BLEBaseManager { private final String TAG = "BLEManager"; private static BLEManager instance = new BLEManager();
public static BLEManager getInstance() {
return instance;
} @Override
public HashMap<Integer, UUID> onGetDevicesServiceUUID() {
HashMap<Integer, UUID> map = new HashMap();
map.put(HeaterDevice.DEVICE_TYPE_ID, HeaterDevice.SERVICE_UUID);
map.put(LedDevice.DEVICE_TYPE_ID, LedDevice.SERVICE_UUID); return map;
} @Override
public void onScanOver() {
Log.w(TAG, "onScanOve。。");
} @Override
public BLEAppDevice onCreateDevice(BluetoothDevice bluetoothDevice, int deviceType) {
if (deviceType == HeaterDevice.DEVICE_TYPE_ID) {
//数据包解析适配器为null,蓝牙设备回应的数据在 onDevicesRespOriginalData(BLEPacket message)
return new HeaterDevice(bluetoothDevice, null);
}
else if (deviceType == LedDevice.DEVICE_TYPE_ID) {
// 设置了数据包解析适配器,数据回调在 onDeviceRespSpliceData(BLEPacket message)
return new LedDevice(bluetoothDevice, new LedDataAdapter());
}
else {
return null;
}
} @Override
public void onAddScanDevice(BluetoothDevice bluetoothDevice){
EventBus.getDefault().post(new AddScanDeviceEvent(bluetoothDevice));
} @Override
public void onConnectUnTypeDevice(BluetoothDevice bluetoothDevice, int type) {
EventBus.getDefault().post(new ConnectUnTypeDeviceEvent(bluetoothDevice, type));
} @Override
public void onConnectDevice(BLEAppDevice device, int type){
EventBus.getDefault().post(new ConnectDeviceEvent(device, type));
} @Override
public void onAddNewDevice(BLEAppDevice device){
EventBus.getDefault().post(new AddNewDeviceEvent(device));
}
@Override
public void onUpdateDeviceInfo(BLEAppDevice device) {
EventBus.getDefault().post(new updateDeviceInfoEvent(device));
}
@Override
public void onDeviceSendResult(String result){
EventBus.getDefault().post(new BleSendResultEvent(result));
} @Override
public void onDeviceRespSpliceData(BLEPacket message) {
LogUtil.i(TAG, "onDeviceRespSpliceDat: [" + BytesUtil.BytesToHexStringPrintf(message.bleData) + "] bleId: " + message.bleId);
// DataManager.getInstance().DecodeRespData(message.bleData, message.bleId);
} @Override
public void onDevicesRespOriginalData(BLEPacket message) {
LogUtil.v(TAG, "onDevicesRespOriginalDat: [" + BytesUtil.BytesToHexStringPrintf(message.bleData) + "] bleId: " + message.bleId);
} }
建立三个对象,就可以使用此框架了,如此简单!
4、初始化蓝牙框架,APP获得蓝牙相应权限后,调用BLEBaseManager的 initBle(..)方法初始化蓝牙。见demo
注意
1、多设备同时工作,必定引起并发竞争问题,自己要做好同步!demo只是使用方法,没有处理那些问题
2、此框架蓝牙接收特征配置成通知方式,其他方式自行拓展,工作太忙没有太多时间去整理,见谅!
demo运行起来的效果
更新日志
2020-3-12 更新
1、通过广播解析到蓝牙设备的server uuid,从而确定该设备是哪种类型,创建设备,连接设备。之前是连接成功后,才获取service uuid,然后确定其设备类型,这样处理让代码逻辑比较臃肿
2、如果你的设备广播中没有service uuid,抱歉,设备被忽略。大部分设备都是有的
3、每次只能连接一个设备,简化连接流程
4、修改部分变量、注释,增加可读性
以面向对象的思维,搭建Android与多ble蓝牙设备并发通讯小框架的更多相关文章
- 搭建Android与多ble蓝牙设备并发通讯小框架
此框架支持多种不同类型的ble设备,同时连接.收发数据,互不干扰.比如APP同时连两个LED蓝牙灯.两个手环.一个蓝牙加热器,当然连接单个ble设备,或者只连接一种ble设备同样适用本框架. 前言 小 ...
- 搭建Android开发环境附图详解+模拟器安装(JDK+Eclipse+SDK+ADT)
——搭建android开发环境的方式有多种,比如:JDK+Eclipse+SDK+ADT或者JDK+Eclipse+捆绑好的AndroidSDK或者Android Studio. Google 决定将 ...
- mac os 下搭建android开发环境
mac os 下搭建android开发环境 周银辉 mac os 下搭建android环境比较方便, 如下几个步骤: 1,安装jdk 先搞清楚自己是否已经安装,在命令行下:java -version, ...
- 【Android自学日记】搭建Android开发环境
搭建Android应用开发环境所需工具 1_> JDK(JAVA Development)推荐使用6.0以后版本 配置环境变量(以下是环境变量的具体内容及介绍) ================ ...
- 使用IntelliJ IDEA 13搭建Android集成开发环境(图文教程)
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/ ...
- 搭建Android底层开发环境
为了开发linux驱动方便些,我们一般将linux作为Android的开发环境,那么就需要搭建Android的开发环境,下面是一些搭建Android底层时的心得: (1)安装JDK:除了普遍使用的下载 ...
- 在Eclipse下搭建Android开发环境教程
我们昨天向各位介绍了<在NetBeans上搭建Android SDK环境>,前不久也介绍过<在MyEclipse 8.6上搭建Android开发环境>, 都受到了读者的欢迎.但 ...
- 第二章 搭建Android开发环境
这一章为我们讲解了如何搭建Android开发环境. 首先要了解的是Android底层开发需要哪些工具:搭建android应用程序开发环境.android NDK开发环境和交叉编译环境,前两个用来测试L ...
- 搭建Android开发环境简要步骤
(一)安装JDK JDK下载地址 http://www.oracle.com/technetwork/java/javase/downloads/index.html 在Linux终端输入如下命令,设 ...
随机推荐
- [考试反思]1007csp-s模拟测试63:朦胧
别找了原来没有写过叫<朦胧>的我check过了.(慌的一匹) 总算是比较早的改完了一套题. 但是考的是个啥啊... 前两道题都很卡常导致我想到了正解但是都放弃了. 2e8的复杂度怎么可能能 ...
- 测试面试题集-测试用例设计:登录、购物车、QQ收藏表情、转账、充值、提现
以下内容首发于微信公众号[ITester软件测试小栈]: 测试面试题集-2.测试用例设计 大家好 我是coco小锦鲤 上周五给大家分享了测试基础理论题 这个周五给大家分享测试用例设计题 测试用例的考察 ...
- 使用Typescript重构axios(二)——项目起手,跑通流程
0.系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三)- ...
- 『题解』Codeforces1142B Lynyrd Skynyrd
更好的阅读体验 Portal Portal1: Codeforces Portal2: Luogu Description Recently Lynyrd and Skynyrd went to a ...
- 『题解』LibreOJ6277 数列分块入门 1
更好的阅读体验 Portal Portal1: LibreOJ Description 给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间加法,单点查值. Input 第一行输入一个数字 ...
- php微信卡券logo上传方法
php微信卡券logo上传方法 <pre> $xiangmupath = $this->getxiangmupath(); $logo = $xiangmupath . '/imag ...
- supervisord进程管理
一:简介 supervisord是一个进程管理工具,提供web页面管理,能对进程进行自动重启等操作. 优点: - 可以将非后台运行程序后台运行 - 自动监控,重启进程 缺点: - 不能管理后台运行程序 ...
- JavaScript with Image:创建缩略图
当图片很大,直接把图片从Server下载到浏览器上看是一种很不明智的做法,浪费了服务器的资源,网络带宽和客户端的资源.所以,通常Server和Client之间会传输缩略图,只有当Client请求某张图 ...
- Jmeter使用代理录制web
Jmeter有录制功能,录制HTTPs需要增加一个证书配置,录制步骤如下: 1.打开jmeter,添加线程组.线程组右键,逻辑控制器>录制控制器 工作台 右键 非测试元件 >HTTP代理服 ...
- opencv之常用还是忘,那咋办嘛
相机标定:https://blog.csdn.net/y2c58s43d69g8h7G_g/article/details/97239418 畸变参数个数要是镜头太凸的话,就像鱼眼相机和哨兵150视角 ...