【Kotlin】【Flow】使用zip和combine监听数据变化

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操作符的使用,但是为什么在监听四个数据的时候只会回调一次,这个问题还是没有想明白,有无大佬评论区解答一下,万分感谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JD_George

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值