在 Android 应用中,通常需要从 UI 层收集 Kotlin 数据流,以便在屏幕上显示数据更新。同时,您也会希望通过收集这些数据流,来避免产生不必要的操作和资源浪费 (包括 CPU 和内存),以及防止在 View 进入后台时泄露数据。
本文将会带您学习如何使用 LifecycleOwner.addRepeatingJob、Lifecycle.repeatOnLifecycle 以及 Flow.flowWithLifecycle API 来避免资源的浪费;同时也会介绍为什么这些 API 适合作为在 UI 层收集数据流时的默认选择。
资源浪费
无论数据流生产者的具体实现如何,我们都推荐从应用的较底层级暴露 Flow API。不过,您也应该保证数据流收集操作的安全性。
使用一些现存 API (如 CoroutineScope.launch、Flow.launchIn 或 LifecycleCoroutineScope.launchWhenX) 收集基于 channel 或使用带有缓冲的操作符 (如 buffer、conflate、flowOn 或 shareIn) 的冷流的数据是不安全的,除非您在 Activity 进入后台时手动取消启动了协程的 Job。这些 API 会在内部生产者在后台发送项目到缓冲区时保持它们的活跃状态,而这样一来就浪费了资源。
注意:冷流是一种数据流类型,这种数据流会在新的订阅者收集数据时,按需执行生产者的代码块。
例如下面的例子中,使用 callbackFlow 发送位置更新的数据流:
// 基于 Channel 实现的冷流,可以发送位置的更新
fun FusedLocationProviderClient.locationFlow() = callbackFlow<Location> {
val callback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult?) {
result ?: return
try { offer(result.lastLocation) } catch(e: Exception) {}
}
}
requestLocationUpdates(createLocationRequest(), callback, Looper.getMainLooper())
.addOnFailureListener { e ->
close(e) // 在出现异常时关闭 Flow
}
// 在 Flow 收集结束时进行清理操作
awaitClose {
removeLocationUpdates(callback)
}
}
注意:callbackFlow 内部使用 channel 实现,其概念与阻塞队列十分类似,并且默认容量为 64。
使用任意前述 API 从 UI 层收集此数据流都会导致其持续发送位置信息,即使视图不再展示数据也不会停止!示例如下:
class LocationActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 最早在 View 处于 STARTED 状态时从数据流收集数据,并在
// 生命周期进入 STOPPED 状态时 SUSPENDS(挂起)收集操作。
// 在 View 转为 DESTROYED 状态时取消数据流的收集操作。
lifecycleScope.launchWhenStarted {
locationProvider.locationFlow().collect {
// 新的位置!更新地