Android BLE开发

有不少帖子都在说Android蓝牙开发的方法,可是对于其中的概念以及做用时间一直没有不是很清楚,下边整理一下相关概念性的东西,记录一下。android

基础概念

蓝牙链接传输数据的过程当中,会用到如下几个概念:服务,特性,描述。一个蓝牙设备会有多个服务,每个服务都是一类操做;在这类操做下会存在几个不一样的值须要读写或者通知,每个值对应惟一一个标记,该标记便是特征值(特性characteristic),个人理解是键值(key);每个特征值又有多个不一样的属性c#

  1. 描述(descriptor): 描述是某个特征值的一个属性。每一个特征值有其指定的属性,例如长度(size);权限(permission):属性访问权限,通常有Read、Write、Notifications、Indications;值(value):属性的值,通常是咱们向设备写入的数据或者设备通知出来的数据;描述(descriptor);数组

  2. 特性(characteristic): 开发过程当中定义的一种参数,能够对其进行读、写、通知操做,对应的就是咱们须要的数据;bash

  3. 服务(service): 在存在多个特性值的状况下,通常会对其进行分类,每个分类便是一个服务(service)app

以上三个概念每个都使用惟一的UUID指定。异步

做用时间

属性定义以下这般: ide

Handler:是属性表的一个句柄(索引),在程序里只是一个数组,这个值咱们不须要专门去处理;

Type:属性的类型,即UUID,蓝牙标准组织对UUID进行了分类,以下:ui

0x1800 - 0x26FF 用做服务类通用惟一识别码
0x2700 - 0x27FF 用于标识计量单位
0x2800 - 0x28FF 用于区分属性类型
0x2900 - 0x29FF 用做特性描述
0x2A00 - 0x7FFF 用于区分特性类型
复制代码

作蓝牙BLE开发过程当中有如下类须要使用spa

BluetoothGatt: 通用属性协议。定义BLE通信的基本规则,对通信过程最上层的封装,例如从新链接蓝牙设备,发现蓝牙设备的Service等;线程

BluetoothGattService: 服务。经过BluetoothGatt实例调用getService(UUID)获取,即前面说的对特性分组的服务;用于获取和管理其包含的特性;

BluetoothGattCharacteristic: 特性。经过BluetoothGattService实例调用getCharacteristic(UUID)获取,是GATT通信中的最小数据单元;咱们想蓝牙设备发送数据、接收蓝牙设备的通知都须要用到,是进行通信的最小的操做对象;

BluetoothGattDescriptor: 特性描述符。对特性的额外描述,包括但不只限于特征的单位、属性等。

BluetoothDevice: 表明一个远程蓝牙设备。这个类可使咱们链接其表明的蓝牙设备或者获取一些有关它的信息,例如它的名字、地址和绑定状态等;

BluetoothAdapter: Android中的蓝牙适配器。用于操做蓝牙硬件,例如开启蓝牙扫描,根据已知MAC地址实例化一个BluetoothDevice用于链接蓝牙设备的操做等。

以上就是在蓝牙BLE通讯过程当中须要用到的一些类和概念。

开发步骤

具体看JBD的Android BLE 蓝牙开发入门

第一步 声明所须要的权限

<uses-permission android:name="android.permission.BLUETOOTH"/> 使用蓝牙所须要的权限
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> 使用扫描和设置蓝牙的权限(申明这一个权限必须申明上面一个权限)
复制代码

以上BLUETOOTH和BLUETOOTH_ADMIN两个权限是蓝牙开发必须的,同时不一样的SDK版本还会要求其余的权限。在Android 5.0 以后,须要在manifest中声明GPS硬件模块功能的使用。

<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
<uses-feature android:name="android.hardware.location.gps" />
复制代码

在Android 6.0及以上,还须要打开位置权限。若是应用没有位置权限,蓝牙扫描功能不能使用(其它蓝牙操做例如链接蓝牙设备和写入数据不受影响)。同时位置权限是敏感权限,须要进行权限动态申请及判断。

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
复制代码

第二步 蓝牙链接前初始化

  1. 调用getSystemService(Context.BLUETOOTH_SERVICE)获取BluetoothManager实例,该实例为全局单例;而后经过BluetoothManager实例调用getAdapter()获取系统蓝牙适配器BluetoothAdapter实例;
  2. 检测蓝牙是否开启,并尝试开启蓝牙,同时也必须校验BlutoothAdapter实例是否为空。

第三步 扫描蓝牙设备

外围设备开启蓝牙后,会广播出许多的关于该设备的数据信息,例如 mac 地址,uuid 等等。经过这些数据咱们能够筛选出须要的设备。使用BluetoothAdapter实例扫描蓝牙设备,低版本SDK中有两个方法能够调用

//扫描含有特定UUID Service的蓝牙设备
boolean startLeScan(UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback)
//扫描所有蓝牙设备
boolean startLeScan(BluetoothAdapter.LeScanCallback callback)
复制代码

在SDK>=21时这两个方法已经被废弃,官方建议使用BluetoothLeScanner中的如下方法。

//该方法是在API版本26开始添加的,能够在主程序再也不运行的时候再后台进行扫描操做,当发现符合过滤条件的设备时会向PendingIntent指定的操做发送通知,进行后续操做。
int startScan (List<ScanFilter> filters,ScanSettings settings,PendingIntent callbackIntent)
//从API版本21开始添加
//搜索所有蓝牙设备,使用默认扫描设置,而且没有扫描过滤器
void startScan (ScanCallback callback)
//从API版本21开始添加
//搜索符合过滤条件的设备
void startScan (List<ScanFilter> filters,ScanSettings settings,ScanCallback callback)
复制代码

ScanFilter: 是一个蓝牙扫描过滤器,能够指定扫描条件,该参数能够为空,具体支持以下,后边两个应该不常使用,翻译可能不太准确:

  • 过滤包含指定Service UUID的设备(Service UUIDs which identify the bluetooth gatt services running on the device.)
  • 过滤指定名称的设备(Name of remote Bluetooth LE device.)
  • 过滤指定MAC地址的设备(Mac address of the remote device.)
  • 过滤包含与服务相关的服务数据的设备(Service data which is the data associated with a service.)
  • 过滤包含特定制造商特定数据的设备(Manufacturer specific data which is the data associated with a particular manufacturer. )

ScanSettings: 能够配置扫描设置,采用构造器模式。能够设置

  • 扫描模式(低电量、均衡、最高占空比三种模式)
  • 扫描回调类型(第一次、所有、最后一次触发回调三种类型)
  • 扫描结果返回类型(所有和简洁两种模式)
  • 扫描结果数量(一个,少许,尽量多三种配置)
  • 匹配模式(饥饿和粘滞两种模式)

等,具体能够查看源代码或者Google官方文档。

BluetoothAdapter.LeScanCallback: 是扫描结果回调,第一个参数是表明蓝牙设备的类;第二个参数是蓝牙的信号强弱指标,经过蓝牙的信号指标,咱们能够大概计算出蓝牙设备离手机的距离。计算公式为:d = 10^((abs(RSSI) - A) / (10 * n));第三个参数是蓝牙广播出来的广播数据。

ScanCallback: 是startScan的扫描回调,自己是一个抽象类;有三个方法可选实现方法;

//扫描结束是返回全部匹配的设备列表
void onBatchScanResults(List<ScanResult> results)
//当发现设备广播时回调,第一个参数是回调类型即前边说的ScanSettings中设置的回调类型,第二个参数是扫描到的蓝牙设备信息
void onScanResult(int callbackType, ScanResult result) 
//当启动扫描失败时调用该方法
void onScanFailed(int errorCode)
复制代码

而后就能够调用startLeScan或者startScan开始搜索设备了。若想中止扫描须要根据开始扫描方法肯定调用stopLeScan或者stopScan;出如今回调中的设备会重复出现,因此须要手动过滤掉已经发现的设备。另外须要强调的是终止扫描时传入的回调必须是开始扫描时传入的回调,不然扫描动做不会中止。

第四步 链接蓝牙设备

链接蓝牙设备能够经过 BluetoothDevice#ConnectGatt 方法链接,也能够经过 BluetoothGatt#connect 方法进行从新链接。

//BluetoothDevice#connectGatt
//第二个参数表明是否须要自动链接,true-表示若是设备断开了,会不断的尝试自动链接,false-表示只进行一次链接尝试
BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback) 
复制代码

当调用蓝牙的链接方法以后,蓝牙会异步执行蓝牙链接的操做,若是链接成功会回调 BluetoothGattCalbackl#onConnectionStateChange 方法。这个方法运行的线程是一个 Binder 线程,因此不建议直接在这个线程处理耗时的任务,由于这可能致使蓝牙相关的线程被阻塞。

void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
复制代码

这一个方法有三个参数,第一个就蓝牙设备的Gatt服务链接类。第二个参数表明是否成功执行了链接操做,若是为BluetoothGatt.GATT_SUCCESS表示成功执行链接操做,第三个参数才有效,不然说明此次链接尝试不成功。有时候,咱们会遇到 status == 133 的状况,根据网上大部分人的说法,这是由于Android最多支持链接6到7个左右的蓝牙设备,若是超出了这个数量就没法再链接了。因此当咱们断开蓝牙设备的链接时,还必须调用 BluetoothGatt#close方法释放链接资源。不然,在屡次尝试链接蓝牙设备以后很快就会超出这一个限制,致使出现这一个错误再也没法链接蓝牙设备。 第三个参数表明当前设备的链接状态,若是newState==BluetoothProfile.STATE_CONNECTED说明设备已经链接,能够进行下一步的操做了(发现蓝牙服务,也就是Service)。当蓝牙设备断开链接时,这一个方法也会被回调,其中的newState==BluetoothProfile.STATE_DISCONNECTED。

第五步 发现服务

在成功链接到蓝牙设备以后才能进行这一个步骤,也就是说在BluetoothGattCalbackl#onConnectionStateChang方法被成功回调且表示成功链接以后调用BluetoothGatt#discoverService 这一个方法。当这一个方法被调用以后,系统会异步执行发现服务的过程,直到 BluetoothGattCallback#onServicesDiscovered被系统回调以后,手机设备和蓝牙设备才算是真正创建了可通讯的链接。

到这一步,咱们已经成功和蓝牙设备创建了可通讯的链接,接下来就能够执行相应的蓝牙通讯操做了,例如写入数据,读取蓝牙设备的数据等等。

读取数据

当咱们发现服务以后就能够经过BluetoothGatt#getService获取BluetoothGattService,接着经过 BluetoothGattService#getCharactristic获取BluetoothGattCharactristic。经过BluetoothGattCharactristic#readCharacteristic方法能够通知系统去读取特定的数据。若是系统读取到了蓝牙设备发送过来的数据就会调用BluetoothGattCallback#onCharacteristicRead方法。经过BluetoothGattCharacteristic#getValue能够读取到蓝牙设备的数据。

写入数据

和读取数据同样,在执行写入数据前须要获取到BluetoothGattCharactristic。接着执行如下步骤:

  1. 调用 BluetoothGattCharactristic#setValue传入须要写入的数据(蓝牙最多单次1支持 20 个字节数据的传输,若是须要传输的数据大于这一个字节则须要分包传输)。
  2. 调用 BluetoothGattCharactristic#writeCharacteristic方法通知系统异步往设备写入数据。
  3. 系统回调 BluetoothGattCallback#onCharacteristicWrite方法通知数据已经完成写入。此时,咱们须要执行BluetoothGattCharactristic#getValue方法检查一下写入的数据是否咱们须要发送的数据,若是不是按照项目的须要判断是否须要重发。

向蓝牙设备注册监听实时读取蓝牙设备的数据

BLE app一般须要获取设备中characteristic 变化的通知。以下为一个Characteristic设置一个监听:

mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
//其中的参数为00002902-0000-1000-8000-00805f9b34fb
BluetoothGattDescriptor descriptor=characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
复制代码

以上代码,除了经过 BluetoothGatt#setCharacteristicNotification开启Android端接收通知的开关,还须要往Characteristic的Descriptor属性写入开启通知的数据开关使得当硬件的数据改变时,主动往手机发送数据。

蓝牙接收数据的特性和蓝牙发送通知数据的特性不能相同,不然在高版本系统上没法接收到通知的数据

断开链接

当咱们链接蓝牙设备完成一系列的蓝牙操做以后就能够断开蓝牙设备的链接了。经过 BluetoothGatt#disconnect能够断开正在链接的蓝牙设备。当这一个方法被调用以后,系统会异步回调 BluetoothGattCallback#onConnectionStateChange方法。经过这个方法的 newState 参数能够判断是链接成功仍是断开成功的回调。

因为 Android 蓝牙链接设备的资源有限,当咱们执行断开蓝牙操做以后必须执行 BluetoothGatt#close 方法释放资源。须要注意的是经过 BluetoothGatt#close 方法也能够执行断开蓝牙的操做,不过BluetoothGattCallback#onConnectionStateChange 将不会收到任何回调。此时若是执行 BluetoothGatt#connect方法会获得一个蓝牙 API 的空指针异常。因此,咱们推荐的写法是当蓝牙成功链接以后,经过BluetoothGatt#disconnect 断开蓝牙的链接,紧接着在BluetoothGattCallback#onConnectionStateChange 执行 BluetoothGatt#close 方法释放资源。

相关文章
相关标签/搜索