Android蓝牙bt/ble开发
1:经典蓝牙开发
1.1:经典蓝牙简介
Android 平台具备蓝牙网络堆栈的支持功能
- 检查其他可连接的蓝牙设备列表
- 访问本地计算机上列出的所有蓝牙适配器,并查找与当前计算机配对的蓝牙设备列表
- 创建一个基于RFCOMM协议的数据通信通道
- 利用服务发现机制与其他外部设备建立连接
- 实现与其他外部设备之间的双向数据传输通信
- 协调并管理多个数据通信连接
传统蓝牙主要应用于电池消耗较高的操作场景。如 Android 设备间的流数据传输与通信操作。为满足低功耗需求的蓝牙设备设计,在 Android 版本 4.3(API 版本 18)中增加了支持低功耗蓝牙功能的 API 接口。
1.2:基础知识
使用 Android Bluetooth API 来完成使用蓝牙进行通信的四项主要任务:
配置蓝牙设置 ;寻找附近区域内的配对设备或可用设备 ;连接相应的设备,并在这些设备之间传输数据
在本节中简要介绍 android.bluetooth 包中的主要API及其作用
BluetoothAdapter
本地端点适配器即代表所有蓝牙交互的入口点 ,它具体指每个本地设备自身的蓝牙端点适配器。整个系统仅包含一个独特的 Bluetooth 适配器配置。通过该端点适配器可以识别其他蓝牙设备并列出可连接(配对)的目标设备地址;然后指定一个已知 Mac 地址实例化 BluetoothDevice 并生成一个 Bluetooth 服务器套接字来监听来自其他设备的通信。
BluetoothDevice
代表远程蓝牙设备。通过 BluetoothSocket ,它能够发起连接请求至某台远程设备,并获取关于该设备的详细信息包括其名称、地址、类别以及绑定的状态等详细信息。
BluetoothSocket
标识蓝牙套接字接口(与 TCP Socket 类似)。这是一项供应用程序通过 InputStream 和 OutputStream 实现与其他蓝牙设备进行数据交换的功能模块。正是借助此对象实现 Bluetooth 设备间的通信过程。
BluetoothServerSocket
用于监听传入请求的开发服务器套接字(类似于Bluetooth ServerSocket)必须通过两台Android设备实现通信。在这一过程中,其中一台设备必须作为主设备,另一台作为从设备参与通信链路建立。当远程蓝牙设备尝试与此设备建立连接时,BluetoothServerSocket将响应并返回已建立的BluethoothSocket。
BluetoothClass
该组属性主要用于定义设备的主要和次要设备类及其服务。然而它不能可靠地描述设备支持的所有蓝牙配置文件和服务,只能用于快速判断设备的大致类型信息。
BluetoothProfile
表示为蓝牙配置文件的接口。而蓝牙配置文件则是一种适用于设备间的蓝牙通信的无线接口规范。其中一种例子即是免提配置文件。如欲深入了解关于配置文件的相关讨论,请参考下方有关配置文件的详细讲解
BluetoothHeadset
该方案支持蓝牙耳机的连接,并便于手机进行配对使用。其中包含蓝牙耳机以及免提功能(1.5版本)的设置文件。BluetoothProfile 类的实现
BlutoothA2dp
阐述如何通过蓝牙连接与流式传输在两台设备之间进行数据传输。“A2DP”作为...其中...其中...其中...其中...其中...其中...其中...其中...其中...其中...其中....A2DP配置文件”。它是BluetoothProfile的实现类
BluetoothHealth
负责管理蓝牙服务相关健康设备配置文件的工作流程。 BluetoothProfile 的子类
BluetoothGatt
BluetoothProfile 的实现类 。与低功耗蓝牙通信有关的配置文件代理
BluetoothHealthCallback
用于实现蓝牙健康(BluetoothHealth)回声的抽象类。应扩展此类并开发回声方法,以便接收与应用注册状态及蓝牙通道状态变化相关的更新信息。
BluetoothHealthAppConfiguration
第三方蓝牙健康应用所代表的应用配置用于注册远程蓝牙健康设备进行通信
BluetoothProfile.ServiceListener
BluetoothProfile IPC客户端在连接至服务时会主动向相关方发送通知;当该客户端断开与服务的连接后也会向相关方发送通知。
1.3:经典蓝牙模块
1.3.1:蓝牙权限
使用蓝牙必须声明权限 BLUETOOTH 才可以执行蓝牙通信。:
<mainifest>
<uses-permission android:name = "android.permission.BLUETOOTH"/>
<!--启用应用启动设备发现或者操作蓝牙设备的超级管理员-->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
</mainifest>
1.3.2:获取蓝牙适配器
所有蓝牙Activity都依赖于BluetoothAdapter。通过调用BluetoothAdapter类的静态方法getDefaultAdapter()来获取该适配器。该类将返回一个代表设备自身蓝牙适配器(即蓝牙无线装置)的BluetoothAdapter实例。
整个系统配置了一个蓝牙适配器,并且我们的应用能够通过BluetoothAdapter这个对象与之互动。若getDefaultAdapter()返回null,则表明该设备不具备蓝牙功能
1.3.3:启用蓝牙
请确认当前是否已开启蓝牙功能,并检查其状态指示器isEnabled();如果返回值为false,则表示蓝牙设备目前处于关闭状态。为了确保蓝牙连接的正常性,请尝试重新启动相关设备并手动开启蓝牙功能。为了实现这一目标,请执行以下操作:首先使用intent操作来发起一个名为ACTION_REQUEST_ENABLE的操作,并调用startActivityForResult()方法以通过系统设置发出相应的请求;此外,在不影响其他设备的情况下,默认情况下此设备支持直接打开蓝牙连接
// 没有开始蓝牙
if(!mBluetoothAdapter.isEnabled()){
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent,REQUEST_ENBLE_BT);
}
我们的应用可以选择监听 ACTION_STATE_CHANGED 广播intent。每当蓝牙状态发生变动时,系统会发送该intent。该intent将携带额外字段 EXTRA_STATE 和 EXTRA_PREVIOUS_STATE,分别表示当前的和之前的蓝牙连接状态信息。
1.3.4:查找设备
借助 BluetoothAdapter ,既可以方便地识别或检测远程蓝牙设备;也可以通过查看配对设备列表来进行配对。
该过程即为一种探索机制,在探究周围潜在资源方面发挥重要作用;其主要作用是探测周围区域内的可连接蓝牙装置;通常被称作探测流程;**只有当这些区域内的蓝牙装备具备可探测状态时才会有响应行为发生;**当某个装置能够识别周边环境中的其他电子装置时,则可能触发相关操作;其响应行为可能包括分享诸如标识符之类的关键数据;这些数据有助于实现与被探测装置之间的通信连接。
当初次尝试与远方设备联结时, 系统会在初次联结时主动发出配对邀请。一旦完成配对, 系统会存储该设备的核心信息(包括名称标识符和MAC地址)。此外, 还可以通过蓝牙技术规范库函数读取这些数据。如果目标远端设备已知其MAC地址, 那么无需执行搜索操作即可直接与其建立连接(假设远端装置当前处于有效通信范围内)。
在蓝牙技术中存在两种不同的操作状态:配对与连接。在蓝牙技术中存在两种不同的操作状态:配对与连接。当设备处于配对状态时,则表示它们已经互相识别并具备用于身份验证的共享密钥;而当设备处于连接状态时,则表示它们已共享一个基于RFCOMM的标准通信通道。值得注意的是,在蓝牙协议栈中,默认情况下Android设备无法检测到其他外部设备;此外,在蓝牙配置过程中,默认情况下Android设备会被设置为不可检状态
在执行设备发现操作之前,在查询完已配对的设备集合后有必要进行一次校验以确认所连接的设备是否为已知配置。为此可以通过调用 getBondedDevices() 方法来实现这一功能 这将返回一组表示已配对设备的 BluetoothDevice 实例
比如我们可以检索所有配对好的设备 进而让系统向用户展示各设备的具体名称:
Set<BluetoothDevice> pairedDevices = mBlutooothAdapter.getBondedDevices();
if(pairedDevices.size() > 0){
for(BluetoothDevice device:pairedDevices){
// 把名字和地址取出来添加到适配器中
mArrayAdapter.add(device.getName()+"\n"+ device.getAddress());
}
}
要发起连接仅需要知道目标蓝牙设备的 Mac 地址就可以了。
:
1.3.5:发现设备
通过调用 startDiscovery() 进程来实现异步操作。该方法会立即返回一个布尔值来表明是否已成功启动发现操作。此过程一般包括约 12 秒的查询扫描后完成对已发现设备的扫描以便检索其蓝牙名称。
我们应用为了针对 ACTION_FOUND Intent 注册一个 BroadcastReceiver,并接收每台被发现设备的信息;该系统将为每个设备发送 ACTION_FOUND Intent;这个意图将携带额外字段 EFFECTIVE_DEVICE 和 EFFECTIVE_CLASS;这两个字段各自包含蓝牙设备信息与蓝牙类别的详细描述。
// 创建一个接受 ACTION_FOUND 的 BroadcastReceiver
private final BroadcastReceiver mReceiver = new BroadcastReceiver(){
public void onReceive(Context context,Intent intent){
String action = intent.getAction();
// 当 Discovery 发现了一个设备
if(BluetoothDevice.ACTION_FOUND.equals(action)){
// 从 Intent 中获取发现的 BluetoothDevice
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// 将名字和地址放入要显示的适配器中
mArrayAdapter.add(device.getName + "\n" + device.getAddress());
}
}
};
// 注册这个 BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReiver,filter);
// 在 onDestroy 中 unRegister
需要注意的是,在蓝牙适配器中进行 discovery 是一项耗时且资源密集的任务。一旦找到目标设备后,请务必确保调用 cancelDiscovery() 函数来终止该过程,并尝试建立连接。当已经在某设备上建立连接时,在这种情况下尝试再次进行 discovery 会导致该连接所使用的带宽大幅减少。因此,在已经处于连接状态的情况下不应该再进行 discovery 操作。
1.3.6:启用可检测性(如果是连接设备,可以忽略该步骤)
为了使设备能够被其他设备检测到, 我们可以使用 ACTION_REQUEST_DISCOVERABLE 来操作 Intent, 调用 startActivityForResult(Intent, int). 这样操作会向系统发出指令以启用设备的可检测模式(无需中断我们的应用)。默认情况下, **设备会变为可检测状态并且持续 120 秒钟. 此外, 在某些情况下我们可以利用 EXTRA_DISCOVERABLE_DURATION 的意图来延长这一时长.
用于确定不同的持续时间。最大值设为 3600 秒,在所有情况下都能被检测到的情况下取 value = 0。当 value 超出 [-36, -1] 和 (36, +\infty) 范围时会被重置为 12.5 分钟(即 75 秒)。
例如:
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,300);
startActivityForResult(discoverableIntent);
启动对话框以获取用户的权限设置指示。当用户的指示为YES时, 设备会被标记为可检测并继续维持该状态直至指定时间段结束。随后, 您的Activity将会发送一个Middleton()回调请求, 其返回值等于设备保持可检测状态的时间长度。若用户的指示为NO或发生错误, 则返回的结果码将是RESULT_CANCELED。
如果设备没有打开蓝牙,则启用设备可检测性的时候会自动启用蓝牙。
设备将在预设的时间段内以静默状态持续维持可探测的扫描模式。当可探测的扫描模式发生变化时需要发出警报时,请使用 ACTION SCAN_MODE_CHANGED Intent注册 BroadcastReceiver。它将包含两个新增字段:E X T R A _ S C A N _ M O D E 和 E X T R A _ P R E V I O U S _ S C A N _ M O D E ,分别表示最新的和上一次的扫描状态。每个字段可能包含以下几种类型:SCAN_MODE_CONNECTABLE DISCOVERABLE(表示当前处于或能被探测到的扫描状态)、SCAN_MODECONNECTABLE(表示当前不在探测范围内但允许新连接)以及SCAN_MODE_NOE(表示既不可探测又无法接通的状态)。
1.3.7:连接设备
要在跨两台设备的应用之间建立连接,则需确保服务端与客户端均具备相关机制支持。这是因为其中一台设备必须配置服务器套接字以便接收请求(如通过发送使用该服务器设备MAC地址的请求),而另一台则需发送请求(如通过发送使用该客户机设备MAC地址的请求)。当同一RFCOMM通道上有两个两端都已绑定BluetoothSocket时,则这两方会被视为彼此相连。在此状态下双方都可以进行输入输出的数据流处理,并开始数据传输工作。
服务端和客户端分别采用不同的途径获取BluetoothSocket。当服务器接收到来自客户端的连接请求时,会收到该套接字。而当客户端建立与服务器的RFCOMM通道后,则会收到该套接字。
一种实现方式是通过自动化流程使每台设备配置为一个服务端口,并在此基础上启动相应的服务器套接字以便监听 incoming connections. 同时, 任意一台设备都可以主动发起与另一台设备之间的 connection 并作为客户端参与该过程. 或者, 其中一台设备可以通过展示"托管"状态来提供所需服务并根据特定需求动态地打开相应的 server socket, 这样另一台设备就可以直接发起 connection 来建立联系.
在连接之前如果两个设备没有配对,则系统会自动发出配对请求
连接为服务器
在将两台设备进行连接时,在第一台设备上运行一个BluetoothServerSocket实例作为服务器节点,并负责接收来自外部设备的蓝牙信号请求。一旦接收到这些请求并建立蓝牙会话后,该服务实例会返回一个已建立蓝牙会话的BluetoothSocket对象。随后,在获取到这个蓝牙客户端之后即可立即关闭该服务所等待的状态以释放系统资源。
关于 UUID
通用唯一标识符(UUID),一种用于标识信息唯一性的字符串 ID 标识符,在128位长度下具有显著区分性特征。现有多种随机 UUID 生成工具均可使用 formString(String) 方法来创建新的 UUID 对象。
服务器套接字接受连接的基本过程
借助 listenUsingRfcommWithServiceRecord(String,UUID)生成 BluetoothServerSocket实例
其中 String参数是我们自定义的服务名称
系统会自定将其写入到设备上的新服务发现协议(SDP)数据库条目中
该服务的唯一标识符(UUID)也会被包含在SDP中
作为客户端设备连接协议的匹配依据
只有客户端设备与之具有相同UUID时才能建立连接
-
accept()用于侦听客户端的连接请求
该方法采用阻塞式调用方式,在客户端连接被接通或出现错误时将返回;当操作成功后将返回一个BluetoothSocket实例。 -
如果你不打算接收更多的连接请求,则可以通过调用
close()来关闭这个监听机制。
这将导致服务器套接字及其所有资源被释放出来,并不会影响到已经在该处建立起来的 BluetoothSocket 连接。
与 TCP/IP 协议不同的是,在 RFCOMM 协议下规定,在同一个通道内只能存在一个已建立的客户端连接。
放在子线程中去执行。
例子:
private class AcceptThread extend Thread{
private final BluetoothServerSocket mServerSocket;
public AcceptThread(){
mServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME,MY_UUID);
}
public void run(){
BluetoothSocket socket = null;
while(true){
socket = mServerSocket.accept();
if(socket!=null){
// 自定义方法
manageConnectedSocket(socket);
mServerSocket.close();
break;
}
}
}
public void cancle(){
mServerSocket.close();
}
}
连接为客户端
为了实现与设备建立蓝牙连接的目标,在获取该设备的BluetoothDevice对象之前进行必要的步骤。然后通过该对象获得BluetoothSocket并完成连接过程。
客户端连接的基本过程
利用 BluetoothDevice 的 createRfcommSocketToServiceRecord(UUID) 生成 BluetoothSocket 对象。必须确保这里的 UUID 与服务器端保持一致。
- 通过 connect() 发起连接
执行此方法后,系统将会在远程设备上执行 SDP 查找,来匹配 UUID。如果查找成功了并且远程设备接受了该连接,它将共享 RFCOMM 通道在连接期间使用。这个时候 connect() 就会返回。这个方法也是阻塞的,如果失败或者超时(12秒之后),将引发异常。
调用 connect() 的时候要确保客户端没有执行发现操作。如果执行了会大幅度降低连接的速度,增加失败的可能性
例子
private class ConnectThread extend Thread{
private BluetoothDevice mDevice;
private BluetoothSocket mSocket;
public ConnectThread(BluetoothSocket device){
mDevice = device;
// 这里的 UUID 需要和服务器的一致
mSocket = device.createRfcommSocketToServiceRecord(My_UUID);
}
public void run(){
// 关闭发现设备
mBluetoothAdapter.cancelDiscovery();
try{
mSocket.connect();
}catch(IOException connectException){
try{
mSocket.close();
}catch(IOException closeException){
return;
}
}
// 自定义方法
manageConnectedSocket(mmSocket);
}
public void cancle(){
try{
mSocket.close();
}cathc(IOException closeException){
}
}
}
在连接前必须先执行此操作以确保不会出现冲突,并且不需要检查当前是否处于扫描状态
1.3.8:管理连接
连接建立完成后,在这两个设备上都有一个 BluetoothSocket ,可以通过这个 BluetoothSocket 在这两个设备之间传输数据。
过程:
- 获取输入流和输出流
- 通过调用read(byte[])和write(byte[])方法接收或发送数据到数据传输过程
1.3.9:使用配置文件
自 Android 3.0 开始后就支持使用蓝牙配置文件作为接口。这些蓝牙配置文件充当设备间进行蓝牙通信所需的无线接口规范。
蓝牙配置文件就是设备间通信(蓝牙设备)的一种规范
免提配置文件作为一个示例,在连接到无线耳机的手机设备中,则要求双方设备均需支持这种免提功能。此外我们还可以通过创建并实现该接口 BluetoothProfile 来编写自定义类以满足特定蓝牙配额需求。而Android API则提供了包括以下几种蓝牙配额实施方式:
- 耳机 :耳机配置文件提供了蓝牙耳机的支持。也就是这个配置文件提供了手机和蓝牙耳机进行通信的一种规范。使用 BluetoothHeadset 类,用于进程间通信来控制蓝牙耳机服务的代理。这个类包含 AT 命令支持。
- A2DP: 高级音频分发配置文件(A2DP)。定义了高质量音频如何通过蓝牙连接和流式传输,从一个设备传输到另一个设备。BluetoothAdp 类,是用于通过进程间通信(IPC)来控制蓝牙 A2DP 服务的代理。
- 健康设备: Android 4.0(API 14)引入了对蓝牙健康设备配置文件(HDP)的支持。这样就允许我们创建的应用可以使用蓝牙与支持蓝牙功能的健康设备进行通信。(心率检测仪、血糖仪、温度计等等)。详解的配置要查看健康设备配置文件。
使用配置文件的基本步骤
- 获取或获取 BluetoothAdapter,默认适配器
- 通过调用 getProfileProxy() 方法创建并返回一个配置文件代理对象实例,并将其用于建立与所述配置文件相关联的连接。
- 注册服务监听器以捕获客户端连接或断开事件。当这些事件发生时,该监听器会触发特定响应。
- 在 onServiceConnected() 方法中请求获取配置文件代理对象的句柄。
- 通过调用 getProfileProxy() 方法创建并返回一个配置文件代理对象实例后,在后续步骤中将此实例用于监控连接状态并执行与该配置文件相关联的操作。
例子: 如何连接至 Bluetooth headset 作为中间人, 以便控制耳机配置文件.
BluetoothHeadset mBluetoothHeadset;
// 获取默认蓝牙适配器
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 设置监听(监听连接状态)
private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener(){
public void onServiceConnected(int profile,BluetoothProfile proxy){
if(profile == BluetoothProfile.HEADSET){
mBluetoothHeadset = (BluetoothHeadset)
}
}
public void onServiceDisconnected(int profile){
if(profile == BluetoothProfile.HEADSET){
mBluetoothHeadset = null;
}
}
}
// 建立与配置文件代理的连接
mBluetoothAdapter.getProfileProxy(contenxt,mProfileListener,BluetoothProfile.HEADSET);
// 使用 mBluetoothHeadset 代理内部的方法
// 使用完毕后关闭
mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset);
供应商特定的 AT 命令
自Android 3.0版本起。应用可注册接收耳机配对设备预设的命令(如Plantronics +XEVENT指令)。具体而言,在我们的应用中可接收连接设备电池电量状态的相关广播,并根据需求通知用户或执行相应操作。通过创建带有**ACTION_VENDOR SPECIFIC...**intent的广播接收器来处理特定音频事件
健康设备配置文件
Android 4.0 增加了对蓝牙健康设备配置文件(HDP)的支持能力。我们的应用能够通过蓝牙技术与兼容性良好的医疗设备进行数据传输(如心率监测器、血糖检测仪等),从而实现精准的数据采集和分析功能。
创建 HDP 应用:
通过服务监听器获取蓝牙健康配置文件类型的代理对象类似于常规耳机及A2DP配置。通过ServiceListener注册HEALTH配置文件类型参数并调用getProfileProxy()方法以获取与配置文件代理对象的连接。
建立蓝牙健康回调服务并将其配置为健康的统一数据汇总点(BluetoothHealthAPPConfiguration)
与健康的外部设备进行通信连接
当成功与健康的外部设备建立通信后,请通过文件描述符对健康的外部设备执行数据读写操作
当所有操作完成后,请及时关闭相关的健康通道,并取消注册该应用配置以避免资源泄漏
特别地,在长时间未使用的情况下也会自动关闭。
1.3.10:总结
关于普通蓝牙设备和普通蓝牙设备之间的连接通信
可以使用BluetoothAdapter.getDefaultAdapter方法调用系统唯一的一个蓝牙适配器(如果返回结果为null,则表示该设备不具备蓝牙功能)。
可以使用BluetoothAdapter.isEnable方法来检查当前是否已启用蓝牙。
启动蓝牙有两种方式:一种是通过调用BluetoothAdapter.ACTION_REQUEST_ENABLEintent来请求启用服务;另一种是直接使用.bluetooth.enable()方法完成。
使用startDiscovery方法来开启周围设备的搜索功能(该过程会持续12秒),在此期间必须注册广播接收器以捕获发现的设备信号;请务必及时关闭此操作以节省资源。
一旦完成搜索并找到目标设备后,在BluetoothDevice中就可以进行具体的操作(其中客户端与服务端的操作类似于socket通信)。
完成连接后就可以利用BluetoothSocket来读取并处理数据流实现通信功能。
关于蓝牙设备和蓝牙仪器(蓝牙耳机、电子秤等等类似产品)
这种之间的通信是通过配置文件代理来实现的。
都有一个对应的配置文件代理类。具体的操作是通过这个对象来完成。
2:蓝牙4.0标准(Ble开发)
于
蓝牙4.0标准包含传统蓝牙技术和低功耗蓝牙模块技术两大部分 而针对不同应用场景的需求其设计思路也是各有侧重
那么,将蓝牙4.0标准按照应用和支持协议划分主要分为BT 和BLE两种:
经典蓝牙模块(BT):
泛指支持蓝牙协议在4.0以下的模块,一般用于数据量比较大的传输。
经典蓝牙模块 可再细分为:传统蓝牙模块和高速蓝牙模块 。
传统蓝牙模块 于2004年发布,并主要代表采用蓝牙2.1协议的技术,在智能手机时代到来之际。
时期得到广泛支持。
高速蓝牙模块 在2009年推出,速率提高到约24Mbps,是传统蓝牙模块的八倍。
低功耗蓝牙模块(BLE):
满足蓝牙4.0及以上协议需求的模块也可称为BLE模块(Bluetooth Low Energy Module),其主要优势在于效率与能耗的优化。
蓝牙低功耗技术采用了可变的连接时间间隔。该间隔的设置范围为每种应用对应的不同时间长度。
另外,由于BLE技术采用了极快的连接机制,该系统可维持"非连接"模式以节约能源.当需要建立联系时需启动该链路;随后将在最短时间内关闭此链路.此时各端仅有能力感知到对方;必要时需启动该链路并迅速关闭它以减少能耗
蓝牙BT/BLE只是蓝牙模块的一种分类方法,其实蓝牙模块还有很多分类。
举个栗子:
根据用途划分Bluetooth modules,则可分为Data Bluetooth module、Voice Bluetooth module、Serial port Bluetooth module以及Vehicle-mounted Bluetooth module;
根据芯片设计的不同分类,蓝牙模块主要分为两种类型:一种是带有Flash存储的模块(通常采用BGA封装形式),另一种是使用ROM存储的模块(一般采用LCC封装方式)。前者在设计中通常会外置Flash存储芯片,而后者则需要外接EPROM以实现存储功能。
2.1:低功耗BLE开发详解
1:申请权限和经典蓝牙一样
2:打开蓝牙和经典蓝牙一样
3:搜索设备(BLE蓝牙)
mBluetoothAdapter.startLeScan(callback);
private LeScanCallback callback = new LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int arg1, byte[] arg2) {
//device为扫描到的BLE设备
if(device.getName() == "目标设备名称"){
//获取目标设备
targetDevice = device;
}
}
};
4:连接设备
为了实现对BLE设备的识别和管理,并识别出目标设备targetDevice后进行下一步操作。在完成目标设备的建立前必须停止对蓝牙进行搜索。
mBluetoothAdapter.stopLeScan(callback);
注 :通常情况下,执行停止搜索操作会消耗一定时间以完成任务。建议在调用该函数后等待100ms以确保系统已完全停止对蓝牙设备的搜索。随后进行设备连接操作。
BLE蓝牙的连接方法相对简单只需调用connectGatt方法,函数原型如下:
public BluetoothGatt connectGatt (Context context, boolean autoConnect, BluetoothGattCallback callback);
参数说明
返回值 BluetoothGatt: BLE蓝牙连接管理接口, 主要职责是实现与设备之间的通信。后续将深入讲解该接口的相关功能。
boolean autoConnect:建议置为false,能够提升连接速度。
蓝牙GATT连接回调函数 BluetoothGattCallback 是一个重要的参数,在BLE通信中扮演核心角色
5:设备通信
与设备建立连接后进行数据传输,在BluetoothGattCallback的异步回调函数中完成
BluetoothGattCallback中主要回调函数如下
private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt,
BluetoothGattDescriptor descriptor, int status) {
};
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
}
};
这几个关键回调函数是BLE开发过程中不可或缺的部分,在后续章节中将详细阐述其各自的作用和使用时机
(1)等待设备连接成功
当驱动函数targetdDevice.connectGatt(context, false, gattCallback)被调用后时(即启动该函数之后),系统会主动发起尝试与BLE蓝牙设备建立连接。若此操作获得成功,则该连接会被建立到设备上并触发对应的方法onConnectionStateChange进行处理
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
if (newState == BluetoothGatt.STATE_CONNECTED) {
Log.e(TAG, "设备连接上 开始扫描服务");
// 开始扫描服务,安卓蓝牙开发重要步骤之一
mBluetoothGatt.discoverServices();
}
if (newState == BluetoothGatt.STATE_DISCONNECTED) {
// 连接断开
/*连接断开后的相应处理*/
}
};
通过判断newState是否等于BluetoothGatt.STATE_CONNECTED来表示此时已经与设备建立了连接。
(2)开启扫描服务
mBluetoothGatt.discoverServices();
在安卓系统中进行BLE蓝牙开发时,注册相关服务通常被视为一个关键环节。通常在连接成功时会自动执行此操作。当发现已连接的服务时会触发该回调函数,其原型如下:
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
private List<BluetoothGattService> servicesList;
//获取服务列表
servicesList = mBluetoothGatt.getServices();
}
BLE蓝牙协议下的数据通信基于BluetoothGattService、BluetoothGattCharacteristic以及BluetoothGattDescriptor这三个核心组件来实现数据传输。
BluetoothGattService缩略形式的服务是BLE设备协议栈的重要组成部分,在这一协议栈中通常包含一个或多个这样的基本单元
BluetoothGattCharacteristic 作为特征的简称,在此背景下表示一种特性。每个服务都包含一个或多个这样的特性,并且这些特性则被视为数据的基本构成单位。
每个BluetoothGattCharacteristic特征都包括一个数据值字段以及与该特征相关的描述信息
BluetoothGattDescriptor:用于描述特征的类,其同样包含一个value值。
(3)获取负责通信的BluetoothGattCharacteristic
BLE蓝牙开发主要由负责通信的BluetoothGattService来完成。即被定义为通信服务。基于硬件工程师提供的UUID进行获取。具体实现方式如下:
BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString("蓝牙模块提供的负责通信UUID字符串"));
在通信服务中存在一个蓝牙Gatt特性(BluetoothGattCharacteristic)负责接收与发送数据。该特性被分别命名为notifyCharacteristic和writeCharacteristc两部分。其中notifyCharacteristc主要负责启动接收端的监听过程(即开始接收数据),而writeCharacteristc则用于发送数据到系统中。
具体操作方式如下:
BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString("蓝牙模块提供的负责通信服务UUID字符串"));
// 例如形式如:49535343-fe7d-4ae5-8fa9-9fafd205e455
notifyCharacteristic = service.getCharacteristic(UUID.fromString("notify uuid"));
writeCharacteristic = service.getCharacteristic(UUID.fromString("write uuid"));
(4)开启监听
启动收听过程并创建与设备间的通信首包数据通道,在蓝牙技术应用中必须确保上位机完成这一操作才能实现与其他设备的数据交互。具体的实现步骤包括:
mBluetoothGatt.setCharacteristicNotification(notifyCharacteristic, true)
BluetoothGattDescriptor descriptor = characteristic
.getDescriptor(UUID
.fromString
("00002902-0000-1000-8000-00805f9b34fb"));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
当蓝牙Gatt协议启动成功时会响应BluetoothGattCallback中的onDescriptorWrite()方法。具体操作流程如下:
@Override
public void onDescriptorWrite(BluetoothGatt gatt,
BluetoothGattDescriptor descriptor, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//开启监听成功,可以像设备写入命令了
Log.e(TAG, "开启监听成功");
}
};
5)写入数据
当监听成功时, 通过将数据写入writeCharacteristic实现与下位机建立通信。
//value为上位机向下位机发送的指令
writeCharacteristic.setValue(value);
mBluetoothGatt.writeCharacteristic(writeCharacteristic)
其中:value一般为Hex格式指令,其内容由设备通信的蓝牙通信协议规定。
(6)接收数据
当发送指令成功时会调用BluetoothGattCallback中的onCharacteristicWrite()方法,并表明数据已传递至下一位
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "发送成功");
}
super.onCharacteristicWrite(gatt, characteristic, status);
}
如果发送的数据符合通信协议,则下级设备会向上级设备响应相应的数据。该功能由onCharacteristicChanged()回调函数来实现数据接收。
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
// value为设备发送的数据,根据数据协议进行解析
byte[] value = characteristic.getValue();
}
向指定下级设备发送控制指令后,系统会接收来自该设备的响应数据;这样就能实现与其进行通信的目的.
6:断开连接
在完成与设备的通信后必须断开与其建立的连接。请调用如下所述的方法来断开与其建立的连接:
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
基于前述六项核心环节的操作能够有效达成设备间的信息传递。值得注意的是,在蓝牙技术框架下进行连接开发通常遵循既定的规范和步骤。然而,在某些特殊情况下即使严格按照上述方案操作也可能无法确保成功对接。
3:蓝牙开发中遇到的坑
(一)传递对象而不传递地址
在初期阶段实现了主界面的设备搜索功能,在完成这一操作后会触发连接及通信界面功能的开启。因此必须传输目标设备BluetoothDevice相关信息以支持后续操作。最初采用Intent方式获取蓝牙MAC地址,在ManagerActivity中调用mBluetoothAdapter.getRemoteDevice()方法获取远程设备这一流程。经过实际应用后发现该过程运行时间较长会影响整体效率
解决办法:通过Intent传递BluetoothDevice对象。蓝牙设备继承了parable接口,并可以直接通过Intent进行传递。
(二)加上延时,保证执行完毕
移动设备端切换蓝牙服务及启动/停止设备搜索功能均需一定时间;建议执行上述操作后适当延长等待时间以确保所有步骤完成。
(三) BLE蓝牙连接参数
经典蓝牙实现连接后等同于网络通信协议的建立过程,在实际应用中出现故障的情况较为少见。 majority of fault scenarios occur within the BLE(蓝牙) protocol framework.
该函数用于将设备与Gatt接口进行连接(context, autoConnect, callback)。其中第二个参数通常建议设置为false(此处使用反斜杠表示变量),通过实际测试发现采用此值能够显著提高连接速度。
(四)找准UUID,成功开启监听
BLE蓝牙服务和特征均基于UUID进行查找,在开发过程中需明确地区分哪些BluetoothGattCharacteristic用于setCharacteristicNotification功能的配置以及哪些用于writeCharacterData数据的发送。
setCharacteristicNotification函数用于启动监听机制,在成功执行后实现了与设备的通信连接,并随后可以通过发送相应的命令指令来读取设备存储的数据。
setCharacteristicNotification函数返回的结果通常都是true值,并不能直接表明特征通知已成功实现;从官方Google BLE演示代码中可以看出,在Android系统环境下还需配置用于监听蓝牙GattCharacteristic特征的UUID标识符为"0x29 ̄ ̄ ̄ ̄ ̄ "这一蓝牙Gatt描述符的属性值设为蓝牙GattDescriptor.ENABLE_NOTIFICATION_VALUE标志位。
一旦设置成功,则执行onDescriptorWrite回调;仅当status等于BluetoothGatt.GATT_SUCCESS时才可能实现启动数据采集。建议在启动数据采集后等待约200毫秒以确保设备与手机之间建立稳定的通信连接。
(五)最多20字节
启动了监听进程,并最终实现了与设备的通信。我的设备是一台便携式心电计,在这个过程中上位机发送了一个控制指令,并且下位机返回的数据均为64字节长度的信息。随后这些数据被用来计算出心率并生成相应的的心电图信息。然而在理想情况下人们往往抱有很高的期望值但在实际应用中发现每次回调onCharacteristicChanged方法最多仅能获取20字节数据而无法得到完整的全部数据量因此这将这64字节数据划分为四份依次传输
解决办法:必须依赖缓冲区来接收数据。首先评估缓冲区容量以及查看其首字节状态;通过解析数据头信息来确定并提取相应的缓冲区内容;然后进行详细的数据解密与分析过程。
值得庆幸的是上位机的所有命令指令长度均不超过20字节;此外,在至少无需将数据进行分包处理即可完成发送的情况下,请查看关于[BLE分包处理]的相关内容
既然最多20字节一包数据,大数据通信就不要考虑BLE了。
(六)一定要关闭连接
程序在通信完成时没断开连接。发现后重新连接设备时每次输入命令onCharacteristicChanged都会回调相同的数据显示。导致缓冲区处理出现问题。感觉是建立多个通道了。确保在通信出现错误或完成同步操作后立即释放资源。
mBluetoothGatt.close();
断开当前连接。
(七)加密BLE设备
非加密型BLE设备数量较多,在无需进行配对的情况下即可实现通信功能。大部分软件均能实现对BLE设备中数据量较大的信息进行实时同步传输至手机端,并且整个过程无需人工干预。大体流程如下所述:首先依次包括:首先扫描目标设备;随后开启连接;当完成上述操作后会进入下一步骤——检测到服务启动后会开始监听接收和发送相关数据;最后完成上述步骤后会切断与该目标设备的连接关系
当BLE设备加密后,在尝试连接其他设备时会出现蓝牙配对对话框,并要求用户提供配对。通常情况下,在此阶段尚未完成配对操作。然而,在安卓端不仅能够实现设备间的通信连接,并且还可以识别并响应相关服务。手动启动监听功能将无法取得预期效果。
解决办法:系统中关于蓝牙配对状态的广播信息将被持续监测,在完成配对操作后将开启相应的监听机制。已建立蓝牙连接的设备将不会收到蓝牙特有的Pairing状态广播通知,在这种情况下可以直接切换至非加密通信模式无需额外处理即可进入非加密模式
(八)6.0以上权限问题
最初测试使用的是较为老旧的设备,在更换新手机后发现无法在新手机上扫描到蓝牙设备。通过查询后发现问题属于权限设置不当。通常在安卓系统更新至6.0及以上版本时,可能需要启用模糊定位权限才能进行蓝牙扫描
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
安卓随着版本提升对于权限限制更加严格,还是学着用动态申请权限吧。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (this.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
(九)碎片化带来各种问题
安卓系统碎片化导致遇到了各种奇葩问题:
华为mate8 mate9 荣耀6a无法自主断开与之加密的BLE设备连接,除非采取取消配对或关闭其中一方蓝牙连接的操作。
魅力 Flyme 系统在首次匹配完成时无法切断连接,在之后的操作中能够顺利中断连接。
一部手机在其与BLE设备建立连接后会短暂地切开一次连接,并随后迅速重新连接至该设备。
