Anadroid-蓝牙使用以及知识总结

本文先介绍蓝牙4.0的使用 有时间再补上2.0
哪里有问题欢迎大家指出

1. 权限和相关基础知识介绍

1.1 获取蓝牙权限

“android:required=”true”表示apk只有在具有bluetooth_le属性的系统里运行,这个4.3之前android系统没有

文章中的权限相关问题都限于Android6.0以前的版本 6.0之后权限需要动态申请 以后会补上

<uses-featureandroid:name="android.hardware.bluetooth_le"android:required="true"/>

<uses-permissionandroid:name="android.permission.BLUETOOTH"/>

<uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN"/>
1.2 蓝牙4.0相关协议

bluetooth_le是指BLE( Bluetooth Low Energy )BLE是蓝牙4.0的核心Profile,主打功能是快速搜索,快速连接,超低功耗保持连接和传输数据,弱点是数据传输速率低,由于BLE的低功耗特点,因此普遍用于穿戴设备。Android 4.3才开始支持BLE API,所以请各位客官把 本文代码 运行在蓝牙4.0和Android 4.3及其以上的系统,另外本文所用的BLE终端是一个蓝牙4.0的串口蓝牙模块。

Bluetooth Profile 是蓝牙的一个很重要特性,就是所有的Bluetooth产品都无须实现全部的Bluetooth规范。为了更容易的保持Bluetooth设备之间的兼容,Bluetooth规范中定义了 Profile。Profile定义了设备如何实现一种连接或者应用的一个通用的规范,按照这个规范来收发数据。你可以把Profile理解为连接层或者应用层协议。

GATT (Generic Attribute Profile)
通过BLE连接,读写属性类小数据的Profile通用规范。现在所有的BLE应用Profile都是基于GATT的。

ATT (Attribute Protocol)
GATT是基于ATT Protocol的。ATT针对BLE设备做了专门的优化,具体就是在传输过程中使用尽量少的数据。每个属性都有一个唯一的UUID,属性将以characteristics and services的形式传输。

Service
Characteristic的集合。

Characteristic
Characteristic可以理解为一个数据类型,它包括一个value和0至多个对次value的描述(Descriptor)。

Descriptor
对Characteristic的描述,例如范围、计量单位等。

关系 BLE分为三部分Service、Characteristic、Descriptor,这三部分都由UUID作为唯一标示符。 一个蓝牙4.0的终端可以包含多个Service,一个Service可以包含多个 Characteristic,一个 Characteristic包含一个Value和多个 Descriptor,一个 Descriptor包含一个Value。

1.3 Android中蓝牙4.0相关类

BluetoothGatt
继承BluetoothProfile,通过BluetoothGatt可以连接设备(connect),发现服务(discoverServices),并把相应地属性返回到 BluetoothGattCallback

BluetoothGattCharacteristic
相当于一个数据类型,它包括一个value和0~n个value的描述(BluetoothGattDescriptor)

BluetoothGattDescriptor
描述符,对Characteristic的描述,包括范围、计量单位等

BluetoothGattService
服务,Characteristic的集合。

BluetoothProfile
一个通用的规范,按照这个规范来收发数据。

BluetoothGattCallback
已经连接上设备,对设备的某些操作后返回的结果。这里必须提醒下,已经连接上设备后的才可以返回,没有返回的认真看看有没有连接上设备。

BluetoothAdapter 应用通过一个统一的类BluetoothAdapter(蓝牙本地适配器类)与这些蓝牙设备协议对应的BLUETOOTH API进行交互。

BluetoothAdapter是所有蓝牙对象交互和执行蓝牙操作的入口:包括调用BLUETOOTH profile API,发现其它蓝牙设备、查询配对成功的设备、使用已知的MAC地址实例化蓝牙设备、创建一个BluetoothServerSocket对象来监听其它蓝牙设备以及根据地址实例化蓝牙设备等操作。

获取BluetoothAdapter对象:
android 4.3 以下(API<18)的版本使用如下方式获得BluetoothAdapter单例对象。

BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();

android 4.3 以下(API>=18)的版本使用如下方式获得BluetoothAdapter单例对象。

BluetoothManager bluetoothManager =  
           (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);  
BluetoothAdapter mAdpter=bluetoothManager.getAdapter();

2. 开始搜索设备

搜索之前 先检查是否支持BLE

if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(this,R.string.ble_not_supported, Toast.LENGTH_SHORT).show();

        }

如果蓝牙没打来,还需要打开蓝牙

if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

后台打开蓝牙,不做任何提示,这个也可以用来自定义打开蓝牙对话框啦

mBluetoothAdapter.enable();

后台关闭蓝牙

mBluetoothAdapter.disable();

搜索蓝牙的常用方法

private void connect(){
    mHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        }
    }, 10000);
    mBluetoothAdapter.startLeScan(mLeScanCallback);
}

1.10秒的时间内搜索蓝牙startLeScan()方法只能搜索支持BLE的蓝牙设备.
2.mBluetoothAdapter.startLeScan()方法和mBluetoothAdapter.startDiscovery()互斥
3.这里有一个回调,通过它获得蓝牙的信息 方法如下:

private BluetoothAdapter.LeScanCallback mLeScanCallback =
                new BluetoothAdapter.LeScanCallback() {

            @Override
            public void onLeScan(final BluetoothDevice device, final int rssi, byte[] scanRecord) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                    Log.e("rssi", "RSSI=:"+rssi+""); //接收信号强度(负值)
                    Log.e("name", device.getName());//蓝牙设备名称
                    Log.e("andrass",device.getAddress()); //蓝牙MAC地址
          Log.e("a","发现蓝牙"+device.getAddress()+"状态"+device.getBondState()+"type"+device.getType()+device.describeContents());
                    }
                });
            }
        };

此时,如果发现支持BLE的蓝牙设备可以获得一些基本信息,此处可以筛选我们想要连接的蓝牙设备.

扩展:

RSSI - 接收信号强度(负值)
根据rssi可计算蓝牙发射端和蓝牙接收端的距离
计算公式:

d = 10^((abs(RSSI) - A) / (10 * n))

其中:

d - 计算所得距离
    A - 发射端和接收端相隔1米时的信号强度
    n - 环境衰减因子

3. 开始连接设备

3.1
以给定的MAC地址去创建一个 BluetoothDevice 类实例(代表远程蓝牙实例)。即使该蓝牙地址不可见,也会产生一个BluetoothDevice 类实例。
如果该蓝牙设备MAC地址不能被识别,其蓝牙Name为null。
如果MAC address无效,抛出IllegalArgumentException。

BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);

直接创建连接

BluetoothGatt mBluetoothGatt = device.connectGatt(context, false, mGattCallback);

在这之前,需要创建一个BluetoothGattCallback的实例,用来接收是否发现服务以及连接状态的变化

private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            if (newState == BluetoothProfile.STATE_CONNECTED) 
                {
                // 试图发现服务连接成功后
                Log.e(TAG, "启动服务发现:" +
                        mBluetoothGatt.discoverServices());
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                // 试图发现服务连接失败后
                Log.e(TAG, "服务器断开");
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            Log.e("BluetoothGattCallback", "onServicesDiscovered");
            if (status == BluetoothGatt.GATT_SUCCESS) {
                //发现服务成功 在此处可得到服务列表
                Log.i(TAG, "onServicesDiscovered");
            } else {
                Log.e(TAG, "onservicesdiscovered收到: " + status);
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic characteristic,
                                         int status) {
            Log.e("BluetoothGattCallback", "onCharacteristicRead");
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.i(TAG, "onCharacteristicRead_success");
            }
        }
        /**
         * 处理返回数据。
         */
        public void onCharacteristicChanged(BluetoothGatt gatt,
            BluetoothGattCharacteristic characteristic) {
                Log.e("BluetoothGattCallback", "onCharacteristicChanged");
        }
        /**
         * 获取信号强度
         */
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
            Log.e("BluetoothGattCallback", "onReadRemoteRssi");
            Log.e("a", "返回读出的值:"+rssi);
        };
    };

通过device.connectGatt()方法连接蓝牙设备后,无论成功与否都会进入mGattCallback.onConnectionStateChange()方法中,在此处判断连接是否成功.链接成功后可以通过mBluetoothGatt.discoverServices()方法启动发现服务功能.

boolean state = mBluetoothGatt.discoverServices()

别忘了BLE主要的结构就在这里(Service->Characteristic->Descriptor).如果发现服务成功,则会调用mGattCallback.onServicesDiscovered()方法.代码在上面已经给出.

此时,在mGattCallback.onServicesDiscovered()执行时,说明已经可以获取到Service列表

List<BluetoothGattService> gattServices = mBluetoothGatt.getServices()

这里可以根据你的蓝牙设备来查找相应的Service或者Characteristic,常用方法如下:

String serviceUuid = service.getUuid().toString()
List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
String characteristicsUuid = characteristic.getUuid().toString();
int property = characteristic.getProperties();
//通过uuid直接得到BluetoothGattService
BluetoothGattService linkLossService =mBluetoothGatt
             .getService(UUID.fromString("49535343-fe7d-4ae5-8fa9-9fafd205e455"));
//通过uuid直接得到BluetoothGattCharacteristic 
BluetoothGattCharacteristic alertLevel =linkLossService.getCharacteristic(UUID.fromString("49535343-8841-43f4-a8d4-ecbe34729bb3"));

对于Service,Characteristic,Descriptor不太熟悉的可以回到本文前面再看一下.

一般来说,Characteristic是手机与BLE终端交换数据的关键,Characteristic有较多的跟权限相关的字段,例如PERMISSION和PROPERTY,而其中最常用的是PROPERTY.Characteristic的PROPERTY可以通过位运算符组合来设置读写属性,例如READ|WRITE、READ|WRITE_NO_RESPONSE|NOTIFY,因此读取PROPERTY后要分解成所用的组合.

if ((property | BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
    mBluetoothGatt.readCharacteristic(characteristic);
    mBluetoothGatt.setCharacteristicNotification(characteristic, true);
}

获取到characteristic时,通过readCharacteristic()方法可以获取读属性,可以读取数据.通过setCharacteristicNotification()方法启用或禁用通知给特性,可以获取来自蓝牙设备的通知.
readCharacteristic()会回调mGattCallback.onCharacteristicRead()方法.这时传入的characteristic参数可能带有设备的返回值,可自行处理.

基本到这里蓝牙设备已经连接成功,可能不同的设备连接方式会有所出入,这里只是一个大概的流程,总体的思路就是:
开始搜索BLE设备 -> 找到,筛选设备 -> 通过MAC地址创建设备对象 -> 获取gatt连接 -> 获取设备服务列表 -> 获取特征项(连接完成)

4. 传输数据

Android和蓝牙设备连接完成后,取得特征项后将通讯指令写入
注意:在写入数据和接受蓝牙设备传回的数据时要注意数据格式,一般的蓝牙设备的开发文档都会注明.

boolean state = characteristic.setValue(bb);

然后将特征项发送出去

mBluetoothGatt.writeCharacteristic(characteristic);

此时在会回调mGattCallback的onCharacteristicWrite()方法,可以回去写入状态等信息.

当蓝牙设备返回信息时,会调用mGattCallback的onCharacteristicChanged()方法.
最后处理返回结果,或者发送下一条指令.

获取信号强度

mBluetoothGatt.readRemoteRssi()

该方法可以获得当前的信号强度,会回调mGattCallback的onReadRemoteRssi()方法

4. 断开连接

mBluetoothGatt.close();

一句代码就能断开连接

提示

在设计蓝牙功能的时候可以将其封装到Service中,降低代码耦合,提高控制蓝牙的简洁度.