学而实习之 不亦乐乎

Android 低功耗蓝牙(BLE)开发

2024-06-05 06:43:28

一、Android BLE 概述

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

Android BLE 使用的蓝牙协议是 GATT 协议,此协议表有两个关键属性:Service、Characteristic。关系如图所示:

 

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 就会收到一个监听的回调。

二、Gatt 协议相关类

  1. BluetoothGatt:这个类提供了 Bluetooth GATT 的基本功能。例如重新连接蓝牙设备,发现蓝牙设备的 Service 等等。
  2. BluetoothGattService:这一个类通过 BluetoothGatt.getService() 获得,如果当前服务不可见那么将返回一个 null。这一个类对应上面说过的 Service。我们可以通过这个类的 getCharacteristic(UUID uuid) 进一步获取 Characteristic 实现蓝牙数据的双向传输。
  3. BluetoothGattCharacteristic:这个类对应上面提到的 Characteristic。通过这个类定义需要往外围设备写入的数据和读取外围设备发送过来的数据。

三、开发流程

同经典蓝牙一样,需要先申请权限并打开蓝牙。

1、扫描蓝牙

低功耗蓝牙扫描有两种方式

  1. api>=21 bluetoothAdapter.getBluetoothLeScanner().startScan(scanCallback)
  2. api<21 bluetoothAdapter.startLeScan(leScanCallback)
/**
 * 扫描
 */
public void scanLeDevice(final BluetoothAdapter.LeScanCallback leScanCallback, final ScanCallback scanCallback) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        if (bluetoothAdapter.isEnabled() && bluetoothLeScanner != null) {
            bluetoothLeScanner.startScan(scanCallback);//开始搜索
        } else {
            Log.e("mcy", "蓝牙不可用...");
        }
    } else {
        if (bluetoothAdapter.isEnabled()) {
            bluetoothAdapter.startLeScan(leScanCallback); //开始搜索
        } else {
            Log.e("mcy", "蓝牙不可用...");
        }
    }
    Log.e("mcy", "开始扫描...");
}

停止扫描

/**
 * 停止扫描
 */
public void stopScan(BluetoothAdapter.LeScanCallback mLeScanCallback, ScanCallback scanCallback) {
    Log.e("mcy", "停止扫描...");
    if (bluetoothAdapter != null && mLeScanCallback != null) {
        bluetoothAdapter.stopLeScan(mLeScanCallback);
    }
    if (bluetoothLeScanner != null && scanCallback != null) {
        bluetoothLeScanner.stopScan(scanCallback);
    }
}

还有两个回调的方法,这是用来反馈扫描的结果的:

//api<21回调这个借口(可以在代码中看到已过时)
leScanCallback = new BluetoothAdapter.LeScanCallback() {

    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {

        if (!TextUtils.isEmpty(device.getName())) {
            if (!devicesList.contains(device)) {
                devicesList.add(device);
                Log.e("mcy", "扫描到设备-->" + device.getName());
                textView.setText(textView.getText() + "\n" + device.getName());
            }

            if (device.getName().equals("00doos009000012147")) {//连接制定的设备。!!!!!测试使用!!!!!!
                Log.e("mcy", "扫描到设备-->" + device.getName());
                bleBTBind.stopScan(leScanCallback, scanCallback);
                bleBTBind.connectLeDevice(MainActivity.this, device);
            }
        }
    }
};

//api>=21回调这个借口
scanCallback = new ScanCallback() {
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        if (!TextUtils.isEmpty(result.getDevice().getName())) {
            if (!devicesList.contains(result.getDevice())) {
                devicesList.add(result.getDevice());
                textView.setText(textView.getText() + "\n" + result.getDevice().getName());
            }

            if (result.getDevice().getName().equals("00doos009000012147")) {//连接制定的设备。!!!!!测试使用!!!!!!
                Log.e("mcy", "扫描到设备-->" + result.getDevice().getName());
                bleBTBind.stopScan(leScanCallback, scanCallback);
                bleBTBind.connectLeDevice(MainActivity.this, result.getDevice());
            }
        }
    }
};

2、连接蓝牙

低功耗蓝牙没有配对这么一说,直接连接,连接方式有两种:

一种是根据蓝牙的地址获取远程设备连接

另一种是蓝牙设备直接连接,传入一个回调接口,反馈连接状态,发现服务状态,可以进行下一步的操作


/**
 * 连接方式二
 */
public void connectLeDevice(Context context, BluetoothDevice device) {
    bluetoothGatt = device.connectGatt(context, false, mBluetoothGattCallback);
}

/**
 * 连接方式一
 */
public void connection(Context context, String address) {
    if (BluetoothAdapter.checkBluetoothAddress(address)) {
        BluetoothDevice remoteDevice = bluetoothAdapter.getRemoteDevice(address);
        if (remoteDevice == null) {
            Log.e("mcy", "设备不可用");
        }
        connectLeDevice(context, remoteDevice);
    } else {
        Log.e("mcy", "设备不可用");
    }
}

连接的时候,需要传递一个接口回调,这个是设备反馈回来的状态,具体的使用,代码注释说的很清楚了,不再赘述。

mBluetoothGattCallback = new BluetoothGattCallback() {

    //当连接状态发生改变
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        super.onConnectionStateChange(gatt, status, newState);
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            Log.e("mcy", "连接成功..." + gatt.getDevice().getName());
            gatt.discoverServices();
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            Log.e("mcy", "连接断开...");
        } else if (newState == BluetoothProfile.STATE_DISCONNECTING) {
            Log.e("mcy", "连接ing...");
        }
    }

    //发现新服务,即调用了mBluetoothGatt.discoverServices()后,返回的数据
    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        super.onServicesDiscovered(gatt, status);
        if (status == BluetoothGatt.GATT_SUCCESS) {
            Log.e("mcy", "发现服务成功...");
            gattService = gatt.getService(UUID.fromString("49535343-fe7d-4ae5-8fa9-9fafd205e455"));
            indexTpye = 1;
            if (gattService == null) {
                indexTpye = 2;
                gattService = gatt.getService(UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb"));
            }

            if (gattService == null) {
                Log.e("mcy", "获取bluetoothGattService失败...");
            } else {
                if (indexTpye == 1) {
                    gattCharacteristic = gattService.getCharacteristic(UUID.fromString("49535343-8841-43F4-A8D4-ECBE34729BB3"));
                } else {
                    gattCharacteristic = gattService.getCharacteristic(UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"));
                }
                if (gattCharacteristic == null) {
                    Log.e("mcy", "获取Characteristic失败...");
                } else {
                    bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);//这一句是为了接受蓝牙数据,必须写!!!否则接受不到数据
                    bleResultCallBack.onDiscoverServicesSuccess();
                }
            }
        } else {
            Log.e("mcy", "发现服务失败...");
        }
    }

    //读取从设备传递过来的数据值,在这里读数据
    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicRead(gatt, characteristic, status);
        Log.e("mcy", "onCharacteristicRead...");
    }

    //发送数据后的回调
    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicWrite(gatt, characteristic, status);
        Log.e("mcy", "onCharacteristicWrite...发送成功后走这个方法");
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        super.onCharacteristicChanged(gatt, characteristic);
        Log.e("mcy", "---蓝牙回传数据onCharacteristicChanged---");
        byte[] value = characteristic.getValue();
        if (value != null && value.length > 0) {
            Log.e("mcy", "接收数据" + Arrays.toString(value));
            bleResultCallBack.onReturnResult(value);
        }
    }
};

3、通信

通信包括发送数据和读取数据,这里,我们只需要发送数据就行啦,读数据的话,在上面的接口中回调回来了,进行数据处理就行啦

我这里展示了两种发送数据的方式:均使用 handler 处理了一下,没有直接发送。因为Characteristic最长只能发送 20 个字节,如果要是超过 20 个字节,就得循环发送,当然了,接受数据的时候,也是这个样子,一次接收不完,循环接收。

我这里也有一个问题,就是直接发送数据,第二次会失败,一直没找到原因,但是我换handler 方式,就发送成功。

/**
 * 向蓝牙发送数据方式一
 */
public void sendDataToBT() {
    bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);//不写这一句,蓝牙消息会回传不回来
    if (gattCharacteristic != null && bluetoothGatt != null) {
        //设置读数据的UUID
        for (byte[] datum : data) {
            Log.e("mcy_devidedPacket", "" + Arrays.toString(datum));
            gattCharacteristic.setValue(datum);
            Message message = new Message();
            message.obj = datum;
            handler1.sendMessage(message);
        }
    }
}

private void writeData() {
    try {
        boolean b = bluetoothGatt.writeCharacteristic(gattCharacteristic);
        if (b) {
            Thread.sleep(200);
        } else {
            for (int i = 0; i < 10; i++) {
                if (bluetoothGatt.writeCharacteristic(gattCharacteristic)) {
                    return;
                }
            }

            Log.e("mcy", "10次递归发送数据失败" + b);
            cancleConnection();
        }

        Log.e("mcy", "发送数据是否成功:" + b);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

//存储待发送的数据队列
public Queue<byte[]> dataInfoQueue = new LinkedList<>();
private Handler handler2 = new Handler();
private Runnable runnable = new Runnable() {
    @Override
    public void run() {
        send();
    }
};

/**
 * 向蓝牙发送数据方式二
 */
public void sendDataToBT2() {
    if (dataInfoQueue != null) {
        dataInfoQueue.clear();
        dataInfoQueue = Utils.splitPacketFor20Byte(data2);
        handler2.post(runnable);
    }
}

private void send() {
    if (dataInfoQueue != null && !dataInfoQueue.isEmpty()) {
        //检测到发送数据,直接发送
        if (dataInfoQueue.peek() != null) {
            gattCharacteristic.setValue(dataInfoQueue.poll());//移除并返回队列头部的元素
            boolean b = bluetoothGatt.writeCharacteristic(gattCharacteristic);
            Log.e("mcy", "发送数据是否成功:" + b);
        }

        //检测还有数据,延时后继续发送,一般延时100毫秒左右
        if (dataInfoQueue.peek() != null) {
            handler2.postDelayed(runnable, 100);
        }
    }
}

4、关闭各种通信

/**
 * 断开连接
 */
public void cancleConnection() {
    if (bluetoothGatt != null) {
        bluetoothGatt.close();
        Log.e("mcy", "主动断开连接...");
    }
}

三、实例

BleBlueToothService.java

public class BleBlueToothService extends Service {

    private BluetoothAdapter bluetoothAdapter;
    private BluetoothGatt bluetoothGatt;
    private BluetoothLeScanner bluetoothLeScanner;
    private BluetoothGattCallback mBluetoothGattCallback;
    private BleResultCallBack bleResultCallBack;
    private BluetoothGattService gattService;
    private BluetoothGattCharacteristic gattCharacteristic;

    private byte[][] data = new byte[][]{{2, 0, 19, 67, 79, 49, 50, 51, 52, 53, 54, 55, 56, 1, 73, -33, 77, -19, -61, -1},
            {41, -45, -26, 3}};

    private byte[] data2 = new byte[]{2, 0, 19, 67, 79, 49, 50, 51, 52, 53, 54, 55, 56, 1, 73, -33, 77, -19, -61, -1, 41, -45, -26, 3};

    private int indexTpye = 0;

    @Override
    public void onCreate() {
        super.onCreate();
        //获取蓝牙适配器
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
        mBluetoothGattCallback = new BluetoothGattCallback() {

            //当连接状态发生改变
            @Override
            public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
                super.onConnectionStateChange(gatt, status, newState);
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    Log.e("mcy", "连接成功..." + gatt.getDevice().getName());
                    gatt.discoverServices();
                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    Log.e("mcy", "连接断开...");
                } else if (newState == BluetoothProfile.STATE_DISCONNECTING) {
                    Log.e("mcy", "连接ing...");
                }
            }

            //发现新服务,即调用了mBluetoothGatt.discoverServices()后,返回的数据
            @Override
            public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                super.onServicesDiscovered(gatt, status);
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    Log.e("mcy", "发现服务成功...");
                    gattService = gatt.getService(UUID.fromString("49535343-fe7d-4ae5-8fa9-9fafd205e455"));
                    indexTpye = 1;
                    if (gattService == null) {
                        indexTpye = 2;
                        gattService = gatt.getService(UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb"));
                    }
                    if (gattService == null) {
                        Log.e("mcy", "获取bluetoothGattService失败...");
                    } else {
                        if (indexTpye == 1) {
                            gattCharacteristic = gattService.getCharacteristic(UUID.fromString("49535343-8841-43F4-A8D4-ECBE34729BB3"));
                        } else {
                            gattCharacteristic = gattService.getCharacteristic(UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"));
                        }
                        if (gattCharacteristic == null) {
                            Log.e("mcy", "获取Characteristic失败...");
                        } else {
                            bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);//这一句是为了接受蓝牙数据,必须写!!!否则接受不到数据
                            bleResultCallBack.onDiscoverServicesSuccess();
                        }
                    }
                } else {
                    Log.e("mcy", "发现服务失败...");
                }
            }

            //读取从设备传递过来的数据值,在这里读数据
            @Override
            public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
                super.onCharacteristicRead(gatt, characteristic, status);
                Log.e("mcy", "onCharacteristicRead...");
            }

            //发送数据后的回调
            @Override
            public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
                super.onCharacteristicWrite(gatt, characteristic, status);
                Log.e("mcy", "onCharacteristicWrite...发送成功后走这个方法");
            }

            @Override
            public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
                super.onCharacteristicChanged(gatt, characteristic);
                Log.e("mcy", "---蓝牙回传数据onCharacteristicChanged---");
                byte[] value = characteristic.getValue();
                if (value != null && value.length > 0) {
                    Log.e("mcy", "接收数据" + Arrays.toString(value));
                    bleResultCallBack.onReturnResult(value);
                }
            }
        };
    }

    public class BleBlueToothBind extends Binder {
        public BluetoothAdapter getAdapter() {
            return bluetoothAdapter;
        }

        public void setBleResultCallBack(BleResultCallBack bleResultCallBack) {
            BleBlueToothService.this.bleResultCallBack = bleResultCallBack;
        }

        /**
         * 扫描
         */
        public void scanLeDevice(final BluetoothAdapter.LeScanCallback leScanCallback, final ScanCallback scanCallback) {

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                if (bluetoothAdapter.isEnabled() && bluetoothLeScanner != null) {
                    bluetoothLeScanner.startScan(scanCallback);//开始搜索
                } else {
                    Log.e("mcy", "蓝牙不可用...");
                }
            } else {
                if (bluetoothAdapter.isEnabled()) {
                    bluetoothAdapter.startLeScan(leScanCallback); //开始搜索
                } else {
                    Log.e("mcy", "蓝牙不可用...");
                }
            }
            Log.e("mcy", "开始扫描...");
        }


        /**
         * 停止扫描
         */
        public void stopScan(BluetoothAdapter.LeScanCallback mLeScanCallback, ScanCallback scanCallback) {
            Log.e("mcy", "停止扫描...");
            if (bluetoothAdapter != null && mLeScanCallback != null) {
                bluetoothAdapter.stopLeScan(mLeScanCallback);
            }
            if (bluetoothLeScanner != null && scanCallback != null) {
                bluetoothLeScanner.stopScan(scanCallback);
            }
        }

        /**
         * 连接方式二
         */
        public void connectLeDevice(Context context, BluetoothDevice device) {
            bluetoothGatt = device.connectGatt(context, false, mBluetoothGattCallback);
        }

        /**
      * 连接方式一
      */
        public void connection(Context context, String address) {
            if (BluetoothAdapter.checkBluetoothAddress(address)) {
                BluetoothDevice remoteDevice = bluetoothAdapter.getRemoteDevice(address);
                if (remoteDevice == null) {
                    Log.e("mcy", "设备不可用");
                }
                connectLeDevice(context, remoteDevice);
            } else {
                Log.e("mcy", "设备不可用");
            }
        }

        private Handler handler1 = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                writeData();
            }
        };

        /**
         * 向蓝牙发送数据方式一
         */
        public void sendDataToBT() {
            bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);//不写这一句,蓝牙消息会回传不回来
            if (gattCharacteristic != null && bluetoothGatt != null) {
                //设置读数据的UUID
                for (byte[] datum : data) {
                    Log.e("mcy_devidedPacket", "" + Arrays.toString(datum));
                    gattCharacteristic.setValue(datum);
                    Message message = new Message();
                    message.obj = datum;
                    handler1.sendMessage(message);
                }
            }
        }

        private void writeData() {
            try {
                boolean b = bluetoothGatt.writeCharacteristic(gattCharacteristic);
                if (b) {
                    Thread.sleep(200);
                } else {
                    for (int i = 0; i < 10; i++) {
                        if (bluetoothGatt.writeCharacteristic(gattCharacteristic)) {
                            return;
                        }
                    }
                    Log.e("mcy", "10次递归发送数据失败" + b);
                    cancleConnection();
                }
                Log.e("mcy", "发送数据是否成功:" + b);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        //存储待发送的数据队列
        public Queue<byte[]> dataInfoQueue = new LinkedList<>();
       
        private Handler handler2 = new Handler();
        private Runnable runnable = new Runnable() {
            @Override
            public void run() {
                send();
            }
        };

        /**
         * 向蓝牙发送数据方式二
         */
        public void sendDataToBT2() {
            if (dataInfoQueue != null) {
                dataInfoQueue.clear();
                dataInfoQueue = Utils.splitPacketFor20Byte(data2);
                handler2.post(runnable);
            }
        }

        private void send() {
            if (dataInfoQueue != null && !dataInfoQueue.isEmpty()) {
                //检测到发送数据,直接发送
                if (dataInfoQueue.peek() != null) {
                    gattCharacteristic.setValue(dataInfoQueue.poll());//移除并返回队列头部的元素
                    boolean b = bluetoothGatt.writeCharacteristic(gattCharacteristic);
                    Log.e("mcy", "发送数据是否成功:" + b);
                }

                //检测还有数据,延时后继续发送,一般延时100毫秒左右
                if (dataInfoQueue.peek() != null) {
                    handler2.postDelayed(runnable, 100);
                }
            }
        }

        /**
         * 断开连接
         */
        public void cancleConnection() {
            if (bluetoothGatt != null) {
                bluetoothGatt.close();
                Log.e("mcy", "主动断开连接...");
            }
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return new BleBlueToothBind();
    }
}

使用:

bleConnection = new ServiceConnection() {

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        bleBTBind = ((BleBlueToothService.BleBlueToothBind) service);
        if (bleBTBind.getAdapter() != null) {
            //判断蓝牙是否开启
            if (!bleBTBind.getAdapter().isEnabled()) {

                //打开蓝牙
                openBlueSync(MainActivity.this, openBTCode);
            } else {
                //========================开始执行工作=============================
                bleBTBind.scanLeDevice(leScanCallback, scanCallback);
                final StringBuilder stringBuilder = new StringBuilder();
                bleBTBind.setBleResultCallBack(new BleResultCallBack() {

                    //连接成功回调
                    @Override
                    public void onDiscoverServicesSuccess() {
                        bleBTBind.stopScan(leScanCallback, scanCallback);
                        bleBTBind.sendDataToBT();//方式一
//                                bleBTBind.sendDataToBT2();//方式二

                    }

                    //蓝牙返回数据回调
                    @Override
                    public void onReturnResult(byte[] data) {
                        bleBTBind.stopScan(leScanCallback, scanCallback);
                        for (byte byteChar : data) {
                            stringBuilder.append(String.format("%02X ", byteChar));
                        }
                        String returnedPacket = stringBuilder.toString().replace(" ", "");
                        byte[] packetByte = Utils.hexStringToByteArray(returnedPacket);
                        if (packetByte.length - 5 == Utils.getLengthFromToken(packetByte)) {
                            Log.e("mcy_returnedPacket", returnedPacket);
                            bleBTBind.cancleConnection();//取消连接
                        }
                    }
                });
            }
        } else {
            Log.e("mcy", "此设备不支持蓝牙");
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        bleBTBind = null;
    }
};

bindService(new Intent(this, BleBlueToothService.class), bleConnection, BIND_AUTO_CREATE);