android - 我从蓝牙中丢失了在活动之间移动和使用服务的数据
问题描述
我正在构建一个通过蓝牙 spp 连接与外部设备交换数据的应用程序。在此处搜索了一下如何保持活动之间的连接后,我发现更好的方法是实现一个服务并将其绑定到活动。我认为我编写的代码是正确的,但是当我从第一个活动移动到第二个活动并向外部设备发送请求时,我从读取缓冲区中丢失了一些答案消息。如果我在发出请求之前等待大约 5 秒钟,它就可以正常工作。
这是我的课程,我正在使用一个名为BlueFlow的蓝牙库:
class BluetoothService : Service() {
private val btChannel: BtChannel by inject()
inner class LocalBinder: Binder() {
val bindService = this@BluetoothService
}
override fun onBind(intent: Intent?): IBinder {
return LocalBinder()
}
fun sendRequest(data: ByteArray): Boolean {
return btChannel.send(data)
}
fun readChannel(): LiveData<ByteArray>? {
return btChannel.read()?.cancellable()?.asLiveData()
}
}
class BtChannel(private val blueFlow: BlueFlow) {
fun read(): Flow<ByteArray>? {
return blueFlow.getIO()?.readByteArrayStream()
}
fun send(data: ByteArray): Boolean {
return blueFlow.getIO()?.send(data) ?: false
}
}
// This is the readByteArrayStream function from the blueFlow library
@ExperimentalCoroutinesApi
fun readByteArrayStream(
delayMillis: Long = 1000,
minExpectedBytes: Int = 2,
bufferCapacity: Int = 1024,
readInterceptor: (ByteArray) -> ByteArray? = { it }
): Flow<ByteArray> = channelFlow {
if (inputStream == null) {
throw NullPointerException("inputStream is null. Perhaps bluetoothSocket is also null")
}
val buffer = ByteArray(bufferCapacity)
val byteAccumulatorList = mutableListOf<Byte>()
while (isActive) {
try {
if (inputStream.available() < minExpectedBytes) {
delay(delayMillis)
continue
}
val numBytes = inputStream.read(buffer)
val readBytes = buffer.trim(numBytes)
if (byteAccumulatorList.size >= bufferCapacity)
byteAccumulatorList.clear()
byteAccumulatorList.addAll(readBytes.toList())
val interceptor = readInterceptor(byteAccumulatorList.toByteArray())
if (interceptor == null)
delay(delayMillis)
interceptor?.let {
offer(it)
byteAccumulatorList.clear()
}
} catch (e: IOException) {
byteAccumulatorList.clear()
closeConnections()
error("Couldn't read bytes from flow. Disconnected")
} finally {
if (bluetoothSocket?.isConnected != true) {
byteAccumulatorList.clear()
closeConnections()
break
}
}
}
}.flowOn(Dispatchers.IO)
class FirstActivity: AppCompatActivity() {
private lateinit var viewModel: FirstViewModel
private lateinit var mBluetoothService: BluetoothService
private var mBound = false
private val serviceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as BluetoothService.LocalBinder
mBluetoothService = binder.bindService
mBound = true
}
override fun onServiceDisconnected(name: ComponentName?) {
mBound = false
}
}
override fun onStart() {
super.onStart()
startBluetoothService()
setObservers()
}
override fun onStop() {
super.onStop()
unbindService(serviceConnection)
mBound = false
removeObservers()
}
private fun startBluetoothService() {
val intent = Intent(this, BluetoothService::class.java)
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
}
private fun setObservers() {
viewModel.hours.observe(this) {hoursTv?.text = it }
mBluetoothService?.readChannel()?.observe(this, { viewModel.getData(it) })
}
private fun removeObservers() {
viewModel.hours.removeObservers(this@FirstActivity)
mBluetoothService?.readChannel()?.removeObservers(this@FirstActivity)
}
}
class FirstViewModel: ViewModel() {
val hours: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
fun getData(bytes: ByteArray) = viewModelScope.launch {
getDataSafeCall(bytes)
}
@ExperimentalUnsignedTypes
private fun getDataSafeCall(bytes: ByteArray) {
PacketFrame.decodePacket(bytes.toUByteArray(), bytes.size).onEach { updateData(it) }.launchIn(viewModelScope)
}
fun updateData(packet: HashMap<PacketHeader, UByteArray>) {
packet.forEach { packet ->
when (packet.key) {
PacketHeader.Hour -> {
hours.value = PacketFrame.payloadToString(packet.value)
}
else -> {
}
}
}
}
}
class SecondActivity: AppCompatActivity() {
//same as FirstActivity
}
class SecondViewModel: ViewModel() {
// same as FirstViewModel
}
PacketFrame 是一个单例。我用它来组合使用自定义协议从蓝牙接收到的数据包。
你有什么建议吗?
更新:
我正在阅读文档,试图找出问题所在,我发现了这一点:
流是冷流 是类似于序列的冷流——流构建器中的代码在收集流之前不会运行。
但我也发现了这个:
channelFlow 创建一个冷流实例,其中包含发送到通过 ProducerScope 提供给构建器代码块的 SendChannel 的元素。它允许元素由在不同上下文或同时运行的代码生成。结果流是冷的,这意味着每次将终端运算符应用于结果流时都会调用该块。此构建器确保线程安全和上下文保存,因此提供的 ProducerScope 可以从不同的上下文中同时使用。一旦块中的代码及其所有子代码完成,生成的流程就会完成。
我的理解是 channelFlow (用于库中的 readByteArrayStream 函数)继续运行,直到消费者还活着并请求元素。
在我的代码中,我在 ViewModel 中启动协程,当我调用第二个 Activity 时它没有被清除,因为它保留在堆栈中,因此第一个 ViewModel 上的函数继续从蓝牙接收数据,直到在第二个 ViewModel 中创建流程和使用 LiveData e 观察它来消耗它。
你怎么看待这件事?关于如何解决的任何建议?
解决方案
我将尝试提出一些建议,尽管很难知道您要实现的目标以及为什么要在读数中间启动新活动。
我建议您将readChannel()
返回类型更改为SharedFlow
并将readByteArrayStream()
函数转换为 SharedFlow 以及使用shareIn()调用。然后按原样收集它而不将其转换为实时数据。该库是之前构建的StateFlow
并被SharedFlow
引入,因此这些新的流类型在这种情况下可能会派上用场。
另一种方法可能是SharedFlow
再次使用将充当事件总线并重新发送通过readByteArrayStream()
查看此处收集的数据以获取此类 EventBus 实现的示例。
这就是我现在所能建议的。我真的不确定您的用例,如果可以更好地理解,我想检查一个与相关问题相关的示例项目。
推荐阅读
- python - 无法从列表中删除特定元素 [Python]
- java - 使用 Process Builder 执行命令时获得权限被拒绝
- swift - 如何在 RealityKit 中实现广告牌?
- python - 调整另一个网站的网页抓取代码
- javascript - 如何从另一个单文件组件VueJS触发单文件组件中的方法
- javascript - 多个嵌入到一条消息中
- java - 通过引用子索引从 Firebase 检索数据
- php - 未定义变量:dataP(查看:D:\wamp\www\BTPl\resources\views\salarie\payer.blade.php)
- highcharts - Highcharts - 当 useHTML=true 时 StackLabels 显示在工具提示上
- unit-testing - 使用 Jest 进行单元测试。TypeError:无法读取未定义的属性“getters”