BLE学习

android 18(4.3)引入BLE(低功耗蓝牙)全称Bluetooth Low Energy的核心功能API,应用程序通过这些API可以完成 扫描设备  链接设备 查询服务  读写characteristics(特征值)等操作。

Android BLE 使用的蓝牙协议是 GATT 协议,有关该协议的详细内容可以参见蓝牙官方文档。以下我引用一张官网的图来大概说明 Android 开发中我们需要了解的一些 Bluetooth Low Energy 的专业术语


(蓝牙协议栈图)

Bluetooth4.0结构图

Service

一个低功耗设备可以有很多个service,一个service可以理解为设备的一个功能,而一个低功耗设备可以理解为一个功能集合,设备中每一个service都有一个128位的UUID,每个service的UUID都不相同,它作为一个service的唯一标识,蓝牙核心规范制定了两种不同的UUID,一种是基本的UUID,一种16为的UUID用来代替的UUID,所有蓝牙技术联盟定义公用一个基本的UUID,它的基本格式为:0x0000xxxx-0000-1000-8000-00805F9B34FB

为了进一步简化基本UUID,每一个蓝牙技术联盟定义的属性有一个唯一的16位UUID,以代替上面的基本UUID的‘x’部分。例如,心率测量特性使用0X2A37作为它的16位UUID,因此它完整的128位UUID为:
0x00002A37-0000-1000-8000-00805F9B34FB

Characteristic

在service下面有许多的characteristic(特征值),每个characteristic都是一个独立的数据,可以理解为属性(我们把一个设备看成一个project,每个service相当于这个project中的class, 而characteristic就是每个class中的属性),characteristic和service相同,每个characteristic都有一个标识的UUID,在Android开反中,蓝牙建立连接后我们向设备的发送数据,其实就是改变characteristic的value字段的值,外围设置发送数据给手机就是在监听这些characteristic的value有没有发生变化,如果发生了变化手机BLE的API就会产生回调。

API简介(https://developer.android.com/reference/android/bluetooth/BluetoothA2dp.html)

BluetoothAdapter

bluetoothAdapter代表本中心设备(如:手机),它拥有基本的蓝牙操作 扫描 使用已知的 MAC 地址 (BluetoothAdapter#getRemoteDevice)实例化一个 BluetoothDevice 用于连接蓝牙设备的操作等等。

BluetoothDevice

代表一个远程蓝牙设备。这个类可以让你连接所代表的蓝牙设备或者获取一些有关它的信息,例如它的名字,地址和绑定状态等等。

BluetoothGatt

bluetoothGatt是整个蓝牙连接通信中最为重要的类,它主要用于发送和接受数据。

BluetoothGattService

bluetoothService通过(bluetoothGatt#getServices())获得,如果当前服务不可见返回null,buletoothService发现过程为异步(这是一个耗时过程,扫描也相同),我们可以通过个BluetoothService来获取characteristic(单个获取getCharacteristic(UUID)获取全部getCharacteristics())。

BluetoothGattCharacteristic

通过改变这个BluetoothGattCharacteristic的value来实现设备与平台之间的相互通信。

BluetoothSocket

代表蓝牙socket的接口(类似TCP的Socket)。这是允许一个应用程序跟另一个蓝牙设备通过输入流和输出流进行数据交换的连接点。

BluetoothServerSocket

代表一个打开的监听传入请求的服务接口(类似于TCP的ServerSocket)。为了连接两个Android设备,一个设备必须用这个类打开一个服务接口。当远程蓝牙设备请求跟本设备建立连接请求时,BluetoothServerSocket会在连接被接收时返回一个被连接的BluetoothSocket对象。

BluetoothHeadset

提供对使用蓝牙耳机的移动电话的支持。它同时包含了Bluetooth Headset和Hands-Free(v1.5)的配置

BluetoothA2dp

定义如何把高品质的音频通过蓝牙连接从一个设备流向另一个设备。“A2DP”是Advanced Audio Distribution Profile的缩写。 

BluetoothProfile.ServiceListener

BluetoothProfile IPC客户端连接或断开服务的通知接口(它是运行特俗配置的内部服务)。

Android Bluetooth开发流程

一权限申请

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

在Android5.0之前,是默认申请GPS硬件功能的。而在Android 5.0 之后,需要在manifest 中申明GPS硬件模块功能的使用。

<uses-feature android:name="android.hardware.location.gps" />
复制代码

在Android6.0后还需要位置权限,如果没有位置权限扫描功能是不能使用的(其它蓝牙操作例如连接蓝牙设备和写入数据不受影响)

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

二初始化蓝牙适配器

BluetoothManage是系统的服务所以一般使用:context.getSystemService(Context.BLUETOOTH_SERVICE)l来获取一个BluetoothManage。

BluetoothAdapter adapter = blurtoothManage.getAdapter();//获取蓝牙适配器

如果返回的adapter == null者说明该设备部支持蓝牙

adapter.isEnable()返回true则代表蓝牙是开启的,false这表示蓝牙没有打开需要我们去打开蓝牙。

开启蓝牙(隐式意图,弹窗告知用户并请求开启蓝牙):
 Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);

三扫描周围设备

在bluetoothAdapter中我们可以看到有两个扫描的方法:

//扫描指定UUID的设备
boolean    startLeScan(UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback)
复制代码

//扫描周围所有可用的蓝牙设备 boolean startLeScan(BluetoothAdapter.LeScanCallback callback)

例:

//扫描到设备后的回掉
final BluetoothAdapter.LeScanCallback callback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
        bluetoothDeviceArrayList.add(device);
        Log.d(TAG, "run: scanning...");
    }
};

mBluetoothAdapter.startLeScan(callback);复制代码

在LeScanBack的回调中有三个参数,第一个BluetoothDevice代表平台所扫描到的设备,第二个rssi代表设备信号的强弱,数值越大信号越强。通过信号值我们可以大概计算出蓝牙设备离手机的距离。计算公式为:d = 10^((abs(RSSI) - A) / (10 * n));第三个参数是蓝牙广播出来的广告数据。当执行上面的代码之后,一旦发现蓝牙设备,LeScanCallback 就会被回调,直到 stopLeScan 被调用。出现在回调中的设备会重复出现,所以如果我们需要通过 BluetoothDevice 获取外围设备的地址手动过滤掉已经发现的外围设备。

停止扫描

void    stopLeScan(BluetoothAdapter.LeScanCallback callback)
//这里的callback必须和statLeScan()的callback是同一个。复制代码

由于扫描周围设备是一个比较耗费内存的事情,所以在找到我们需要的设备时应该关闭扫描功能。

四链接设备

打我们扫描到设备后,我们通过BluetoothDevice.connectGatt(this, false, callback)链接远程设备参数一上下文 第二个。如果设置为 true, 表示如果设备断开了,会不断的尝试自动连接。设置为 false 表示只进行一次连接尝试。 三需要一个BluetoothGattCallback的链接回调。

当链接成功后会回调BluetoothGattCalbackl的onConnectionStateChange 方法,返回当前的链接状态。

void    onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
//status代表执行链接操作是否成功BluetoothGatt.GATT_SUCCESS代表成功执行了链接操作
//newState是最新的链接状态
//BluetoothGatt.STATE_CONNECTING链接中
//BluetoothGatt.STATE_CONNECTED连接成功
//BluetoothGatt.STATE_DISCONNECTING//正在断开连接
//BluetoothGatt.STATE_DISCONNECTED断开连接复制代码

五发现服务

只有成功链接后才可以做这一步操作,当成功链接后使用BluetoothGatt调用discoverServices()发现服务,当成功执行发现服务的操作后回毁掉BluetoothGattCallback中OnServicesDiscovered(BluetoothGatt gatt, int status)方法,当status等于BluetoothGatt.GATT_SUCCESS是代表设备与平台之间的信道正式打通,可以相互开始通信了。
现在我们已经成功建立了平台与设备之间的信道,我们使用BluetoothGatt.getServices()来获取设备中的所有BluetoothGattService(服务),并使用bluetoothGattService.getCharacteristics()来获取每个服务中特征值,也可以通过 BluetoothGattCharactristic#readCharacteristic 方法可以通知系统去读取特定的数据。如果系统读取到了蓝牙设备发送过来的数据就会调BluetoothGattCallback#onCharacteristicRead 方法。通过 BluetoothGattCharacteristic#getValue 可以读取到蓝牙设备的数据。

例:

@Override
public void onCharacteristicRead(final BluetoothGatt gatt,
                                    final BluetoothGattCharacteristic characteristic,
                                    final int status) {

    Log.d(TAG, "callback characteristic read status " + status
            + " in thread " + Thread.currentThread());
    if (status == BluetoothGatt.GATT_SUCCESS) {
        Log.d(TAG, "read value: " + characteristic.getValue());
    }

}


// 读取数据
BluetoothGattService service = gattt.getService(SERVICE_UUID);
BluetoothGattCharacteristic characteristic = gatt.getCharacteristic(CHARACTER_UUID);
gatt.readCharacteristic();
复制代码

写入数据

和读取数据一样,在执行写入数据前需要获取到 BluetoothGattCharactristic。接着执行一下步骤。

调用 BluetoothGattCharactristic#setValue 传入需要写入的数据(蓝牙最多单次1支持 20 个字节数据的传输,如果需要传输的数据大于这一个字节则需要分包传输)。

调用 BluetoothGattCharactristic#writeCharacteristic 方法通知系统异步往设备写入数据

系统回调 BluetoothGattCallback#onCharacteristicWrite 方法通知数据已经完成写入。

此时,我们需要执行 BluetoothGattCharactristic#getValue 方法检查一下写入的数据是否我们需要发送的数据,如果不是按照项目的需要判断是否需要重发。

@Override
public void onCharacteristicWrite(final BluetoothGatt gatt,
                                    final BluetoothGattCharacteristic characteristic,
                                    final int status) {
    Log.d(TAG, "callback characteristic write in thread " + Thread.currentThread());
    if(!characteristic.getValue().equal(sendValue)) {
        // 执行重发策略
        gatt.writeCharacteristic(characteristic);
    }
}

//往蓝牙数据通道的写入数据
BluetoothGattService service = gattt.getService(SERVICE_UUID);
BluetoothGattCharacteristic characteristic = gatt.getCharacteristic(CHARACTER_UUID);
characteristic.setValue(sendValue);
gatt.writeCharacteristic(characteristic);
复制代码

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

app中通常要获取设备中characteristic的变化通知,我们需要如何为一个设备添加一个实时监听呢?

mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);

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 方法释放资源。

@Override
public void onConnectionStateChange(final BluetoothGatt gatt, final int status,
                                    final int newState) {
        Log.d(TAG, "onConnectionStateChange: thread "
                + Thread.currentThread() + " status " + newState);

        if (status != BluetoothGatt.GATT_SUCCESS) {
            String err = "Cannot connect device with error status: " + status;
      // 当尝试连接失败的时候调用 disconnect 方法是不会引起这个方法回调的,所以这里
                //   直接回调就可以了。
            gatt.close();
            Log.e(TAG, err);
            return;
        }

        if (newState == BluetoothProfile.STATE_CONNECTED) {
            gatt.discoverService();
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            gatt.close();
        }
    }
复制代码

注意事项

蓝牙的写入操作( 包括 Descriptor 的写入操作), 读取操作必须序列化进行. 写入数据和读取数据是不能同时进行的, 如果调用了写入数据的方法, 马上调用又调用写入数据或者读取数据的方法,第二次调用的方法会立即返回 false, 代表当前无法进行操作.

Android 连接外围设备的数量有限,当不需要连接蓝牙设备的时候,必须调用 BluetoothGatt#close 方法释放资源

蓝牙 API 连接蓝牙设备的超时时间大概在 20s 左右,具体时间看系统实现。有时候某些设备进行蓝牙连接的时间会很长,大概十多秒。如果自己手动设置了连接超时时间(例如通过 Handler#postDelay 设置了 5s 后没有进入 BluetoothGattCallback#onConnectionStateChange 就执行 BluetoothGatt#close 操作强制释放断开连接释放资源)在某些设备上可能会导致接下来几次的连接尝试都会在 BluetoothGattCallback#onConnectionStateChange 返回 state == 133。

所有的蓝牙操作使用 Handler 固定在一条线程操作,这样能省去很多因为线程不同步导致的麻烦


转载于:https://juejin.im/post/5a9e00e86fb9a028b86d8719

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值