目前智能家居都被看成是下一个科技爆发点,而智能家居里面使用的技术,响应最高的就算是BLE了,下面,我们说一下android怎么开发BLE,和要注意的一些问题:

1.首先,得知道,android是从android4.3版本才开始支持BLE的,所以,开发的前提就是要知道系统的支持:

if (android.os.Build.VERSION.SDK_INT < 18) {
    // 说明sdk不够高版本
}

2.声明权限,BLE,就是低功耗蓝牙的英文缩写,所以,就是要开启蓝牙的权限,在manifest添加:

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

3.获取适配器,这里需要注意,和经典蓝牙有个点区别(千万别搞错了):

mBluetoothManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();

4.查看手机蓝牙是否开启,如果未开启,则需要主动开启(不推荐),或者提示用户开启(推荐):

if (!mBluetoothAdapter.isEnabled()) {
    startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 0);  // 弹对话框的形式提示用户开启蓝牙
    //mBluetoothAdapter.enable(); // 强制开启,不推荐使用
}

// 注册个广播监听这个值,就可以获取到蓝牙的开关状态
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { // 蓝牙开关发生变化
    // 这里可以直接使用mBluetoothAdapter.isEnabled()来判断当前蓝牙状态
    return;
}

5.接下来就是扫描BLE,这里有2个部分,android4.3-5.0的版本,和5.0+的版本:

// 4.3-5.0版本
mBluetoothAdapter.stopLeScan(lescancallback);   // 停止扫描
mBluetoothAdapter.startLeScan(lescancallback);  // 开始扫描
private LeScanCallback lescancallback = new LeScanCallback() {
    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        // device 就是扫描到的蓝牙对象,里面各种跟蓝牙有关的信息
        // rssi信号强度,这个值是个负数,范围一般为0到-100,负数越大,代表信号越弱,一般如果超过-90,连接会出现不理想的情况
        // scanRecord广播数据,里面的数据就是蓝牙设备希望手机在连接它之前,让手机知道的信息(稍后的篇章讲解广播数据的组成格式,并且如何解析)
    }
};

// 5.0+版本
BluetoothLeScanner scaner = mBluetoothAdapter.getBluetoothLeScanner();  // android5.0把扫描方法单独弄成一个对象了
scaner.stopScan(mScanCallback);   // 停止扫描
scaner.startScan(mScanCallback);  // 开始扫描
private ScanCallback mScanCallback = new ScanCallback() {
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        super.onScanResult(callbackType, result);
        // callbackType:确定这个回调是如何触发的
        // result:包括4.3版本的蓝牙信息,信号强度rssi,和广播数据scanRecord
    }
    @Override
    public void onBatchScanResults(List<ScanResult> results) {
        super.onBatchScanResults(results);
        // 批量回调,一般不推荐使用,使用上面那个会更灵活
    }
    @Override
    public void onScanFailed(int errorCode) {
        super.onScanFailed(errorCode);
        // 扫描失败,并且失败原因
    }
};

因为有2种回调方法,最好在开发的时候,重新打包封装成一个类,根据系统不同,而分别调用不同的方法,因为google会想到推出新方法,那固然会在以后的某个版本把老方法取消。
需要注意的是:

  • 在扫描前,最好先调用一次停止扫描
  • 而且扫描和停止扫描里面的参数对象必须是同一个,所以,这里不能用匿名的方式来创建扫描回调
  • 不要长时间开始扫描,停止扫描,开始扫描,这样的循环,扫描是很耗电的,部分机型这样会导致蓝牙死机
  • 当扫描到自己需要的设备时,停止扫描。

6.连接BLE,大部分手机可以通过MAC来直接连接,不需要扫描,小部分一定要先扫描到才可以进行连接(目前所知型号红米1s):

mBluetoothAdapter.stopLeScan(lescancallback);     // 停止扫描,连接前,必须停止扫描,不然,失败率很高
device = mBluetoothAdapter.getRemoteDevice(mac);  // 通过mac地址获取蓝牙对象,或者可以直接用扫描到的对象,效果都是一样
mBluetoothGatt = device.connectGatt(context, false, mGattCallback);
// BLE连接回调
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, final int status, final int newState) {
        super.onConnectionStateChange(gatt, status, newState);
            handl.post(new Runnable() {
                public void run() {
        if (status != BluetoothGatt.GATT_SUCCESS) { // 连接失败判断
            return;
        }
        if (newState == BluetoothProfile.STATE_CONNECTED) { // 连接成功判断
            mBluetoothGatt.discoverServices(); // 发现服务
            return;
        }
        if (newState == BluetoothProfile.STATE_DISCONNECTED) {  // 连接断开判断
            return;
        }
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, final int status) {
        super.onServicesDiscovered(gatt, status);
        if (status != BluetoothGatt.GATT_SUCCESS) { // 发现服务失败
            return;
        }
        //不是失败的情况就是成功
    }

    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, final int status) {
        super.onDescriptorWrite(gatt, descriptor, status);
        if (status != BluetoothGatt.GATT_SUCCESS) {  // 写Descriptor失败
            return;
        }
        //不是失败的情况就是成功
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        super.onCharacteristicChanged(gatt, characteristic);
        //BLE设备主动向手机发送的数据时收到的数据回调
        characteristic.getValue(); // 通过这个方法来提取收到的数据
    }
    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicWrite(gatt, characteristic, status);
        if (status != BluetoothGatt.GATT_SUCCESS) {  // 写数据失败
            return;
        }
    }
// 还有很多其他回调方法,这里就不一一介绍了
};

需要注意的是:

  • BLE连接成功并不意味着可以通信,需要发现服务后,才可以正常通信,不要问我为什么,因为这就是BLE协议
  • 以手机开启蓝牙为起始点,对某个设备第一次连接,速度会比较慢,这个速度主要取决于BLE设备的广播频率(不懂广播频率?就问你们固件工程师吧),第二次以后,会快很多,因为手机会缓存很多信息,比如虽然需要重新发现服务,但是实际上,手机并没有去发现服务,而是用了缓存的服务。所以,会比较快
  • 中间可能会有回调很多错误,记住一点,不要去深究是什么原因,因为android文档上面没有说,所以,最好的解决方法就是,一旦出现错误,就断开,重新连接,因为BLE底层会对失败进行多次重试,如果告诉你出错误了,也就是说其实底层已经重试了很多次了,还是错误。所以,直接断开,重连就行了

7.获取通信特征值:BLE连接成功后,就需要发现服务,发现服务后,就需要发现特征值,这个特征值才是真正BLE通信需要用到的东西。发现服务后,服务里面有很多BluetoothGattService,这个BluetoothGattService,你可以看做是一个个的文件夹,里面包含了很多文件(特征值BluetoothGattCharacteristic)

private static final String DATA_SERVICE_UUID = "0000f1f0-0000-1000-8000-00805f9b34fb";
BluetoothGattService data_service = mBluetoothGatt.getService(UUID.fromString(DATA_SERVICE_UUID));  // 先获取BluetoothGattService
BluetoothGattCharacteristic txd_charact = data_service.getCharacteristic(UUID.fromString(TXD_CHARACT_UUID)); // 再通过BluetoothGattService获取BluetoothGattCharacteristic特征值

8.手机往BLE设备写入数据:获取到特征值后,就可以正式的进行通信了,手机往设备写数据:

txd_charact.setValue(senddatas);  // 往通道写数据

注意:因为大部分BLE只支持最大20byte的通信,所以,如果有大于20byte的数据需要发送,请拆成多个20byte以内的数组,进行分包发送(当然后面我们会讲如何一次性发送超过20byte的方法)
9.BLE设备主动往手机写数据:这个功能叫做notify、indecation(用这种方式的比较少,微信BLE就是用这种方式),汉语叫可通知属性(不是所有的通道都有这个属性),要主动去开启(有些不规范的BLE设备已经默认开启,可以省略这步)。成功与否,需要看回调(看上面第6点)

if (0 != (charact.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY)) { // 查看是否带有可通知属性notify
    mBluetoothGatt.setCharacteristicNotification(charact, true);
    BluetoothGattDescriptor descriptor = charact.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); // 这包数据什么意思,可以不用管,反正是固定这包数据就是了。
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    mBluetoothGatt.writeDescriptor(descriptor);
} else if (0 != (charact.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE)) {  // 查看是否带有indecation属性
    mBluetoothGatt.setCharacteristicNotification(charact, true);
    BluetoothGattDescriptor descriptor = charact.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
    descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
    mBluetoothGatt.writeDescriptor(descriptor);
}

到这里还不够,上面的只是设置BLE设备可以主动向手机发送数据,手机还需要开启是否接收BLE设备发来的数据:这里设置成功与否没有回调,运行了这段代码,就设置成功了

mBluetoothGatt.setCharacteristicNotification(charact, enable);  // 设置手机是否接收BLE设备发来的数据

10.断开连接:也有相应的回调(看上面第6点)

mBluetoothGatt.disconnect();

需要注意的是:android的BLE有很多问题,如果一定时间(一般5秒,根据BLE设备不同而不同)内没有回调断开,则需要强制关一下手机蓝牙,至于是强制开是强制开,还是用提示用户的方式,根据自己的需求而定