Android经典蓝牙与低功耗蓝牙开发相关知识

1、需要知道的几个关键关键词

1.1、蓝牙通信中是使用的UUID是什么?

简单的说这个UUID是蓝牙通信必须要用的唯一识别号。
复杂的说是服务记录的UUID是用于查找RFCOMM通道。
那什么是RFCOMM?它是为了兼容传统的串口应用,同时取代有线的通信方式,蓝牙协议栈需要提供与有线串口一致的通信接口而开发出的协议。

1.2、经典蓝牙

经典蓝牙泛指支持蓝牙协议4.0以下的蓝牙,一般用于连续流式传输音频和数据量比较大的传输,例如音乐、语音、打印机等。

1.3、低功耗蓝牙

英文名称:Bluetooth Low Energy,简称(ble),低功耗蓝牙指支持蓝牙协议4.0或更高的版本,它不向后兼容4.0之前的经典蓝牙协议,目前已经迭代到蓝牙5.3版本,主打低功耗(使用一个纽扣电池最起码都能工作好几个月),低延迟(几毫秒级别的响应);应用于实时性要求比较高,但是低速率,低功耗的场景,如鼠标键盘、智能家居、智能穿戴这类不需要大数据量交互的场景中,非常适合物联网应用。

2、如何获取不同设备类型的UUID

英文单词:Universally Unique Identifier,用于标识蓝牙服务以及通讯特征访问属性,蓝牙技术联盟SIG定义UUID 共用了一个基本的UUID ,这些共用的UUID大致有下面几类。注意,设备厂商可以使用共用的UUID,也可以自定义,如果自定义需要向开发者提供。

2.1、手机类设备

分为两种,一种是安全的连接,一种是不安全的连接。

(1)安全的连接
对应的UUID是:fa87c0d0-afac-11de-8a39-0800200c9a66
此UUID将作为此函数的参数:createRfcommSocketToServiceRecord

(2)不安全的连接
是因为通信通道将没有经过身份验证的链接密钥,即它将受到中间人攻击。
对应的UUID是:8ce255c0-200a-11e0-ac64-0800200c9a66
此UUID将作为此函数的参数:createInsecureRfcommSocketToServiceRecord

2.2、串口设备

如:小型蓝牙打印机
UUID1:0000180a-0000-1000-8000-00805f9b34fb

UUID2:00001101-0000-1000-8000-00805F9B34FB

2.3、(BLE)低功耗蓝牙设备

如:一些物联网终端设备。
低功耗设备需要的UUID则更为不同也更细分出来,需要用到三个不同的UUID,即用于服务连接的UUID、读取数据的UUID、写入数据的UUID,这些一般对接的蓝牙设备厂商也会提供此三项值。
//服务连接的UUID
SERVICE_UUID = “0000ffb0-0000-1000-8000-00805f9b34fb”;
//读使用的UUID
READ_UUID = “0000ffb2-0000-1000-8000-00805f9b34fb”;
//写使用的UUID
WRITE_UUID = “0000ffb1-0000-1000-8000-00805f9b34fb”;

当然我们也可以借助"BLE调试助手"这款APP来连接蓝牙设备,并查看对应的三项UUID,如下图所示:
在这里插入图片描述

3、(BLE)低功耗蓝牙与经典蓝牙开发方式区别

虽然它们在开发时,都使用了android.bluetooth.BluetoothDevice这个类,但是却使用了不同的连接函数,如下。

3.1、低功耗蓝牙:

Added in API level 18

BluetoothDevice.connectGatt(context, false, bluetoothGattCallback)

3.2、经典蓝牙:

Added in API level 5,有两种连接方式,
第一种方式:

BluetoothSocket socket = BluetoothDevice.createRfcommSocketToServiceRecord(GlobalDef.BT_UUID);
socket.connect();

第二种方式:

BluetoothSocket socket = BluetoothDevice.createInsecureRfcommSocketToServiceRecord(GlobalDef.BT_UUID);
socket.connect();

不同的连接函数,导致了开发过程中使用的蓝牙API也已完全不同。所以,寻找Demo时一定要区分是低功耗蓝牙开发还是经典蓝牙开发,不然很可能出现,用传统的蓝牙的开发方式去连接低功耗蓝牙设备导致连接失败等一些列诡异的问题。

4、开发过程中的注意事项

4.1、(BLE)低功耗蓝牙开发时,发送数据前务必要做忙碌检测。

现象:当蓝牙设备处于忙碌状态时,发送数据此项操作将会失败。

为什么蓝牙设备会处于忙碌状态呢?

查看源码发现了一些线索,在android.bluetooth.BluetoothGatt类的writeCharacteristic函数中有一段同步的代码,synchronized锁住了mDeviceBusy变量,如果写入操作太快太猛,蓝牙设备某项任务还未执行完,将可能会导致此处直接返回false,也就是写入失败,不再继续往下执行。

package android.bluetooth;
public final class BluetoothGatt implements BluetoothProfile {
...
private Boolean mDeviceBusy = false;
...
	public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
		...
		synchronized (mDeviceBusy) {
            if (mDeviceBusy) return false;
            mDeviceBusy = true;
        }
 	 	...
   	 	mService.writeCharacteristic(mClientIf, device.getAddress(),
    	...
	}
}

那么如何去判定忙碌状态呢?

	//(检查蓝牙设备是否处于忙碌状态)此函数执行的超时时间
    private static long WAIT_CHECK_DEVICE_BUSY_TIME = 2000;

    /**
     * 检查蓝牙设备是否处于忙碌状态
     * @param mBluetoothGatt
     */
    private void checkDeviceBusyStatus(BluetoothGatt mBluetoothGatt) {
        boolean isBusy;
        long startTime = System.currentTimeMillis();
        try {
            while (System.currentTimeMillis() - startTime < WAIT_CHECK_DEVICE_BUSY_TIME) {
                isBusy = (boolean) readField(mBluetoothGatt, "mDeviceBusy");
                if (isBusy) {
                	//如果蓝牙设备处于忙碌状态,则休息10毫秒
                    Thread.sleep(10);
                    Log.d(TAG, "设备是否忙碌状态:" + isBusy);
                } else {
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 通过反射的方式去获取对象私有属性的值
     */
    private Object readField(Object obj, String fieldName) throws IllegalAccessException, NoSuchFieldException {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        return field.get(obj);
    }

在写入数据前调用checkDeviceBusyStatus函数:

       //检查设备是否忙碌的状态
  	checkDeviceBusyStatus(mBluetoothGatt);
  	bool writeResult= mBluetoothGatt.writeCharacteristic(writeCharacteristic);

4.2、数据写入需要关注是否需要远程设备响应

	writeCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);

WRITE_TYPE_DEFAULT 写入数据后默认远程设备有响应;
WRITE_TYPE_NO_RESPONSE 写入数据后,无需远程设备响应,速度更快。

4.3、蓝牙连接失败的常见原因

(1)蓝牙开发的版本不对
一定要向设备厂商明确使用的蓝牙协议的版本号是多少,以决定采用那种开发方式,采用(BLE)低功耗蓝牙开发的方式去连接低功耗的蓝牙设备,经典蓝牙设备开发的方式连接经典蓝牙设备,如果要想都兼容 ,可通过蓝牙版本区分来调用对应的版本的代码。

(2)UUID不正确
这类问题最常见,明确UUID是使用的共用的UUID还是自定义的UUID,如果设备厂商自定义了UUID,那么就让设备厂商提供,UUID不正确将会导致一直连接失败。

参考:
[1]:低功耗蓝牙(BLE)你入门了吗
[2]:蓝牙服务UUID






原创不易,求个关注。

在这里插入图片描述

微信公众号:一粒尘埃的漫旅
里面有很多想对大家说的话,就像和朋友聊聊天。
写代码,做设计,聊生活,聊工作,聊职场。
我见到的世界是什么样子的?
搜索关注我吧。

公众号与博客的内容不同。

### 纯虚函数抽象类的优势及应用场景 #### 1. **强制派生类实现特定功能** 纯虚函数的存在使得基类能够定义一个接口,而具体的实现由派生类负责。这种方式可以确保所有派生类都提供了某种特定的行为,从而增强了代码的一致性可靠性[^3]。 例如: ```cpp class Shape { public: virtual double area() const = 0; // 纯虚函数 }; class Circle : public Shape { private: double radius; public: Circle(double r) : radius(r) {} double area() const override { return 3.14 * radius * radius; } }; ``` 在此示例中,`Shape` 是一个抽象类,它规定了所有的具体形状(如圆形、矩形等)必须实现 `area()` 方法。这有助于开发者遵循统一的设计规范[^5]。 --- #### 2. **防止实例化抽象概念** 抽象类本身代表了一种高层次的通用概念,而不是可以直接使用的具体对象。通过设置至少一个纯虚函数,编译器会阻止创建该类的实例,只允许其作为其他更具体类型的基类存在[^4]。 比如,在图形绘制系统里,“几何体”可能只是一个理论上的分类名称,并不适合单独作为一个实体出现;相反,像三角形或者椭圆这样的子类别才是有意义的实际单位[^1]。 --- #### 3. **促进多态性与灵活性** 借助于指向不同派生类对象的指针或引用调用同一个名字的方法时,实际执行的是对应的具体版本的操作——这就是所谓的运行期多态现象。由于抽象基类包含了若干待定细节的部分,所以非常适合用来构建灵活可扩展的应用框架[^2]。 设想这样一个场景:我们需要编写一段程序来处理各种文件读取任务。如果采用普通的非虚拟继承模型,则每次新增一种新的文件格式处理器就需要修改现有源码甚至重构整个体系结构。但如果运用到带有纯虚函数机制的抽象基类的话,则只需简单添加一个新的派生类即可满足需求变化的要求。 --- #### 4. **简化接口设计** 对于那些仅仅希望暴露某些核心服务能力却不关心内部状态表示的语言组件来说,利用全是由纯虚构成的所谓“接口类”,往往可以让整体设计方案变得更加清晰简洁明了[^5]。 举个简单的例子: ```cpp class IStreamable { public: virtual void write(const char*) = 0; virtual size_t read(char*, size_t) = 0; protected: ~IStreamable() {} // 防止外部直接销毁接口类 }; ``` 这里展示了一个典型的输入输出流控制协议样貌。任何想要兼容这套标准的人都只需要按照既定规则去填充相应的内容就可以了,根本不用再去思考底层到底采用了什么样的存储媒介或者是传输通道之类的东西[^4]。 --- ### 应用场景总结 - 当需要定义一组共同行为但又不想限定其实现细节的时候; - 建立大型软件项目的初步蓝图阶段,先勾勒出各个主要模块之间交互的方式再逐步完善各自的功能单元; - 设计插件式架构的产品线解决方案过程中,预先规划好第三方贡献者应该遵守的技术契约文档; - 构建跨平台工具链环境下的适配层中间件服务端口映射关系表等等场合都非常适用引入纯虚函数以及依托它们形成的抽象基类技术思路来进行开发实践工作。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值