前言

最近需要在产品中集成一个蓝牙门锁的功能,之前没有接触过蓝牙(包括经典蓝牙),所以没有头绪,只能硬着头皮找资料,开始挖坑填坑的日子,这篇文章内容是个人经过学习和实践以后总结的,有不对地方请大家指正。

BLE简介

BLE是Bluetooth low energy的意思,属于蓝牙低功耗协议。顾名思义,比一般蓝牙省电,听说有效距离在100米以上,而且一颗纽扣电池就可以工作数年,这些都是网上查到的,在这里再啰嗦一遍。
那么BLE设备是如何工作呢,BLE设备里面包含了Service(服务),Characteristic(特征),Descriptor(描述),一个BLE设备包含一个或多个Service,一个Service包含一个或多个Characteristic,一个Characteristic包含一个value和一个或多个Descriptor,一个Descriptor包含一个value,与BLE设备的通信就是通过与这些个属性打交道。

Android 中的BLE

Android文档中写到:安卓4.3(API 18)为BLE的核心功能提供平台支持和API,App可以利用它来发现设备、查询服务和读写特性。
下面我们来看看Android提供了哪些API给我们使用:
BluetoothManager:从名字可以看出是用来管理蓝牙设备的,用它来获取BluetoothAdapter;
BluetoothAdapter:蓝牙适配器,针对蓝牙模块的操作,比如:关闭,打开,扫描等;
LeScanCallback:在蓝牙扫描过程中,发现一个设备,就会回调一次;
BluetoothDevice:代表一个远程的蓝牙设备
BluetoothGatt:代表一个Bluetooth GATT Profile,用来控制蓝牙开关,和特征的读写等;
BluetoothGattCallback:这个可就厉害了,针对蓝牙的连接状态,读写等操作,这里都会有相应的回调;
BluetoothGattService:表示一个服务;
BluetoothGattCharacteristic:表示一个特征;
BluetoothGattDescriptor:表示一个描述;
以上就是我们开发BLE所需要的API了,下面我们就动手来使用这些API。

实战

启用/关闭蓝牙

/**
 * 初始化蓝牙适配器
 */
public void init(){
    //获取蓝牙适配器
    BluetoothManager manager = (BluetoothManager)
            mContext.getSystemService(Context.BLUETOOTH_SERVICE);
    mBluetoothAdapter = manager.getAdapter();
}

/**
 * 打开蓝牙
 */
public void open() {
    if (mBluetoothAdapter != null && !mBluetoothAdapter.isEnabled()) {
        //不提示直接打开
        //mBluetoothAdapter.enable();

        //提示是否打开
        Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        mContext.startActivity(intent);
    }
}

/**
 * 关闭蓝牙
 */
public void close() {
    if (mBluetoothAdapter != null) {
        mBluetoothAdapter.disable();
    }
}

打开蓝牙有2种方式,一种直接打开,一种是询问用户是否打开,在测试过程中,发现有些机型(例如三星S4)即使使用第一种方式打开,也同样会询问,所以使用了第二种方式,这也是google官方推荐的打开方式。

扫描设备

/**
 * 扫描设备,定义10秒后自动停止
 */
public void scan(){
    if(mBluetoothAdapter !=null){
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mBluetoothAdapter.stopLeScan(mScanCallback);
            }
        },10000);
        mBluetoothAdapter.startLeScan(mScanCallback);
    }
}

/**
 * 扫描回调接口
 */
private LeScanCallback mScanCallback = new LeScanCallback() {
    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        Log.d("ble",device.getName()+"_"+ device.getAddress());
    }
};

连接/断开设备

当扫描到设备以后,选择一个设备进行连接:

/**
 * 连接设备
 */
public void connect(BluetoothDevice device) {
    if (device != null) {
        mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback);
    }
}

private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {

    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            Log.d("ble","设备已连接");
            //链接成功发现服务
            gatt.discoverServices();
        }else if(newState== BluetoothProfile.STATE_DISCONNECTED){
            Log.d("ble","设备已断开");
        }
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicRead(gatt, characteristic, status);
        Log.d("ble","读取特征值回调");
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        super.onCharacteristicChanged(gatt, characteristic);
        Log.d("ble","特征值改变回调");
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        super.onServicesDiscovered(gatt, status);
        Log.d("ble","发现服务回调");
    }
};

/**
 * 断开设备
 */
public void disConnect() {
    if (mBluetoothGatt != null) {
        mBluetoothGatt.disconnect();
    }
}

连接上蓝牙设备之后,在开始和蓝牙设备交互之前要先发现服务,否则我们是无法获取服务、特征和描述的,只需要调用BluetoothGatt.discoverServices()方法即可。

开始通信

与蓝牙进行通信,首先需要知道蓝牙Service的UUID,Characteristic的UUID,Characteristic的属性又分为可读,可写,通知等,可以通过BluetoothGattCharacteristic.getProperties()判断,这些UUID应该都是由硬件商提供,否则程序无法与蓝牙通信。下面开始贴代码:

/**
 * 接收特征值   在 {@link #mGattCallback} 的 onCharacteristicChanged 回调
 *
 * @param sUUID  服务UUID
 * @param cUUID  特征UUID
 * @param dUUID  描述UUID
 * @param enable 是否启用
 * @return true成功,false失败
 */
public boolean receiveCharacteristic(String sUUID, String cUUID, String dUUID, boolean enable) {
    if (mBluetoothGatt != null) {
        BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString(sUUID));
        if (service != null) {
            BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(cUUID));
            mBluetoothGatt.setCharacteristicNotification(characteristic, enable);
            if (enable) {  //监听需要设置描述
                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(dUUID));
                if (descriptor != null) {
                    descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                    if (mBluetoothGatt.writeDescriptor(descriptor)) {
                        return true;
                    } else {
                        return false;
                    }
                } else {
                    return false;
                }
            }
            return true;
        } else {
            return false;
        }
    } else {
        return false;
    }
}

/**
 * 读取特征值数据  在{@link #mGattCallback}中的  onCharacteristicRead 回到获取结果
 *
 * @param sUUID 服务UUID
 * @param cUUID 特征UUID
 * @return true成功,false失败
 */
public boolean readCharacteristic(String sUUID, String cUUID) {
    if (mBluetoothGatt != null) {
        BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString(sUUID));
        if (service != null) {
            BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(cUUID));
            if (characteristic != null) {
                return mBluetoothGatt.readCharacteristic(characteristic);
            } else {
                return false;
            }
        } else {
            return false;
        }
    } else {
        return false;
    }
}

/**
 * 写入特征值
 *
 * @param sUUID 服务UUID
 * @param cUUID 特征UUID
 * @param data  写入值
 * @return true成功,false失败
 */
public boolean writeCharacteristic(String sUUID, String cUUID, byte[] data) {
    if (mBluetoothGatt != null) {
        BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString(sUUID));
        if (service != null) {
            BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(cUUID));
            if (characteristic != null) {
                return writeValue(characteristic, data);
            } else {
                return false;
            }
        } else {
            return false;
        }
    } else {
        return false;
    }
}

private boolean writeValue(BluetoothGattCharacteristic characteristic, byte[] data) {

    //写入值
    boolean ret = false;

    if (data == null) {
        return false;
    }
    int length = data.length;
    if (length <= dataBufferSize) {
        characteristic.setValue(data);
        ret = mBluetoothGatt.writeCharacteristic(characteristic);
    } else {
        int count = 0;
        int offset = 0;
        while (offset < length) {

            if ((length - offset) < dataBufferSize) {
                count = length - offset;
            } else {
                count = dataBufferSize;
            }
            byte tempArray[] = new byte[count];
            System.arraycopy(data, offset, tempArray, 0, count);

            characteristic.setValue(data);
            ret = mBluetoothGatt.writeCharacteristic(characteristic);

            if (!ret) {
                return ret;
            }

            offset = offset + count;
        }
    }
    return ret;
}

蓝牙的监听和读取都是异步操作,BluetoothGattCallback 中有很多方法可以重写实现回调,有兴趣的可以试试。

总结

在监听特征值的时候,除了需要服务和特征的UUID,android还需要描述的UUID,往描述里写入值后才能监听到,我做IOS的同事则说IOS不需要描述的UUID,不知道为何,期待大神给出答案。

另外:本文中读取特征值的代码没有实际测试过,因为手中的硬件没有相应的特征,有条件的可以测试一下,如果错误请指正。

最后,完整代码如下:

package com.elf.demo.bluetooth;

import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAdapter.LeScanCallback;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.util.Log;

import java.util.UUID;

/**
 * ble帮助类
 * Created by Lidong on 2016/6/5.
 */
@TargetApi(18)
public class BLEHelper {

    private static final int dataBufferSize = 20;

    protected Context mContext;
    protected BluetoothAdapter mBluetoothAdapter;
    protected BluetoothGatt mBluetoothGatt;
    private Handler mHandler;

    public BLEHelper(Context context) {
        mContext = context;
        mHandler = new Handler();
    }

    /**
     * 初始化蓝牙适配器
     */
    public void init() {
        //获取蓝牙适配器
        BluetoothManager manager = (BluetoothManager)
                mContext.getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = manager.getAdapter();
    }

    /**
     * 打开蓝牙
     */
    public void open() {
        if (mBluetoothAdapter != null && !mBluetoothAdapter.isEnabled()) {
            //不提示直接打开
            //mBluetoothAdapter.enable();

            //提示是否打开
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            mContext.startActivity(intent);
        }
    }

    /**
     * 关闭蓝牙
     */
    public void close() {
        if (mBluetoothAdapter != null) {
            mBluetoothAdapter.disable();
        }
    }

    /**
     * 扫描设备,定义10秒后自动停止
     */
    public void scan() {
        if (mBluetoothAdapter != null) {
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mBluetoothAdapter.stopLeScan(mScanCallback);
                }
            }, 10000);
            mBluetoothAdapter.startLeScan(mScanCallback);
        }
    }

    /**
     * 扫描回调接口
     */
    private LeScanCallback mScanCallback = new LeScanCallback() {
        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
            Log.d("ble", device.getName() + "_" + device.getAddress());
        }
    };

    /**
     * 连接设备
     */
    public void connect(BluetoothDevice device) {
        if (device != null) {
            mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback);
        }
    }

    private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {

        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                Log.d("ble", "设备已连接");
                //链接成功发现服务
                gatt.discoverServices();
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                Log.d("ble", "设备已断开");
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
            Log.d("ble", "读取特征值回调");
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
            Log.d("ble", "特征值改变回调");
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            Log.d("ble", "发现服务回调");
        }
    };

    /**
     * 断开设备
     */
    public void disConnect() {
        if (mBluetoothGatt != null) {
            mBluetoothGatt.disconnect();
        }
    }

    /**
     * 接收特征值   在 {@link #mGattCallback} 的 onCharacteristicChanged 回调
     *
     * @param sUUID  服务UUID
     * @param cUUID  特征UUID
     * @param dUUID  描述UUID
     * @param enable 是否启用
     * @return true成功,false失败
     */
    public boolean receiveCharacteristic(String sUUID, String cUUID, String dUUID, boolean enable) {
        if (mBluetoothGatt != null) {
            BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString(sUUID));
            if (service != null) {
                BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(cUUID));
                mBluetoothGatt.setCharacteristicNotification(characteristic, enable);
                if (enable) {  //监听需要设置描述
                    BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(dUUID));
                    if (descriptor != null) {
                        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                        if (mBluetoothGatt.writeDescriptor(descriptor)) {
                            return true;
                        } else {
                            return false;
                        }
                    } else {
                        return false;
                    }
                }
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * 读取特征值数据  在{@link #mGattCallback}中的  onCharacteristicRead 回到获取结果
     *
     * @param sUUID 服务UUID
     * @param cUUID 特征UUID
     * @return true成功,false失败
     */
    public boolean readCharacteristic(String sUUID, String cUUID) {
        if (mBluetoothGatt != null) {
            BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString(sUUID));
            if (service != null) {
                BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(cUUID));
                if (characteristic != null) {
                    return mBluetoothGatt.readCharacteristic(characteristic);
                } else {
                    return false;
                }
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * 写入特征值
     *
     * @param sUUID 服务UUID
     * @param cUUID 特征UUID
     * @param data  写入值
     * @return true成功,false失败
     */
    public boolean writeCharacteristic(String sUUID, String cUUID, byte[] data) {
        if (mBluetoothGatt != null) {
            BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString(sUUID));
            if (service != null) {
                BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(cUUID));
                if (characteristic != null) {
                    return writeValue(characteristic, data);
                } else {
                    return false;
                }
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    private boolean writeValue(BluetoothGattCharacteristic characteristic, byte[] data) {

        //写入值
        boolean ret = false;

        if (data == null) {
            return false;
        }
        int length = data.length;
        if (length <= dataBufferSize) {
            characteristic.setValue(data);
            ret = mBluetoothGatt.writeCharacteristic(characteristic);
        } else {
            int count = 0;
            int offset = 0;
            while (offset < length) {

                if ((length - offset) < dataBufferSize) {
                    count = length - offset;
                } else {
                    count = dataBufferSize;
                }
                byte tempArray[] = new byte[count];
                System.arraycopy(data, offset, tempArray, 0, count);

                characteristic.setValue(data);
                ret = mBluetoothGatt.writeCharacteristic(characteristic);

                if (!ret) {
                    return ret;
                }

                offset = offset + count;
            }
        }
        return ret;
    }
}