Android 与BLE设备通讯

 

一、基本介绍

BLE全称Bluetooth Low Energy即低功耗蓝牙。

Android 4.3(API Level 18)开始引入核心功能并提供了相应的 API, 应用程序通过这些 API 扫描蓝牙设备、查询 services、读写设备的 characteristics(属性特征)等操作。

Android BLE 使用的蓝牙协议是 GATT 协议,有关该协议的详细内容可以参见官方文档

Service

一个低功耗蓝牙设备可以定义许多 Service, Service 可以理解为一个功能的集合。设备中每一个不同的 Service 都有一个 128 bit 的 UUID 作为这个 Service 的独立标志。蓝牙核心规范制定了两种不同的UUID,一种是基本的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 也有一个唯一的 UUID 作为标识符。在 Android 开发中,建立蓝牙连接后,我们说的通过蓝牙发送数据给外围设备就是往这些 Characteristic 中的 Value 字段写入数据;外围设备发送数据给手机就是监听这些 Charateristic 中的 Value 字段有没有变化,如果发生了变化,手机的 BLE API 就会收到一个监听的回调。

Android BLE API 简介

BluetoothAdapter BluetoothAdapter 拥有基本的蓝牙操作,例如开启蓝牙扫描,使用已知的 MAC 地址 (BluetoothAdapter#getRemoteDevice)实例化一个 BluetoothDevice 用于连接蓝牙设备的操作等等。

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

BluetoothGatt 这个类提供了 Bluetooth GATT 的基本功能。例如重新连接蓝牙设备,发现蓝牙设备的 Service 等等。

BluetoothGattService 这一个类通过 BluetoothGatt#getService 获得,如果当前服务不可见那么将返回一个 null。这一个类对应上面说过的 Service。我们可以通过这个类的 getCharacteristic(UUID uuid) 进一步获取 Characteristic 实现蓝牙数据的双向传输。

BluetoothGattCharacteristic 这个类对应上面提到的 Characteristic。通过这个类定义需要往外围设备写入的数据和读取外围设备发送过来的数据。

二、开始使用

1.声明权限,在AndroidManifest.xml里面声明:

 

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

在API21(Android5.0)之后还需要加:

<uses-feature android:name="android.hardware.location.gps" />

在Android6.0以后还需要加:

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

好了,权限声明完成就到了我们的代码环节

2.初始化BluetoothAdapter

private BluetoothAdapter mAdapter;
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mAdapter = bluetoothManager.getAdapter();

3.如果蓝牙没有打开先打开蓝牙

if (mAdapter == null || !mAdapter.isEnabled()) {
    // 弹对话框的形式提示用户开启蓝牙
    //startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 1);  
    mBluetoothAdapter.enable();
}

4、监听蓝牙状态

// 蓝牙状态改变的广播
private final BroadcastReceiver bluetoothState = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            String stateExtra = BluetoothAdapter.EXTRA_STATE;
            int state = intent.getIntExtra(stateExtra, -1);
            switch (state) {
            case BluetoothAdapter.STATE_TURNING_ON:    // 蓝牙打开中
                
                break;
            case BluetoothAdapter.STATE_ON:           // 蓝牙打开完成
                
                break;
            case BluetoothAdapter.STATE_TURNING_OFF:  // 蓝牙关闭中
                
                break;
            case BluetoothAdapter.STATE_OFF:          // 蓝牙关闭完成
                
                break;
            }

        }
};

// 蓝牙状态改变的广播
IntentFilter filter2 = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(bluetoothState,new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); 

5.扫描设备

扫描设备有两种,一种是过滤特别的服务扫描,一种是全部扫描

下面代码演示全部扫描

mAdapter.startLeScan(mLeScanCallback); // 开始扫描

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

   @Override
   public void onLeScan(final BluetoothDevice device, int rssi,
         byte[] scanRecord) {    
        Log.i("扫描到",device.getName()+"rssi"+rssi);
    }
};

停止扫描:

mAdapter.stopLeScan(mLeScanCallback);   // 停止扫描

6.连接GATT服务端

连接蓝牙设备可以通过 BluetoothDevice#ConnectGatt 方法连接,也可以通过 BluetoothGatt#connect 方法进行重新连接。以下分别是两个方法的官方说明:

  1. BluetoothDevice#connectGatt

  2. BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback)

第二个参数表示是否需要自动连接。如果设置为 true, 表示如果设备断开了,会不断的尝试自动连接。设置为 false 表示只进行一次连接尝试。

第三个参数是连接后进行的一系列操作的回调,例如连接和断开连接的回调,发现服务的回调,成功写入数据,成功读取数据的回调等等。

BluetoothDevice device = mAdapter.getRemoteDevice(address);
device.connectGatt(this, false, mGattCallback);
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status,
                                        int newState) {
        
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        
       
    }


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

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt,
                                        BluetoothGattCharacteristic characteristic) {
       
    }
};

当调用蓝牙的连接方法之后,蓝牙会异步执行蓝牙连接的操作,如果连接成功会回调 BluetoothGattCalbackl#onConnectionStateChange 方法。这个方法运行的线程是一个 Binder 线程,所以不建议直接在这个线程处理耗时的任务,因为这可能导致蓝牙相关的线程被阻塞。

这一个方法有三个参数,第一个就蓝牙设备的 Gatt 服务连接类。

第二个参数代表是否成功执行了连接操作,如果为 BluetoothGatt.GATT_SUCCESS 表示成功执行连接操作,第三个参数才有效,否则说明这次连接尝试不成功。根据网上大部分人的说法,这是因为 Android 最多支持连接 6 到 7 个左右的蓝牙设备,如果超出了这个数量就无法再连接了。所以当我们断开蓝牙设备的连接时,还必须调用 BluetoothGatt#close 方法释放连接资源。

第三个参数代表当前设备的连接状态,如果 newState == BluetoothProfile.STATE_CONNECTED 说明设备已经连接,可以进行下一步的操作了(发现蓝牙服务,也就是 Service)。当蓝牙设备断开连接时,这一个方法也会被回调,其中的 newState == BluetoothProfile.STATE_DISCONNECTED。

7.发现服务

在成功连接到蓝牙设备之后才能进行这一个步骤,调用 BluetoothGatt#discoverService 这一个方法。当这一个方法被调用之后,系统会异步执行发现服务的过程,直到 BluetoothGattCallback#onServicesDiscovered 被系统回调之后,手机设备和蓝牙设备才算是真正建立了可通信的连接。

if (newState == BluetoothProfile.STATE_CONNECTED) {
    mBluetoothGatt.discoverServices();
}

8.读写设备

当我们发现服务之后就可以通过 BluetoothGatt#getService 获取 BluetoothGattService,接着通过 BluetoothGattService#getCharactristic 获取 BluetoothGattCharactristic。

通过 BluetoothGattCharactristic#readCharacteristic 方法可以通知系统去读取特定的数据。如果系统读取到了蓝牙设备发送过来的数据就会调用 BluetoothGattCallback#onCharacteristicRead 方法。

通过 BluetoothGattCharacteristic#getValue 可以读取到蓝牙设备的数据。

// 读取数据
BluetoothGattService service = mBluetoothGatt.getService("需要读取的服务uuid");
BluetoothGattCharacteristic characteristic = service.getCharacteristic("需要读取的特征的uuid");
mBluetoothGatt.readCharacteristic(characteristic);
/**
 * BLE终端数据被读的事件
 */
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
                                 BluetoothGattCharacteristic characteristic, int status) {

    Log.i("onCharacteristicRead","读取的回调"+characteristic.getValue());
}

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

  1. 调用 BluetoothGattCharactristic#setValue 传入需要写入的数据(蓝牙最多单次1支持 20 个字节数据的传输,如果需要传输的数据大于这一个字节则需要分包传输)。
  2. 调用 BluetoothGattCharactristic#writeCharacteristic 方法通知系统异步往设备写入数据。
  3. 系统回调 BluetoothGattCallback#onCharacteristicWrite 方法通知数据已经完成写入。此时,我们需要执行 BluetoothGattCharactristic#getValue 方法检查一下写入的数据是否我们需要发送的数据,如果不是按照项目的需要判断是否需要重发。
BluetoothGattService service = mBluetoothGatt.getService("需要写的服务");
BluetoothGattCharacteristic characteristic = service.getCharacteristic("需要写的特征");
characteristic.setValue("需要写入的数据");
mBluetoothGatt.writeCharacteristic(characteristic);
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
    if(!characteristic.getValue().equals("需要写入的数据")) {
        // 执行重发策略
        gatt.writeCharacteristic(characteristic);
    }
}

通知

当我们向蓝牙设备(比如指环、电子秤等)写入指令后,蓝牙设备对我们做出的反馈需要在这里读取:

mBluetoothGatt.setCharacteristicNotification(characteristic, true);

BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
        UUID.fromString("通知特征uuid"));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);//或者descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);

mBluetoothGatt.writeDescriptor(descriptor);

然后来的通知就会回调在

public void onCharacteristicChanged(BluetoothGatt gatt,
                                    BluetoothGattCharacteristic characteristic) {

}

9.断开连接

当我们连接蓝牙设备完成一系列的蓝牙操作之后就可以断开蓝牙设备的连接了。通过 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();
    }
}

 蓝牙操作经常能用到的工具类:

public class DataConvertUtil {
    // 把一个整型数转成2进制并输出
    public static void printNum(int n){
        String num = Integer.toBinaryString(n);
        if(num.length() == 32){
            System.out.println(num);
        }else{
            StringBuilder sb = new StringBuilder("");
            for(int i =0;i < 32 - num.length(); i ++){
                sb.append("0");
            }
            System.out.println(sb.toString() + num);
        }
    }

    // 将整数转换成字节数组
    public static byte[] int2ByteArr(int i){
        byte[] bytes = new byte[4] ;
        bytes[0] = (byte)(i >> 24) ;
        bytes[1] = (byte)(i >> 16) ;
        bytes[2] = (byte)(i >> 8) ;
        bytes[3] = (byte)(i >> 0) ;
        return bytes ;
    }

    // 将字节数组转换成整数
    public static int byteArr2Int(byte[] arr){
        return  (arr[0] & 0xff) << 24
                | (arr[1] & 0xff) << 16
                | (arr[2] & 0xff) << 8
                | (arr[3] & 0xff) << 0 ;
    }

    // 16进制String转byte
    public static byte[] hexStrToByteArray(String str)
    {
        if (str == null) {
            return null;
        }
        if (str.length() == 0) {
            return new byte[0];
        }
        byte[] byteArray = new byte[str.length() / 2];
        for (int i = 0; i < byteArray.length; i++){
            String subStr = str.substring(2 * i, 2 * i + 2);
            byteArray[i] = ((byte)Integer.parseInt(subStr, 16));
        }
        return byteArray;
    }

    // 字节数组转16进制String
    public static String byteArrayToHexStr(byte[] byteArray) {
        if (byteArray == null){
            return null;
        }
        char[] hexArray = "0123456789ABCDEF".toCharArray();
        char[] hexChars = new char[byteArray.length * 2];
        for (int j = 0; j < byteArray.length; j++) {
            int v = byteArray[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }

    // byte数组转String
    public static String byteArrayToStr(byte[] byteArray) {
        if (byteArray == null) {
            return null;
        }
        String str = new String(byteArray);
        return str;
    }

    // 字符串转byte数组
    public static byte[] strToByteArray(String str) {
        if (str == null) {
            return null;
        }
        byte[] byteArray = str.getBytes();
        return byteArray;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值