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() {@Overridepublic 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>() {@Overridepublic 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>>() {@Overridepublic Observable<BluetoothGattCharacteristic> call(BluetoothGattService bleGattService) {return Observable.from(bleGattService.getCharacteristics());}}).filter(new Func1<BluetoothGattCharacteristic, Boolean>() {@Overridepublic Boolean call(BluetoothGattCharacteristic bleGattChar) {return bleGattChar.getUuid().toString().equals(UUID);}}).subscribe(new Action1<BluetoothGattCharacteristic>() {@Overridepublic 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>() {@Overridepublic 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>() {@Overridepublic void call(String receiveData) {sendTv.setText(mStringBuffer.append(receiveData).append("\n"));}
});复制代码

向蓝牙设备发送数据

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

Observable.timer(time, TimeUnit.MILLISECONDS).subscribe(new Action1<Long>() {@Overridepublic 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
本文同步于我的个人博客

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

  1. Win10 平台C#与低功耗蓝牙BLE设备通信案例

    前几天接了个单,客户要在win10电脑上做个工具软件,跟蓝牙锁设备相互通信.一开始以为是普通的蓝牙设备呢,收到客户寄来的测试设备,才发现是低功耗BLE蓝牙设备. PS:当时我研发用的台式机是没有蓝牙设 ...

  2. Android 低功耗蓝牙BLE连接通信

    目录 简介 蓝牙 4.0 BLE与蓝牙4.0的区别 BLE的特点 主要特性 技术细节 BLE的应用 BLE的体系结构 BLE设备链路层状态 就绪态 广播态 扫描态 发起态 连接状态 通信基本过程 两种 ...

  3. PyQt5之QtBluetooth模块:低功耗蓝牙BLE通信

    PyQt5之QtBluetooth模块:低功耗蓝牙BLE通信 最近使用PyQt5开发PC端工具,正巧手上有一个富芮坤的低功耗蓝牙,于是想在PC端试试与之通信,不过发现使用PyQt5开发低功耗蓝牙的教程 ...

  4. java 协议栈_深入浅出讲解低功耗蓝牙(BLE)协议栈

    详解BLE连接建立过程 https://www.cnblogs.com/iini/p/8972635.html 详解BLE 空中包格式-兼BLE Link layer协议解析 https://www. ...

  5. 蓝牙:深入浅出低功耗蓝牙(BLE)协议栈

    深入浅出低功耗蓝牙(BLE)协议栈 BLE协议栈为什么要分层?怎么理解BLE"连接"?如果BLE协议只有ATT层没有GATT层会发生什么? 协议栈框架 一般而言,我们把某个协议的实 ...

  6. 深入浅出低功耗蓝牙(BLE)协议栈,使用Ubertooth one扫描嗅探低功耗蓝牙

    BLE协议栈为什么要分层?怎么理解BLE"连接"?如果BLE协议只有ATT层没有GATT层会发生什么? 深入浅出低功耗蓝牙BLE协议栈 1. 协议栈框架 2. 如何通过无线发送一个 ...

  7. 低功耗蓝牙BLE外围模式(peripheral)-使用BLE作为服务端

    低功耗蓝牙BLE外围模式(peripheral)-使用BLE作为服务端 Android对外模模式(peripheral)的支持 从Android5.0开始才支持 关键术语和概念 以下是关键BLE术语和 ...

  8. 【Funpack】低功耗蓝牙 BLE 协议架构

    想要开发蓝牙应用,了解蓝牙协议架构是必不可少的.本文以低功耗蓝牙 BLE 为例,简要介绍 BLE 蓝牙协议架构,帮助开发者快速了解蓝牙协议概况. BLE 协议分层 BLE 协议栈主要由如下几部分组成: ...

  9. 【IoT】加密与安全:CC254x 低功耗蓝牙 BLE 之 AES-128 加密算法

    蓝牙数据是可以通过空中抓包而被抓取到的,因此需要将通信数据进行加密,即使别人截获了加密后的数据,也无法利用该数据. AES 加密原理 CC254x 支持对称加密 AES: 加密过程: 需要加密的数据 ...

最新文章

  1. python gif压缩_实用性视频转gif,压缩等常用文件工具处理及转换(含自写python工具)...
  2. Java FlameGraph 火焰图
  3. 选择Windows CE wince嵌入式操作系统 的十大理由
  4. springboot controller 访问 404
  5. 互联网日报 | 6月4日 星期五 | 蚂蚁消费金融获批开业;腾讯云四个国际数据中心同步开服;滴滴App上线“老人打车”模式...
  6. listview与adapter用法
  7. python的列表操作_在Python中列表的操作
  8. springmvc个人小结
  9. extjs中什么时候用{},什么时候用[]
  10. 【9933】单词的划分
  11. 刚做微商引流太慢怎么办?微商没有客源怎么办,微商引流需要注意什么
  12. 微商怎么引流被加精准粉?微商有效引流被加方法
  13. 面临裁员潮,更快找到新工作的秘诀
  14. 论OSPF中ASBR和ABR
  15. mysql update后可以跟两个表_update后可接两张表吗,
  16. TCP连接的建立和释放过程详解(三次握手、四次挥手)
  17. Credit Card Fraud Detection(信用卡欺诈检测相关数据集)
  18. 【源码】改进的智能局部搜索Nelder-Mead优化波能转换器
  19. BUUCTF [极客大挑战 2019] PHP
  20. layui 使用laydate动态创建多个时间选择框

热门文章

  1. Android震动vibrator系统开发全过程
  2. 修改oracle归档目录和大小
  3. 【转载】关于如何提取Exe文件中PPT源文件的几种方法
  4. Coolite(二)服务器端Alert,Confirm,Prompt
  5. MySQL查询时构建自增ID
  6. bzoj3895: 取石子(博弈论,记忆化搜索)
  7. MyBatis 关系映射XML配置
  8. laravel5.5使用sendCloud邮件服务
  9. Selenium_WebDriver操作iFrame日历框和复选框_Java
  10. Spring+Mybatis多数据源配置