19年4月25日更新

前言:一个基于rxjava2的AndroidBLe库

github
19年4月25日更新
V1.3:第一版
19年5月10日更新
V1.4:修复了在Android4.4上扫描的Api兼容性问题
19年12/04更新
V1.6:增加对多包指令的更好支持
19年12/30日更新
V1.8:有一种场景是,当手机端连接上蓝牙设备后,蓝牙设备立即主动发送了一条信息,这种情况下库也是支持的,但是之前没有特殊说明,在连接成功后的监听中是可以收到的,具体看下面的第五项,(感觉自己写的有点乱。。好像只有我自己能看懂一样。。。。好吧本来估计也就我自己在用)
另外受不了bintray了。每次更新都一堆网络错误,实在废时间,转投jitpack,所以依赖方式变了,不过不影响之前的使用
目的:

  • 保持代码更好的可读性
  • 控制代码的复杂度
  • 更优雅的处理异步

怎么体现?

举个栗子?

就拿扫描来说,在没有使用rx时它可能是这样的

scanner.startScan(filters, settings, scanCallback)

callback:

 override fun onScanResult(callbackType: Int, result: android.bluetooth.le.ScanResult?) {
     // 解析
    }

如果你需要对低版本兼容添加一些过滤规则 它可能是这样的:

 override fun onScanResult(callbackType: Int, result: android.bluetooth.le.ScanResult?) {
     if(xx == xx){
		//...
	 }
    }

如果你需要对扫描进行控时 它可能是这样的:

Handler().postDelayed({
            scanner.stop(scanCallback)
        },5000)
        scanner.startScan(filters, settings, scanCallback)

你甚至需要有其他更多的需求,在子线程开启扫描?等等等等就不一一列举了
但是现在使用RxAndroidBluetooth,它是这样的:

	 bluetoothController!!
             .startLeScan()
            .subscribe(
                { checkScanResult(it) },
                { error -> checkError(error) },
                { Log.d(tag, "扫描完成") })

对低版本兼容过滤?一个filter操作符解决

bluetoothController!!
             .startLeScan()
            .filter { response ->
                response.getDevice()?.name == "abc"
            }
            .subscribe(
                { checkScanResult(it) },
                { error -> checkError(error) },
                { Log.d(tag, "扫描完成") })

对扫描控制时间?一个timer操作符解决

 bluetoothController!!
         	.startLeScan()
            .timer(6000, TimeUnit.MILLISECONDS)
            .filter { response ->
                response.getDevice()?.name == "abc"
            }
            .subscribe(
                { checkScanResult(it) },
                { error -> checkError(error) },
                { Log.d(tag, "扫描完成") })

开启子线程扫描?

  bluetoothController!!
             .startLeScan()
            .timer(6000, TimeUnit.MILLISECONDS)
            .filter { response ->
                response.getDevice()?.name == "abc"
            }
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                { checkScanResult(it) },
                { error -> checkError(error) },
                { Log.d(tag, "扫描完成") })

其他各种骚操作?rxjava操作符多着呢

 bluetoothController!!
             .startLeScan()
            .timer(6000, TimeUnit.MILLISECONDS)
            .filter { response ->
                !TextUtils.isEmpty(response.getDevice()?.name)
            }
            .map {
                it.getDevice()
            }
           .subscribeOn(Schedulers.io())
           .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                {
                    checkScanResult(it)
                },
                { error -> Log.d(tag, "扫描出错$error") },
                { Log.d(tag, "扫描完成") })

我不会rxjava怎么办,能使用吗?那不顺势学习一波?带着需求学习,事半功倍


上面只是简单的介绍了使用RxAndroidBluetooth的一些简便之处,下面介绍详细使用方法

依赖

将jitpack添加到项目的build.gradle中

allprojects {
	repositories {
		...
		maven { url 'https://www.jitpack.io' }
	}
}

将库的依赖添加到module的build.gradle中

implementation 'com.github.duoshine:AndroidBluetoothPro:1.8'
初始化
private var bluetoothController: BluetoothWorker? = null


		bluetoothController = BluetoothController
				.Builder(this)
                .setNotifyUuid(notifyUUID)
                .setServiceUuid(serviceUUID)
                .setWriteUuid(writeUuid)
                .build()

tips:所有可操作Api都在BluetoothWorker中.下面挨个介绍,使用不分先后顺序

1.startLeScan

开启扫描,如果你的设备>=6.0请自行处理好定位权限,代码:

	scanDispose = bluetoothController!!
            .startLeScan()
            .timer(6000, TimeUnit.MILLISECONDS)
			.subscribe(
                { checkScanResult(it) },
                { error -> checkError(error) },
                { Log.d(tag, "扫描完成") })

startLeScan支持添加过滤条件,但是只支持API21及以上的设备扫描使用(调用者不需要维护版本差异,低于API21的设备RxAndroidBluetooth不会使用过滤条件,即使你传递了过滤规则)
timer(非rxjava原生的静态操作符)定时扫描,如果不使用则一直扫描,直到dispose调用

扫描结果:ScanResult,这个对象需要介绍一下,它所支持的参数随着设备的等级提升而提升,对于API21一下的设备,稳定输出的只有(BluetoothDevice&rssi&scanRecord),使用时请注意

停止扫描

scanDispose?.dispose()

ps:每次扫描任务之前都需要.dispose(),否则你将开启两个扫描任务,这点很容易理解和rxjava+retrofit使用是一样的,你开启了两个request

2.writeOnce

写操作-适用于单包指令,代码:

 bluetoothController!!
            .writeOnce(byteArray)
		     .subscribe(
                { response -> checkResult(response) },
                { error -> checkError(error) }
            )

使用它你只需要处理Response就可以了,Response的处理极为简单,只需要一个通用的解析函数即可:

 private fun checkResult(response: Response) {
    when (response.code) {
        BluetoothWriteProfile.writeSucceed -> Log.d(tag, "写入成功")
        BluetoothWriteProfile.writeFail -> Log.d(tag, "写入失败")
        BluetoothWriteProfile.characteristicChanged -> Log.d(tag, "收到新值-${Arrays.toString(response.data)}")
    }
}

3.writeAuto

写操作-适用于多包指令,它的表现形式是自动发送,接收一个list集合

  bluetoothController!!
            .writeAuto(list)
            .subscribe(
                { response -> checkResult(response) },
                { error -> checkError(error) }
            )

我通常不建议使用此函数来执行写操作,它的执行原理是写入成功即发送下一包,它的结果处理:

private fun checkResult(response: Response) {
    when (response.code) {
        BluetoothWriteProfile.writeSucceed -> Log.d(tag, "写入成功")
        BluetoothWriteProfile.writeFail -> Log.d(tag, "写入失败")
        BluetoothWriteProfile.characteristicChanged -> Log.d(tag, "收到新值-${Arrays.toString(response.data)}")
    }
}

4.writeNext

写操作-适用于多包指令,它的表现形式是调用者决定是否发送下一包,接收一个list集合

 bluetoothController!!
            .writeNext(list)
            .doOnNext(Function { byte ->
                BluetoothNextProfile.next
            })
            .subscribe(
                { response -> checkResult(response) },
                { error -> checkError(error) }
            )

使用此函数你只需要实现doOnNext(非rxjava原生,而是RxAndroidBluetooth的),它接收一个Function<ByteArray,Int>,输入类型是当前包返回的结果,调用者也许需要对此远程设备返回的数据进行效验?解密?或其他操作来决定是否继续发送下一包,请查看BluetoothNextProfile中的功能码,它支持重发等其他操作

5.connect

连接远程设备

 connectDisposable = bluetoothController!!
            .connect("xx:xx:xx:xx:xx:xx")
            .auto()
            .timer(6000, TimeUnit.MILLISECONDS)
            .subscribe(
                { response -> checkResultState(response) },
                { error -> checkError(error) }
            )

connect支持断开自动连接(非手动,如调用dispose后则不会重连),你只需要一个auto即可支持断开自动重连
connect支持连接超时限制,你只需要一个timer操作符即可实现
扫描结果处理(更多功能码请参考BluetoothConnectProfile):

private fun checkResultState(response: Response) {
    when (response.code) {
        BluetoothConnectProfile.connected -> Log.d(tag, "连接成功")
        BluetoothConnectProfile.disconnected -> Log.d(tag, "断开连接")
        BluetoothConnectProfile.connectTimeout -> Log.d(tag, "连接超时")
        BluetoothConnectProfile.enableNotifySucceed -> Log.d(tag, "启用通知特征成功")
        BluetoothConnectProfile.enableNotifyFail -> Log.d(tag, "启用通知特征失败")
        BluetoothConnectProfile.serviceNotfound -> Log.d(tag, "未获取到对应uuid的服务特征")
        BluetoothConnectProfile.notifyNotFound -> Log.d(tag, "未获取到对应uuid的通知特征")
        BluetoothConnectProfile.reconnection -> Log.d(tag, "重连中")
    }
}

断开连接:

connectDisposable?.dispose()

note:如果手机端连接ble设备后,ble设备主动上报了一条信息,那么这里是可以接收到的,只要在上面的 when表达式中多添加一个case即可,功能码是BluetoothWriteProfile.characteristicChanged,演示代码如下

/**
 * ble状态监听
 */
private fun checkResultState(response: Response) {
    var msg = ""
    when (response.code) {
        BluetoothConnectProfile.connected -> msg = "连接成功"
        BluetoothConnectProfile.disconnected -> msg = "断开连接"
        BluetoothConnectProfile.connectTimeout -> msg = "连接超时"
        BluetoothConnectProfile.enableNotifySucceed -> msg = "启用通知特征成功"
        BluetoothConnectProfile.enableNotifyFail -> msg = "启用通知特征失败"
        BluetoothConnectProfile.serviceNotfound -> msg = "未获取到对应uuid的服务特征"
        BluetoothConnectProfile.notifyNotFound -> msg = "未获取到对应uuid的通知特征"
        BluetoothConnectProfile.reconnection -> msg = "重连中"
        //当code为18时  是收到设备主动上传的数据
        else -> checkResult(response)
    }
        Log.d(tag, msg)
}

这里我没有具体使用BluetoothWriteProfile.characteristicChanged,而是使用了else,但是为了严谨你们还是认真一点写。。。

ps:每次连接任务之前最好都需要.dispose(),防止上次连接没有完成,否则你将开启两个连接任务

6.isEnabled

蓝牙是否启用

bluetoothController!!.isEnabled()

7.device

获取gatt对应的远程设备(不处于连接中也可以调用) 这个设备可能是当前正在连接的设备或是上一次连接的设备

  bluetoothController!!
            .device()
            .subscribe(
            {device-> Log.d(tag, "$device")},
            {error-> Log.d(tag, "$error")},
            { Log.d(tag, "完成")})

8.enable

开启蓝牙

bluetoothController!!.enable()

note

  • 你可能需要在不需要扫描及断开连接的地方合适地调用dispose,这和平时使用rxjava是一样的,避免内存泄漏

  • 你如果不在subscribe中处理onError,那么它将由Android捕获,RxAndroidBluetooth维护的异常都在这个包内

    duoshine.rxandroidbluetooth.exception

  • RxAndroidBluetooth的队列不会维护所有指令,它仅仅只维护单条指令(多包的单条指令),如果在并发环境下,它永远都将只处理后一条指令,在并发的环境下它并不能很好的处理多条指令(多包的单条指令),所以调用者如果是并发处理多包指令,请做好测试!!!,单包指令则没有问题(比如开启线程执行心跳包指令)

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐