由于您的要求是在后台保持蓝牙连接,因此您应该在应用程序进程中运行一个前台服务。这将确保您的应用程序进程保持活动状态,但需要在手机/平板电脑的顶部栏中显示图标。
无论您是否将BLE代码放在此服务类中,对于功能都没有问题。
当然,有许多方法可以实现良好的体系结构,但以下是我的方法。
我的方法是拥有一个单例类,处理所有BLE扫描、连接和GATT交互(从现在开始称为Manager)。由于某些BLE操作需要Android上下文,一个好的方法是使用Application上下文作为上下文。要么按照Static way to get 'Context' on Android?获取任何时间的上下文,要么子类化Application类,并从其onCreate
调用一些初始化方法在您的Manager中传递上下文。现在,您可以将所有BLE功能完全与Android Service/Activity/Application分离。只要您将所有内容保留在相同进程中,我真的不认为使用绑定服务等有任何意义。
要实现扫描功能,您可以在Manager中编写一个方法,创建Scanner对象。将Scanner类编写为Android的BLE扫描器包装器,并公开用于启动/停止扫描的方法。当您创建一个Scanner时,该方法应该还会使用一个接口作为参数,用于回调(设备报告和错误)。例如,在Activity中可以使用此类。只需确保在Activity的onStop
方法中停止扫描器,以避免对象泄漏。
创建包装的自定义扫描仪对象有几个原因,而不是直接在 Activity 中使用 Android 的 BLE 扫描 API。首先,您可以对广告数据应用适当的过滤和处理,以便处理您的外围设备并在自定义广告报告回调中显示高级参数(从广告数据解码)。此外,管理器应该在 Bluetooth 启动/停止/重新启动时监听广播,并跟踪所有启动的扫描仪,以便在 Bluetooth 重新启动时无缝重启扫描仪(如果您需要此功能)。您还可能希望跟踪所有扫描开始/停止的时间戳,以便解决 Nougat 中的新限制,即每 30 秒最多只能进行 5 次扫描。
当您想连接到您的外围设备时,请使用类似的方法。例如,您可以让 Manager 创建具有启动/停止连接方法和回调接口以报告事件的 Device 对象。对于每个受支持的功能(例如读取某个远程值),您应该公开一个启动请求的方法,并具有在结果到达时调用的回调。然后,您的 Manager 和 Device 类会处理 GATT 相关内容(包括将所有 GATT 请求加入队列,因此您一次只有一个未完成的 GATT 操作)。请确保您始终可以放弃或忽略结果,例如如果调用 Activity 的 onStop
或 onDestroy
方法。
由于您可能希望在设备断开连接的情况下自动重新连接,因此应在建立连接时使用 autoConnect
标志,并将其设置为 true,以确保此功能正常工作。同样,Manager 应该跟踪所有活动的 Device 对象,并在 Bluetooth 重新启动时自动重新创建 BluetoothGatt 对象。
为了能够显示不同类型的UI内容(例如在Activity中自动显示警告消息,当蓝牙关闭时,在蓝牙开启时删除它),您应该能够向Manager注册Listeners。 在Manager中有一个方法可以注册/取消注册监听器(实际上只是一个回调)对象,跟踪所有的监听器,并当蓝牙状态发生变化时调用所有的监听器。然后在Activity的
onStart
中注册监听器,在
onStop
中取消注册。对于您设备的BLE通知,如果适用,可以采用类似的方法。
剩下的问题是如何处理不同的线程。正如您所知道的,Android API中大多数BLE回调都在Binder线程上发生,因此您可能无法从它们更新UI。如果您的应用程序除了主线程以外没有使用任何其他线程,例如可以将Manager中所有回调的调用都发布到主线程,或者当来自Android的BLE栈的回调到达时,直接移动到主线程(但要注意像
这样的问题)。只要确保从不同的线程永远不会触摸相同的变量即可。
此外,如果您的目标API为23或更高版本,则需要UI代码以让用户授予位置权限以便能够开始扫描。我建议您在UI代码中实现此功能,而不是在Manager中实现,或者在Manager中实现一些“包装器”或辅助方法来完成此任务。