阅读 3453

使用RxJava帮助低功耗蓝牙(BLE)进行通信

  Android中的蓝牙开发有两种,一种是传统蓝牙,另一种是低功耗蓝牙,这两者完全不一样,开发前你得弄清你需要开发的是哪一种,用传统蓝牙的方式进行低功耗蓝牙的开发你可能都没法使你的设备连上蓝牙,不要问我为什么知道,说多了都是泪 (TT)
  低功耗蓝牙(Bluetooth Low Energy)简称BLE,常见于各种运动手环、电子血压计等健康管理设备,Android4.3(API级别18)中引入了面向低功耗蓝牙的API支持。也就是说开发的前提是手机设备支持BLE并且系统是Android4.3以上,与手机通信的蓝牙设备是低功耗蓝牙。
  如果你看了官方文档上的示例,你会发现使用了Handler、和广播进行异步通信,之前我在公司项目中蓝牙功能也是这么写的,现在有了RxJava,我们可以写的更优(zhaung)雅(bi)些,所以就有了这篇文章。

本文的代码地址:RxBleDemo
关于RxJava,如果你还不了解,可以看给 Android 开发者的 RxJava 详解
关于低功耗蓝牙的开发你可以看官方文档或者直接阅读本文

整体思路

  假定你已经有了一部支持BLE的手机和一个可以通信的低功耗蓝牙模块,那么就可以按下面的步骤开搞了:

  • 蓝牙权限
  • 设置并开启手机蓝牙
  • 查找蓝牙设备
  • 连接蓝牙服务
  • 进行蓝牙通信

详细步骤

设置蓝牙权限

  在应用中的manifest文件中声明蓝牙权限

<uses-permission android:name="android.permission.BLUETOOTH"/>  
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>复制代码

  另外我们可以通过PackageManager.hasSystemFeature()方法来判断当前的手机设备是否支持BLE。

设置并开启手机蓝牙设备

  首先通过BluetoothManager获取手机中唯一的的蓝牙适配器(蓝牙发送接收器)BluetoothAdapter对象。再通过BluetoothAdapter开启手机蓝牙设备,代码如下:

public void initBle(Context context) {
        this.mContext = context.getApplicationContext();
        BluetoothManager bluetoothManager =
                (BluetoothManager) this.mContext.getSystemService(Context.BLUETOOTH_SERVICE);
        mBleAdapter = bluetoothManager.getAdapter();
        if (mBleAdapter != null) {
            mBleAdapter.enable();//不弹对话框直接开启蓝牙
        }
    }复制代码

  值得一提的是我将蓝牙功能简单的封装成了一个工具类,用到了静态内部类的单例模式,为了防止内存泄露,在初始化蓝牙的时候传入了Application的Context对象的引用

查找蓝牙设备

  定义了一个scanBleDevices(boolean enable)方法用于开启和关闭扫描。这里使用了BluetoothAdapter.startLeScan(LeScanCallback)方法开启扫描,需要传入一个扫描回调:

private BluetoothAdapter.LeScanCallback mBleScanCallback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(BluetoothDevice bleDevice, int rssi, byte[] scanRecord) {
        if (mIsScanning) {
            Log.d(TAG, "onLeScan:找到设备" + bleDevice.getName());
            if (mTargetDeviceName.equals(bleDevice.getName())) {
                connectDevice(bleDevice);//连接蓝牙设备
            }
        } else {
            Log.d(TAG, "onLeScan: 停止扫描");
        }
    }
};复制代码

  这个回调也用于关闭蓝牙扫描的方法BluetoothAdapter.stopLeScan(LeScanCallback),所以定义了一个布尔型变量mIsScanning判断蓝牙扫描的开启和关闭。
  说了这么多,我们的RxJava好像还没登场。在扫描过程中,我们需要限定蓝牙的扫描的超时时间,不能让手机这么一直扫描,所以我们可以通过RxJava中的timer延时一段时间后执行停止扫描:

Observable.timer(SCAN_PERIOD, TimeUnit.MILLISECONDS).subscribe(new Action1<Long>() {
    @Override
    public void call(Long aLong) {
        mIsScanning = false;
        mBleAdapter.stopLeScan(mBleScanCallback);
    }
});复制代码

连接蓝牙服务并接收数据

  当查找到名为mTargetDeviceName的目标蓝牙设备,就可以通过下面的方法去连接,准确的说是连接设备上的GATT服务:

private void connectDevice(BluetoothDevice bleDevice) {
    scanBleDevices(false);
    mBleGatt = bleDevice.connectGatt(mContext, true, new BleGattCallback());
    mBleGatt.connect();
    Log.d(TAG, "开始连接设备:" + mBleGatt.getDevice().getName());
}复制代码

  连接蓝牙设备前需要关闭蓝牙扫描。bleDevice.connectGatt(mContext, true, new BleGattCallback())方法返回了一个蓝牙GATT对象,这个方法中的true代表自动连接(蓝牙模块断电重启后,可以重新连接它),调用GATT的connect()方法进行连接,连接过程中会执行传入的回调BleGattCallback,这个回调继承了BluetoothGattCallback并重写了以下三个方法:

一、onConnectionStateChange

@Override
public void onConnectionStateChange(BluetoothGatt bleGatt, int status, int newState) {
    super.onConnectionStateChange(bleGatt, status, newState);
    Log.d(TAG, "onConnectionStateChange: 连接状态: " + newState);
    if (newState == BluetoothGatt.STATE_CONNECTED) {//连接成功
        Log.d(TAG, "onConnectionStateChange: 设备连接");
        bleGatt.discoverServices();//搜索服务
    } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {//断开连接
        Log.d(TAG, "onConnectionStateChange: 设备断开");
    }
}复制代码

这个方法监听连接状态的改变,连接状态有四个值:

描述
STATE_CONNECTED 已连接
STATE_CONNECTING 正在连接
STATE_DISCONNECTED 断开连接
STATE_DISCONNECTING 正在断开连接

  当设备已连接时,需要通过discoverServices()查找GATT服务,查找服务过程中会执行重写的第二个方法onServicesDiscovered

二、onServicesDiscovered

  可以在此方法中获取GATT的服务列表,这个服务列表中的每一个服务对应着一个BluetoothGattCharacteristic(用于通信)列表,需要对这个列表通过UUID过滤出我们想要的BluetoothGattCharacteristic,然后就可以拿这个BluetoothGattCharacteristic进行通信了。

关于 UUID
通用唯一标识符 (UUID) 是用于唯一标识信息的字符串 ID 的 128 位标准化格式。 UUID 的特点是其足够庞大,因此你可以选择任意随机值而不会发生冲突。 在此示例中,它被用于唯一标识应用的蓝牙服务。 要获取 UUID 以用于你的应用,你可以使用网络上的众多随机 UUID 生成器之一,然后使用 fromString(String) 初始化一个 UUID。不必过多纠结于UUID。

  上面这个过程如果用传统的方式编写的话,那就是列表遍历嵌套列表遍历再嵌套if判断,下次再看的话就是一堆迷之缩进,还好可以用RxJava写出链式的结构:

@Override
public void onServicesDiscovered(final BluetoothGatt bleGatt, int status) {
    Log.d(TAG, "onServicesDiscovered: 查找服务: " + bleGatt.getServices().size());
    List<BluetoothGattService> serviceList = bleGatt.getServices();
    Observable.from(serviceList)
            .flatMap(new Func1<BluetoothGattService, Observable<BluetoothGattCharacteristic>>() {
                @Override
                public Observable<BluetoothGattCharacteristic> call(BluetoothGattService bleGattService) {
                    return Observable.from(bleGattService.getCharacteristics());
                }
            })
            .filter(new Func1<BluetoothGattCharacteristic, Boolean>() {
                @Override
                public Boolean call(BluetoothGattCharacteristic bleGattChar) {
                    return bleGattChar.getUuid().toString().equals(UUID);
                }
            })
            .subscribe(new Action1<BluetoothGattCharacteristic>() {
                @Override
                public void call(BluetoothGattCharacteristic bleGattChar) {
                    bleGatt.setCharacteristicNotification(bleGattChar, true);//设置开启接收蓝牙数据
                    mBleGattChar = bleGattChar;
                }
            });
}复制代码

三、onCharacteristicChanged

  此方法用于接收蓝牙模块发送过来的数据,它是异步的,可以用RxJava方便的切换到Android主线程:

@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
    Log.d(TAG, "onCharacteristicChanged");
    String receiveData = new String(characteristic.getValue());
    Log.d(TAG, "收到蓝牙发来数据:" + receiveData);
    Observable.just(receiveData)
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Action1<String>() {
                @Override
                public void call(String receiveData) {
                    //处理receiveData
                }
        });
}复制代码

  前面提到了整个蓝牙功能是一个工具类,那么我们怎么在我们想要的地方(Activity)接收到这个receiveData呢?也许你会说可以在这里写一个接口回调啊,是的没问题。在了解到可以通过RxJava实现EventBus事件总线后,我想可以写一个简单的RxBus在这里发射数据,在需要的地方订阅并接受数据。

关于RxBus,你可以先看implementing-an-event-bus-with-rxjava-rxbus,然后可以看state-propagation-in-android-with-rxjava-subjects(需要科学上网)。此外,国内也有很多相关文章。

先定义一个Subject,它既是观察者又是被观察者

private Subject<String, String> mBus = new SerializedSubject<>(PublishSubject.<String>create());复制代码

然后在处理receiveData的地方发射数据

mBus.onNext(receiveData);复制代码

再定义一个方法用于接收数据

public Observable<String> receiveData() {
    return mBus;
}复制代码

最后在需要接收数据的地方订阅

mRxBle.receiveData().subscribe(new Action1<String>() {
    @Override
    public void call(String receiveData) {
        sendTv.setText(mStringBuffer.append(receiveData).append("\n"));
    }
});复制代码

向蓝牙设备发送数据

  通信不仅仅是接收数据,还需要发送数据,这个实现起来很简单,只要使用我们之前拿到的BluetoothGattCharacteristic对象以及BluetoothGatt对象进行相关方法的调用就行,在项目中由于需要对数据进行延时发送,所以也用到了timer

Observable.timer(time, TimeUnit.MILLISECONDS)
        .subscribe(new Action1<Long>() {
            @Override
            public void call(Long l) {
                if (mBleGatt != null && mBleGattChar != null) {
                    mBleGattChar.setValue(data);//设置数据
                    boolean isSend = mBleGatt.writeCharacteristic(mBleGattChar);//写入(发送)数据
                    Log.d(TAG, "发送:" + (isSend ? "成功" : "失败"));
                }
            }
        });复制代码

  RxJava的强大之处在于他有各种各样的操作符,可以对发布的数据源进行各种各样的处理,实际项目中有很多应用的场景。

最后

  我们写一个简单的demo进行测试一下,效果如下:

  • Android端发送Hello,Ble给蓝牙模块,接受蓝牙模块发过来的Hello,Android!



  • 使用调试助手调试蓝牙模块,接受Android端发送过来的Hello,Ble,向Android端发送Hello,Android!



本文的所有代码地址:RxBleDemo
本文同步于我的个人博客

关注下面的标签,发现更多相似文章
评论