关于android:使用更为安全的方式收集-Android-UI-数据流

6次阅读

共计 6722 个字符,预计需要花费 17 分钟才能阅读完成。

在 Android 利用中,通常须要从 UI 层收集 Kotlin 数据流,以便在屏幕上显示数据更新。同时,您也会心愿通过收集这些数据流,来防止产生不必要的操作和资源节约 (包含 CPU 和内存),以及避免在 View 进入后盾时泄露数据。

本文将会带您学习如何应用 LifecycleOwner.addRepeatingJobLifecycle.repeatOnLifecycle 以及 Flow.flowWithLifecycle API 来防止资源的节约;同时也会介绍为什么这些 API 适宜作为在 UI 层收集数据流时的默认抉择。

资源节约

无论数据流生产者的具体实现如何,咱们都 举荐 从利用的较底层级裸露 Flow<T> API。不过,您也应该保障数据流收集操作的安全性。

应用一些现存 API (如 CoroutineScope.launch、Flow<T>.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 {// 新的地位!更新地图} 
        }
        // 同样的问题也存在于:// - lifecycleScope.launch {/* 在这里从 locationFlow() 收集数据 */ }
        // - locationProvider.locationFlow().onEach { /* ... */}.launchIn(lifecycleScope)
    }
}

lifecycleScope.launchWhenStarted 挂起了协程的执行。尽管新的地位信息没有被解决,但 callbackFlow 生产者依然会继续发送地位信息。应用 lifecycleScope.launchlaunchIn API 会更加危险,因为视图会继续生产地位信息,即便处于后盾也不会进行!这种状况可能会导致您的利用解体。

为了解决这些 API 所带来的问题,您须要在视图转入后盾时手动勾销收集操作,以勾销 callbackFlow 并防止地位提供者继续发送我的项目并浪费资源。举例来说,您能够像上面的例子这样操作:

class LocationActivity : AppCompatActivity() {

    // 地位的协程监听器
    private var locationUpdatesJob: Job? = null

    override fun onStart() {super.onStart()
        locationUpdatesJob = lifecycleScope.launch {locationProvider.locationFlow().collect {// 新的地位!更新地图。} 
        }
    }

    override fun onStop() {
       // 在视图进入后盾时进行收集数据
        locationUpdatesJob?.cancel()
        super.onStop()}
}

这是一个不错的解决方案,美中不足的是有些简短。如果这个世界有一个无关 Android 开发者的普遍事实,那肯定是咱们都不喜爱编写模版代码。不用编写模版代码的一个最大益处就是——写的代码越少,出错的概率越小!

LifecycleOwner.addRepeatingJob

当初咱们境遇雷同,并且也晓得问题出在哪里,是时候找出一个解决方案了。咱们的解决方案须要: 1. 简略;2. 敌对或者说便于记忆与了解;更重要的是 3. 平安!无论数据流的实现细节如何,它都应可能应答所有用例。

事不宜迟——您应该应用的 API 是 lifecycle-runtime-ktx 库中所提供的 LifecycleOwner.addRepeatingJob。请参考上面的代码:

class LocationActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)

        // 最早在 View  处于 STARTED 状态时从数据流收集数据,并在
        // 生命周期进入 STOPPED 状态时 STOPPED(进行)收集操作。// 它会在生命周期再次进入 STARTED 状态时主动开始进行数据收集操作。lifecycleOwner.addRepeatingJob(Lifecycle.State.STARTED) {locationProvider.locationFlow().collect {// 新的地位!更新地图} 
        }
    }
}

addRepeatingJob 接管 Lifecycle.State 作为参数,并用它与传入的代码块一起,在生命周期达到该状态时,主动创立并启动新的协程 ;同时也会在生命周期低于该状态时 勾销正在运行的协程

因为 addRepeatingJob 会在协程不再被须要时主动将其勾销,因此能够防止产生勾销操作相干的模版代码。您兴许曾经猜到,为了防止意外行为,这一 API 须要在 Activity 的 onCreate 或 Fragment 的 onViewCreated 办法中调用。上面是配合 Fragment 应用的示例:

class LocationFragment: Fragment() {override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // ...
        viewLifecycleOwner.addRepeatingJob(Lifecycle.State.STARTED) {locationProvider.locationFlow().collect {// 新的地位!更新地图} 
        }
    }
}

留神: 这些 API 在 lifecycle:lifecycle-runtime-ktx:2.4.0-alpha01 库或其更新的版本中可用。

应用 repeatOnLifecycle

出于提供更为灵便的 API 以及保留调用中的 CoroutineContext 的目标,咱们也提供了 挂起函数 Lifecycle.repeatOnLifecycle 供您应用。repeatOnLifecycle 会挂起调用它的协程,并会在进出指标状态时从新执行代码块,最初在 Lifecycle 进入销毁状态时复原调用它的协程。

如果您须要在反复工作前执行一次配置工作,同时心愿工作能够在反复工作开始前放弃挂起,该 API 能够帮您实现这样的操作。示例如下:

class LocationActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)

        lifecycleScope.launch {
            // 单次配置工作
            val expensiveObject = createExpensiveObject()

            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                // 在生命周期进入 STARTED 状态时开始反复工作,在 STOPED 状态时进行
                // 对 expensiveObject 进行操作
            }

            // 当协程复原时,`lifecycle` 处于 DESTROY 状态。repeatOnLifecycle 会在
            // 进入 DESTROYED 状态前挂起协程的执行
        }
    }
}

Flow.flowWithLifecycle

当您只须要收集一个数据流时,也能够应用 Flow.flowWithLifecycle 操作符。这一 API 的外部也应用 suspend Lifecycle.repeatOnLifecycle 函数实现,并会在生命周期进入和来到指标状态时发送我的项目和勾销外部的生产者。

class LocationActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)

        locationProvider.locationFlow()
            .flowWithLifecycle(this, Lifecycle.State.STARTED)
            .onEach {// 新的地位!更新地图}
            .launchIn(lifecycleScope) 
    }
}

留神: Flow.flowWithLifecycle API 的命名以 Flow.flowOn(CoroutineContext) 为先例,因为它会在不影响上游数据流的同时批改收集上游数据流的 CoroutineContext。与 flowOn 类似的另一点是,Flow.flowWithLifecycle 也退出了缓冲区,以避免消费者无奈跟上生产者。这一特点源于其实现中应用的 callbackFlow

配置外部生产者

即便您应用了这些 API,也要小心那些可能浪费资源的热流,就算它们没有被收集亦是如此!尽管针对这些热流有一些适合的用例,然而仍要多加留神并在必要时进行记录。另一方面,在一些状况下,即便可能造成资源的节约,令处于后盾的外部数据流生产者放弃沉闷状态也会利于某些用例,如: 您须要即时刷新可用数据,而不是去获取并临时展现古老数据。您能够依据用例决定生产者是否须要始终处于沉闷状态

您能够应用 MutableStateFlowMutableSharedFlow 两个 API 中裸露的 subscriptionCount 字段来管制它们,当该字段值为 0 时,外部的生产者就会进行。默认状况下,只有持有数据流实例的对象还在内存中,它们就会放弃生产者的沉闷状态。针对这些 API 也有一些适合的用例,比方应用 StateFlowUiState 从 ViewModel 中裸露给 UI。这么做很适合,因为它意味着 ViewModel 总是须要向 View 提供最新的 UI 状态。

类似的,也能够为此类操作应用 共享开始策略 配置 Flow.stateIn 与 Flow.shareIn 操作符。WhileSubscribed() 将会在没有沉闷的订阅者时进行外部的生产者!相应的,无论数据流是 Eagerly (踊跃) 还是 Lazily (惰性) 的,只有它们应用的 CoroutineScope 还处于沉闷状态,其外部的生产者就会放弃沉闷。

留神: 本文中所形容的 API 能够很好的作为默认从 UI 收集数据流的形式,并且无论数据流的实现形式如何,都应该应用它们。这些 API 做了它们要做的事: 在 UI 于屏幕中不可见时,进行收集其数据流。至于数据流是否应该始终处于活动状态,则取决于它的实现。

在 Jetpack Compose 中平安地收集数据流

Flow.collectAsState 函数能够在 Compose 中收集来自 composable 的数据流,并能够将值示意为 State<T>,以便可能更新 Compose UI。即便 Compose 在宿主 Activity 或 Fragment 处于后盾时不会重组 UI,数据流生产者仍会放弃沉闷并会造成资源的节约。Compose 可能会遭逢与 View 零碎雷同的问题。

在 Compose 中收集数据流时,能够应用 Flow.flowWithLifecycle 操作符,示例如下:

@Composable
fun LocationScreen(locationFlow: Flow<Flow>) {

    val lifecycleOwner = LocalLifecycleOwner.current
    val locationFlowLifecycleAware = remember(locationFlow, lifecycleOwner) {locationFlow.flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED)
    }

    val location by locationFlowLifecycleAware.collectAsState()
    
    // 以后地位,能够拿它做一些操作
}

留神,您 须要记得 生命周期感知型数据流应用 locationFlowlifecycleOwner 作为键,以便始终应用同一个数据流,除非其中一个键产生扭转。

Compose 的副作用 (Side-effect) 便是必须处在 受控环境中,因而,应用 LifecycleOwner.addRepeatingJob 不平安。作为代替,能够应用 LaunchedEffect 来创立追随 composable 生命周期的协程。在它的代码块中,如果您须要在宿主生命周期处于某个 State 时从新执行一个代码块,能够调用挂起函数 Lifecycle.repeatOnLifecycle

比照 LiveData

您兴许会感觉,这些 API 的体现与 LiveData 很类似——的确是这样!LiveData 能够感知 Lifecycle,而且它的重启行为使其非常适宜察看来自 UI 的数据流。同理 LifecycleOwner.addRepeatingJobsuspend Lifecycle.repeatOnLifecycle 以及 Flow.flowWithLifecycle 等 API 亦是如此。

在纯 Kotlin 利用中,应用这些 API 能够非常天然地代替 LiveData 收集数据流。如果您应用这些 API 收集数据流,换成 LiveData (绝对于应用协程和 Flow) 不会带来任何额定的益处。而且因为 Flow 能够从任何 Dispatcher 收集数据,同时也能通过它的 操作符 取得更多功能,所以 Flow 也更为灵便。相对而言,LiveData 的可用操作符无限,且它总是从 UI 线程察看数据。

数据绑定对 StateFlow 的反对

另一方面,您会想要应用 LiveData 的起因之一,可能是它受到数据绑定的反对。不过 StateFlow 也一样!更多无关数据绑定对 StateFlow 的反对信息,请参阅 官网文档。

在 Android 开发中,请应用 LifecycleOwner.addRepeatingJobsuspend Lifecycle.repeatOnLifecycle Flow.flowWithLifecycle 从 UI 层平安地收集数据流。

正文完
 0