1、前言
最近在项目中遇到了需要同时监听四个数据变化的需求。注册两个Sensor采集a+g的数据,并获取时间戳和xyz的数据,同时完成对应的操作,这个需求需要同时监听四个数据的变化,之前使用observe()
去监听一个数据的变化,所以我想到了使用Flow来监听数据变化。
2、问题描述
在ViewModule中注册两个Sensor用于分别采集acc和gyro的数据,并创建一个工具类来监听Sensor数据的变化、初始化、注册和解注册。
class MotionGameSensorUtil(private val handleData: (Long, ArrayList<Float>) -> Unit) {
private val TAG = "MotionGameSensorUtil"
private var lastData: ArrayList<Float> = arrayListOf()
private var timestamp: Long = 0L
private var hasData: Boolean = false
private val sensorListener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
val time = event!!.timestamp
timestamp = TimeUnit.NANOSECONDS.toMillis(time) * 1000L
val x = event.values[0]
val y = event.values[1]
val z = event.values[2]
lastData.apply {
clear()
add(x)
add(y)
add(z)
}
hasData = true
postData()
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
}
}
fun init(lifecycle: Lifecycle, sensorManager: SensorManager, sensor: Sensor) {
lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
register(sensorManager, sensor)
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
unregister(sensorManager)
hasData = false
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
lifecycle.removeObserver(this)
}
})
}
fun register(sensorManager: SensorManager, sensor: Sensor) {
sensorManager.registerListener(sensorListener, sensor, SensorManager.SENSOR_DELAY_GAME)
}
fun unregister(sensorManager: SensorManager) {
sensorManager.unregisterListener(sensorListener)
}
fun postData() {
if (hasData) {
handleData(timestamp, lastData)
}
}
}
在ViewModule中创建两个工具类的实力,分别用于获取acc和gyro数据。
val gyroscopeSensorUtil: MotionGameSensorUtil by lazy {
MotionGameSensorUtil(handleData = { time, data ->
runBlocking {
//对于time、data数据操作
}
})
}
val accSensorUtil: MotionGameSensorUtil by lazy {
MotionGameSensorUtil(handleData = { time, data ->
runBlocking {
//对于time、data数据操作
}
})
}
Flow数据定义如下:
val gyroscopeData by lazy { MutableStateFlow<ArrayList<Float>>(arrayListOf()) }
val accData by lazy { MutableStateFlow<ArrayList<Float>>(arrayListOf()) }
val gyroscopeTime by lazy { MutableStateFlow(0L) }
val accTime by lazy { MutableStateFlow(0L) }
起初我的想法是同时监听四个数据,于是我想到了combine和zip两个方式来监听Flow数据流变化,于是有了以下逻辑去监听数据。
@Parcelize
data class SensorData(
var accTimestamp: Long = 0L,
var gyroTimestamp: Long = 0L,
var accData: ArrayList<Float> = arrayListOf(),
var gyroData: ArrayList<Float> = arrayListOf()
) : Parcelable
lifecycleScope.launch {
processViewModule.let {
combine(
it.accTime,
it.accData,
it.gyroscopeTime,
it.gyroscopeData
) { accTime, accData, gyroscopeTime, gyroscopeData ->
it.sensorData.accTimestamp = accTime
it.sensorData.accData = accData
it.sensorData.gyroTimestamp = gyroscopeTime
it.sensorData.gyroData = gyroscopeData
it.sensorData
}.collect{ sensorData->
//对sensorData的操作
}
}
}
这时候我发现回调的数据有很多老数据,尝试用zip来解决问题。这里先说说zip和combine的区别。
zip
:该操作符将两个或多个Flow的对应元素组合在一起。它等待每每个Flow都发出一个新元素,然后将这些元素组合成一个新的元素(通常是一个包含所有源Flow当前元素的元组),再发射这个新元素。如果某个Flow发射元素的速度比其他Flow慢,那么zip会等待,直到每个Flow都有新的元素可供组合。
combineLatest
:这个操作符也会组合多个Flow的元素,但它并不要求每个Flow同时发射新元素。每当任何一个源Flow发射一个新元素时,combinelatest都会立即使用每个Flow的最新元素(如果可用的话)来组合并发射一个新元素。这意味着,如果某个Flow更新得更频繁,那么组合的结果也会更频繁地更新。
并编写了如下代码:
lifecycleScope.launch {
processViewModule.let {
it.accTime.zip(it.accData) { accTime, accData ->
it.sensorData.accTimestamp = accTime
it.sensorData.accData = accData
it.sensorData
}.zip(it.gyroscopeTime) { sensorData, gyroscopeTime ->
it.sensorData.gyroTimestamp = gyroscopeTime
sensorData
}.zip(it.gyroscopeData) { sensorData, gyroscopeData ->
it.sensorData.gyroData = gyroscopeData
sensorData
}.collect { sensorData ->
//对sensorData的操作
}
}
}
根据log我发现collect只执行了一次。
3、问题解决
在使用四个zip操作符进行监听数据时,我发现我只用两个传感器,理论上只需要两个Flow就可以完成功能,于是有了如下代码。
val gyroEvent by lazy {
MutableStateFlow<Pair<Long, ArrayList<Float>>>(
Pair(0L, arrayListOf())
)
}
val accEvent by lazy {
MutableStateFlow<Pair<Long, ArrayList<Float>>>(
Pair(0L, arrayListOf())
)
}
lifecycleScope.launch {
processViewModule.let {
it.accEvent.zip(it.gyroEvent) { acc, gyro ->
it.sensorData.accTimestamp = acc.first
it.sensorData.accData = acc.second
it.sensorData.gyroTimestamp = gyro.first
it.sensorData.gyroData = gyro.second
it.sensorData
}.collect { sensorData ->
it.sendAccAndGyroData(sensorData)
}
}
}
至此,功能完成。
4、结语
此功能让我深入了解了Kotlin响应式编程框架以及combine与zip操作符的使用,但是为什么在监听四个数据的时候只会回调一次,这个问题还是没有想明白,有无大佬评论区解答一下,万分感谢。