赞
踩
Android 平台包含蓝牙网络堆栈支持,凭借此支持,设备能以无线方式与其他蓝牙设备交换数据。应用框架提供了通过 Android Bluetooth API 访问蓝牙功能的途径。使用 Bluetooth API Android 应用可以执行下面的操作:
传统蓝牙适用于电池使用强度较大的操作,例如 Android 设备之间的流传输和通信等。针对具有低功耗要求的蓝牙设备,Android 4.3(API 18)中引入了面向低功耗蓝牙的 API 支持。
使用 Android Bluetooth API 来完成使用蓝牙进行通信的四项主要任务:
设置蓝牙、查找局部区域内的配对设备或可用设备、连接设备、以及在设备之间传输数据。
关于蓝牙的 API 在 android.bluetooth
包中,下面介绍一下和蓝牙相关的主要类
BluetoothAdapter
本地蓝牙适配器,是所有蓝牙交互的入口点,表示蓝牙设备自身的一个蓝牙设备适配器,整个系统只有一个蓝牙适配器。通过它可以发现其他蓝牙设备,查询绑定(配对)设备的列表,使用已知的 Mac 地址实例化 BluetoothDevice
以及创建 BluetoothServerSocket
用来侦听来自其他设备的通信。
BluetoothDevice
表示远程的蓝牙设备。利用它可以通过 BluetoothSocket
请求与某个远程设备建立连接,或查询有关该设备的信息,例如设备的名称、地址、类和绑定状态等。
BluetoothSocket
表示蓝牙套接字接口(与 TCP Socket 相似)。这是允许应用通过 InputStream
和 OutputStream
与其他蓝牙设备交换数据的节点。正是利用这个对象来完成蓝牙设备间的数据交换,
BluetoothServerSocket
表示用于侦听传入请求的开发服务器套接字(类似于 TCP ServerSocket)要连接两台 Android 设备,其中一台设备必须使用此类开发的一个服务器套接字。当一台远程蓝牙设备向此设备发出连接请求时,BluetoothServerSocket
将会在接受连接后返回已连接的 BluethoothSocket
。
BluetoothClass
描述蓝牙设备的一般特性和功能。这是一组只读属性,用于定义设备的主要和次要设备类及其服务。不过,它不能可靠地描述设备支持的所有蓝牙配置文件和服务,而是适合作为设备类型提示。
BluetoothProfile
表示蓝牙配置文件的接口。蓝牙配置文件是适用于设备间蓝牙通信的无线接口规范。免提配置文件便是一个示例。如需了解关于配置文件的详细讨论,参考下面配置文件的讲解
BluetoothHeadset
提供蓝牙耳机支持,以便与手机配合使用。其中包括蓝牙耳机和免提(1.5版)配置文件。BluetoothProfile 的实现类
BlutoothA2dp
定义高质量音频如何通过蓝牙连接和流式传输,从一台设备传输到另一台设备。“A2DP”代表高级音频分发配置文件。是 BluetoothProfile 的实现类
BluetoothHealth
表示用于控制蓝牙服务的健康设备配置文件代理。 BluetoothProfile 的实现类。
BluetoothGatt
BluetoothProfile 的实现类。与低功耗蓝牙通信有关的配置文件代理
BluetoothHealthCallback
用于实现 BluetoothHealth 回调的抽象类。必须扩展此类并实现回调方法,以接收关于应用注册状态和蓝牙通道状态变化的更新内容。
BluetoothHealthAppConfiguration
表示第三方蓝牙健康应用注册的应用配置,以便与远程蓝牙健康设备通信
BluetoothProfile.ServiceListener
在 BluetoothProfile IPC 客户端连接到服务(即,运行特定配置文件的内部服务)或断开服务连接时向其发送通知的接口。
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 调用 BluetoothAdapter 的静态方法 getDefaultAdapter()
方法。会返回一个表示设备自身的蓝牙适配器(蓝牙无线装置)的 BluetoothAdapter。
整个系统有一个蓝牙适配器,我们的应用可以通过 BluetoothAdapter 这个对象与之交互。如果 getDefaultAdapter()
返回 null 则说明 该设备不支持蓝牙。
1.3.3:启用蓝牙
需要确认是否已经开启蓝牙isEnabled()
。返回 false 则说明蓝牙处于关闭状态。请求启用蓝牙。使用 ACTION_REQUEST_ENABLE
操作 Intent 调用 startActivityForResult()
将通过系统设置发出启用蓝牙的请求。也可以通过 mBluetoothAdapter.enable()
直接打开蓝牙。
- // 没有开始蓝牙
- if(!mBluetoothAdapter.isEnabled()){
- Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
- startActivityForResult(enableBtIntent,REQUEST_ENBLE_BT);
- }
我们的应用也可以选择侦听 ACTION_STATE_CHANGED
广播 Intent。每当蓝牙状态发生变化时,系统会广播此 Intent。此广播包含额外字段 EXTRA_STATE
和 EXTRA_PREVIOUS_STATE
分别表示新的和旧的蓝牙状态。
1.3.4:查找设备
使用 BluetoothAdapter 可以通过设备发现或通过查询配对设备的列表来查找远程蓝牙设备。
设备发现是一个扫描过程,它会搜索局部区域内已启用蓝牙功能的设备,然后请求一些关于各台设备的信息。这个过程也称为发现、查询、扫描。局部区域内的蓝牙设备仅在其当前已启用可检测性时才会响应发现请求。如果设备可以检测到,它将通过共享一些信息(例如设备名称、类及其唯一MAC地址)来响应发现请求。利用此信息,执行发现的设备可以选择发起到被发现设备的连接。
在首次与远程设备建立连接后,将会自动向用户显示配对请求。设备完成配对后,将会保存关于该设备的基本信息(如 设备名称、MAC 地址)。并且可以使用 Bluetooth API 读取这些信息。利用远程设备的已知 Mac 地址可以随时向其发起连接,而不需执行发现操作(假定该设备处于有效范围内)。
被配对和被连接之间存在差别。被配对意味着两台设备知晓彼此的存在,具有可用于身份验证的共享链路密钥,并且能够与彼此建立加密连接。被连接意味着设备当前共享一个 RFCOMM 通道,并且能够向彼此传输数据。当前的 Android Bluetooth API 要求对设备进行配对,然后才能建立 RFCOMM 连接(在使用 Bluetooth API 发起加密连接时,会自动执行配对)。Android 设备是默认处于不可检测状态的。
查询配对的设备,在执行设备发现之前,有必要查询已配对的设备集合。用来了解设备是否处于已知状态。通过 getBondedDevices()
来实现,这将返回表示已配对设备的一组 BluetoothDevice
。
例如:我们可以查询所有已配对的设备,然后使用 ArrayAdapter 向用户显示每台设备的名称:
- 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。这个 Intent 会携带额外的字段 EXTRA_DEVICE 和 EXTRA_CLASS。这两者分别包含 BluetoothDevice 和 BluetoothClass。
// 创建一个接受 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()
来停止发现,然后尝试连接。如果您已经和某台设备进行连接,那么这个时候执行发现操作会大幅度的减少此连接可用的带宽!因此不应该在处于连接状态的时候执行发现操作!
1.3.6:启用可检测性(如果是连接设备,可以忽略该步骤)
如果我们希望我们的设备是可以被其他设备检测到的,可以使用 ACTION_REQUEST_DISCOVERABLE
来操作 Intent 调用 startActivityForResult(Intent,int)
。这样会通过系统设置发出启用可检测到模式的请求(无需停止我们的应用)。默认情况下,设备会变为可检测状态并且持续 120 秒钟。我们还可以通过 EXTRA_DISCOVERABLE_DURATION
Intent Extra
来定义不同的持续时间。可以设置的最大持续时间为 3600 秒。值为 0 表示始终可以被检测到。任何小于 0 或者大于 3600 的值都会自动设置为 120 秒钟。
例如:
- Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
- discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,300);
- startActivityForResult(discoverableIntent);
将显示对话框,请求用户允许将设备设为可检测到。如果用户响应为 YES,则设备将变为可检测到并持续指定的时间量。然后您的 Activity 将会收到对 onActivityResult() 回调的调用,其结果代码等于设备可检测到的持续时间。如果用户响应 NO 或者出现错误,结果代码为 RESULT_CANCELED
如果设备没有打开蓝牙,则启用设备可检测性的时候会自动启用蓝牙。
设备将在分配的时间内以静默方式保持可检测到模式。如果我们希望在可检测到模式发生变化时收到通知,可以利用 ACTION_SCAN_MODE_CHANGED
Intent 注册 BroadcastReceiver。它将包含额外字段 EXTRA_SCAN_MODE 和 EXTRA_PREVIOUS_SCAN_MODE,两者分别告诉我们新的和旧的扫描模式。每个字段可能包括SCAN_MODE_CONNECTABLE_DISCOVERABLE(可检测到模式)、SCAN_MODE_CONNECTABLE(未处于可检测模式但可以接受连接)、SCAN_MODE_NOE(未处于可检测到模式并且无法连接)
1.3.7:连接设备
要在两台设备上的应用之间创建连接,必须同时实现服务端和客户端机制,因为其中一台设备必须开放服务器套接字,而另一台设备必须发起连接(使用服务器设备的 MAC 地址发起连接)。当服务器和客户端在同一 RFCOMM 通道上分别拥有已连接的 BluetoothSocket 时,二者将被视为彼此连接。在这种情况下每台设备都能获得输入和输出流式传输,并且可以开始传输数据。
服务端和客户端分别以不同的方式来获得 BluetoothSocket 。服务器将在传入连接被接受时收到套接字。客户端将在其打开到服务器的 RFCOMM 通道时收到该套接字。
一种实现方式是自动将每台设备准备为一个服务器,从而使每台设备开发一个服务器套接字并侦听连接。然后任一设备可以发起与另一台设备的连接,并成为客户端。或者其中一台设备可显示“托管”连接并按需开放一个服务器套接字,从而另一台设备则直接发起连接。
在连接之前如果两个设备没有配对,则系统会自动发出配对请求
连接为服务器
当连接两台设备时,其中一台必须保持开发的 BluetoothServerSocket
来充当服务器,用于监听传入的连接请求,在接受了请求后提供一个已经连接的 BluetoothSocket
。从 BluetoothServerSocket
连接获取 BluetoothSocket
后就可以调用 close 来关闭这个等待了。
关于 UUID
通用唯一标识符(UUID),用于表示唯一标识信息的字符串ID,128位。可以使用网络上众多的随机 UUID 生成器,然后通过 formString(String)
来初始化一个 UUID。
服务器套接字接受连接的基本过程
listenUsingRfcommWithServiceRecord(String,UUID)获取 BluetoothServerSocket
accept()
侦听连接请求BluetoothSocket
。close()
来关闭这个监听放在子线程中去执行。
例子:
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 并发起连接。
客户端连接的基本过程
例子
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){ } } }
在连接之前调用 cancleDiscovery()
在进行连接之前应该始终调用这个方法,而且调用的时候无需检测是否正在扫描。
1.3.8:管理连接
建立连接后的两个设备都有一个 BluetoothSocket
通过这个 Socket 就可以在这两个设备间传输数据了。
过程:
1.3.9:使用配置文件
从 Android 3.0 开始, Bluetooth API 便支持使用蓝牙配置文件。蓝牙配置文件是适用于设备间蓝牙通信的无线接口规范。
蓝牙配置文件就是设备间通信(蓝牙设备)的一种规范
免提配置文件便是一个示例,对于连接到无线耳机的手机,两台设备都必须支持免提配置文件。我们也可以通过实现接口 BluetoothProfile
来写入自己的类来支持特定的蓝牙配置文件。Android API 提供了以下的几种蓝牙配置文件的实现:
使用配置文件的基本步骤
getProfileProxy()
,建立到配置文件所关联的配置文件代理对象的连接。BluetoothProfile.ServiceListener
。这个监听会在客户端连接到服务或者断开服务连接的时候发送通知。onServiceConnected()
中获取配置文件代理对象的句柄。例子: 如何连接到 BluetoothHeadset 代理对象,以便能够控制耳机配置文件:
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 开始。应用可以注册接受耳机所发送的预定义的供应商特定 AT 命令的系统广播(例如 Plantronics +XEVENT命令)(也就是说我们的应用可以接受耳机蓝牙商预定义的命令)。如:应用可以接受指示所连接设备的电池电量的广播,并根据需要通知用户或采取其他操作。使用 ACTION_VENDOR_SPECIFIC_HEADSET_EVENT intent 创建广播接收器,用来处理耳机供应商特定的 AT 命令。
健康设备配置文件
Android 4.0 引入了对蓝牙健康设备配置文件(HDP)的支持。这可以使用我们的应用使用蓝牙与支持蓝牙功能的健康设备进行通信(心率检测仪、血糖仪、温度计、台秤)
创建 HDP 应用:
1.3.10:总结
关于普通蓝牙设备和普通蓝牙设备之间的连接通信
关于蓝牙设备和蓝牙仪器(蓝牙耳机、电子秤等等类似产品)
这种之间的通信是通过配置文件代理来实现的。
都有一个对应的配置文件代理类。具体的操作是通过这个对象来完成。
蓝牙4.0标准是2012年推出的蓝牙标准,是蓝牙3.0的升级版本,相比3.0版本它更省电、成本更低,3毫秒低 延迟、具有超长有效连接距离和 AES-128加密等。
蓝牙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只是蓝牙模块的一种分类方法,其实蓝牙模块还有很多分类。
举个栗子:
按照用用途来分,蓝牙模块有数据蓝牙模块,语音蓝牙模块,串口蓝牙模块和车载蓝牙模块;
按照芯片设计来分,蓝牙模块有flash版本和ROM版本。前者一般是BGA封装(求栅阵列封装),外置flash的,后者一般是LCC封装(表面贴装型封装),外接EEPROM。
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,能够提升连接速度。
BluetoothGattCallback callback 连接回调,重要参数,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设备服务是安卓系统中关于BLE蓝牙开发的重要一步,一般在设备连接成功后调用,扫描到设备服务后回调onServicesDiscovered()
函数,函数原型如下:
- @Override
- public void onServicesDiscovered(BluetoothGatt gatt, int status) {
- private List<BluetoothGattService> servicesList;
- //获取服务列表
- servicesList = mBluetoothGatt.getServices();
- }
BLE蓝牙协议下数据的通信方式采用BluetoothGattService、BluetoothGattCharacteristic和BluetoothGattDescriptor三个主要的类实现通信。
BluetoothGattService 简称服务,是构成BLE设备协议栈的组成单位,一个蓝牙设备协议栈一般由一个或者多个BluetoothGattService组成。
BluetoothGattCharacteristic 简称特征,一个服务包含一个或者多个特征,特征作为数据的基本单元。
一个BluetoothGattCharacteristic特征包含一个数据值和附加的关于特征的描述BluetoothGattDescriptor。
BluetoothGattDescriptor:用于描述特征的类,其同样包含一个value值。
(3)获取负责通信的BluetoothGattCharacteristic
BLE蓝牙开发主要有负责通信的BluetoothGattService完成的。当且称为通信服务。通信服务通过硬件工程师提供的UUID获取。获取方式如下:
BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString("蓝牙模块提供的负责通信UUID字符串"));
通信服务中包含负责读写的BluetoothGattCharacteristic,且分别称为notifyCharacteristic和writeCharacteristic。其中notifyCharacteristic负责开启监听,也就是启动收数据的通道,writeCharacteristic负责写入数据。
具体操作方式如下:
- 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)开启监听
开启监听,即建立与设备的通信的首发数据通道,BLE开发中只有当上位机成功开启监听后才能与下位机收发数据。开启监听的方式如下:
- mBluetoothGatt.setCharacteristicNotification(notifyCharacteristic, true)
- BluetoothGattDescriptor descriptor = characteristic
- .getDescriptor(UUID
- .fromString
- ("00002902-0000-1000-8000-00805f9b34fb"));
- descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
若开启监听成功则会回调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();
通过以上六个主要步骤即可实现与设备的通信流程,BLE蓝牙开发流程相对固定,只需按照固定步骤执行即可。若按照上述流程扔不能完成整个通信过程
开发之初实现主界面搜索设备,点击目标设备进入连接、通信界面的功能,因此需要传递目标设备BluetoothDevice的相关信息。起初采用Intent传递Bluetooth.getAddress()方式传递蓝牙的Mac地址,在ManagerActivity调用BluetoothDevice device = mBluetoothAdapter.getRemoteDevice()获取设备。实际使用中发现此函数比较耗时,影响效率。
解决办法:使用Intent传递BluetoothDevice对象。BluetoothDevice实现了Parcelable接口,可以使用Intent直接传递。
手机端打开蓝牙、关闭蓝牙、开启搜索和停止搜索都需要一定的时间来完成,因此在执行以上操作后最好加上一段延时,等待对应操作完成后再去执行下一步。
经典蓝牙建立连接后类似于Socket通信,出现问题的情况不多。遇到问题的情况大多数出现在BLE蓝牙中。
device.connectGatt(context, autoConnect, callback);第二个参数最好设置为false,实际测试发现设置为false连接速度更快。
BLE蓝牙的服务和特征都是通过UUID来查找的,开发时一定要区分好哪个BluetoothGattCharacteristic是用于setCharacteristicNotification的,哪个BluetoothGattCharacteristic是用于writeCharacteristic的。
setCharacteristicNotification函数用于开启监听,执行成功后相当于与设备建立通信通道,之后可以通过写入命令指令来获取设备中的数据。
setCharacteristicNotification的返回值基本上一直都是true,但是不代表开启监听成功。通过查看谷歌官方BLEdemo发现安卓系统下还需要设置用于监听下BluetoothGattCharacteristic的UUID为"00002902-0000-1000-8000-00805f9b34fb"的BluetoothGattDescriptor的Value为BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE。
当设置成功后回调onDescriptorWrite,当status == BluetoothGatt.GATT_SUCCESS基本上才能实现开启监听。而且开启监听以后最好还是要等待200ms左右再向设备发命令,等待设备和手机建立稳定通信通道。
成功开启监听后,终于可以与设备通信了。我使用的设备是一个便携式心电计。上位机发送命令指令,下位机回复的数据格式均为64字节,然后将64字节解析出心率和心电图数据。理想很丰满,然而实际使用中才发现每次回调onCharacteristicChanged方法获取的数据最多20字节,64字节的数据分成了4包发送上来。
解决办法:只能用缓冲区接收了,判断缓冲区大小、判断缓冲区第一个字节,根据数据头读取缓冲区,并进一步解析数据。
不过值得庆幸的是上位机命令指令均小于20字节,起码写入数据不需要分包发送,如果真有遇到这种特殊情况的请看BLE分包发送。
既然最多20字节一包数据,大数据通信就不要考虑BLE了。
我就有一段程序在通信完毕后忘记了断开连接,发现之后再次连接设备以后每写入一次命令onCharacteristicChanged回调多次相同数据,使得缓冲区处理出现问题。感觉就是建立了多个连接通道。在通信出错或者完成同步之后一定要调用mBluetoothGatt.disconnect();
mBluetoothGatt.close();
断开当前连接。
大多数BLE设备是非加密的,也就是不需要与手机配对即可通信。大部分软件都是同步BLE设备的中数据到手机端,整个流程也是自动执行的。大体流程就是:扫描目标设备、开启连接、连接成功扫描服务,扫到服务开启监听、收发数据、断开连接。
BLE设备加密后在连接设备时会弹出系统蓝牙配对对话框,等待用户配对。按常理说此时设备是没有建立连接的,因为还没有配对成功,但是安卓手机端不仅可以完成建立连接,而且还能扫描到服务。如果按照自动流程就去开启监听是无法成功的,因为设备还没有配对成功。
解决办法:监听系统关于蓝牙配对的广播,在完成配对之后在去开启监听。如果是已经配对的设备不会由蓝牙配对广播,只需按照不加密设备流程执行即可。
最初测试用的设备比较老旧,有天用新手机测试发现根本不能扫描到蓝牙设备,查询之后发现是权限问题。安卓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设备连接上之后会主动断开一次连接,然后马上又重新连接上设备。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。