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

Android 中的蓝牙开发有两种,一种是传统蓝牙,另一种是低功耗蓝牙,这两者完全不一样,开发前你得弄清你需要开发的是哪一种,用传统蓝牙的方式进行低功耗蓝牙的开发你可能都没法使你的设备连上蓝牙,不要问我为什么知道,说多了都是泪 (TT)


低功耗蓝牙(Bluetooth Low Energy)简称 BLE, 常见于各种运动手环、电子血压计等健康管理设备,Android 4.3( API 级别 18 )中引入了面向低功耗蓝牙的API支持。也就是说开发的前提是手机设备支持BLE并且系统是 Android 4.3 以上,与手机通信的蓝牙设备是低功耗蓝牙。


如果你看了官方文档上的示例,你会发现使用了 Handler、和广播进行异步通信,现在有了 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 的强大之处在于他有各种各样的操作符,可以对发布的数据源进行各种各样的处理,实际项目中有很多应用的场景。

传送门:RxBleDemo