从Android UI收集流的更安全方法

这篇具有很好参考价值的文章主要介绍了从Android UI收集流的更安全方法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

从Android UI收集流的更安全方法,jetpack compose,android,ui,kotlin

从Android UI收集流的更安全方法

在安卓应用中,通常从UI层收集Kotlin flows以显示屏幕上的数据更新。但是,为了确保不做过多的工作、浪费资源(包括CPU和内存)或在视图转到后台时泄漏数据,您需要收集这些flows

在本文中,您将学习如何使用Lifecycle.repeatOnLifecycleFlow.flowWithLifecycle API来保护资源,以及为什么它们是UI层flow收集的良好默认值。

浪费资源
建议从应用程序层次结构的较低层公开Flow<T> API,而不考虑flow生产者实现细节。但是,您还应该安全地收集它们。

使用通道支持的cold flow或使用缓冲器(如bufferconflateflowOnshareIn)的运算符不安全,无法与某些现有的API(例如CoroutineScope.launchFlow<T>.launchInLifecycleCoroutineScope.launchWhenX)一起收集,除非当活动转到后台时手动取消启动协程的Job。这些API将保持底层flow的生产者活动状态,同时在后台向缓冲区发出项,从而浪费资源。

注:cold flow是一种类型的flow,当新的订阅者收集时,将按需执行代码块。

例如,考虑使用callbackFlow发出位置更新的以下flow

// Implementation of a cold flow backed by a Channel that sends Location updates
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) // in case of exception, close the Flow
        }
    // clean up when Flow collection ends
    awaitClose {
        removeLocationUpdates(callback)
    }
}

注意:在内部,callbackFlow使用通道,该通道在概念上非常类似于阻止队列,并且默认容量为64个元素。

使用任何前述的API从UI层收集这个flow,即使视图没有在UI中显示它们,也会持续不断地发出位置!请参见下面的示例:

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

        // Collects from the flow when the View is at least STARTED and
        // SUSPENDS the collection when the lifecycle is STOPPED.
        // Collecting the flow cancels when the View is DESTROYED.
        lifecycleScope.launchWhenStarted {
            locationProvider.locationFlow().collect {
                // New location! Update the map
            } 
        }
        // Same issue with:
        // - lifecycleScope.launch { /* Collect from locationFlow() here */ }
        // - locationProvider.locationFlow().onEach { /* ... */ }.launchIn(lifecycleScope)
    }
}

使用lifecycleScope.launchWhenStarted挂起协程执行。新位置将不会被处理,但callbackFlow生产者仍将发送位置。而使用lifecycleScope.launchlaunchIn API更加危险,因为即使视图在后台运行,它仍然会继续消耗位置。这可能导致应用程序崩溃。

要解决这些API的问题,您需要在视图进入后台时手动取消收集以取消callbackFlow,并避免位置提供程序发出项并浪费资源。例如,可以执行以下操作:

class LocationActivity : AppCompatActivity() {

    // Coroutine listening for Locations
    private var locationUpdatesJob: Job? = null

    override fun onStart() {
        super.onStart()
        locationUpdatesJob = lifecycleScope.launch {
            locationProvider.locationFlow().collect {
                // New location! Update the map
            } 
        }
    }

    override fun onStop() {
        // Stop collecting when the View goes to the background
        locationUpdatesJob?.cancel()
        super.onStop()
    }
}

这是一个不错的解决方案,但这就是样板代码了,我的朋友们!如果说有一条关于Android开发者的普遍真理,那就是我们非常讨厌写样板代码。不需要写样板代码的最大好处之一就是代码量减少了,出错的机率也因此降低了!

Lifecycle.repeatOnLifecycle

既然我们都明白问题所在,现在是时候想出一个解决方案了。解决方案需要满足三个条件:1)简单易行,2)用户友好或易于记忆/理解,3)更重要的是:安全!不管具体实现细节如何,它都应该适用于所有用例。

不再多说了,你应该使用的API是Lifecycle.repeatOnLifecycle,它可以在lifecycle-runtime-ktx库中找到。

请注意:这些API需要lifecycle-runtime-ktx库2.4.0或更高版本才能使用。

看一看下面的代码:

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

        // Create a new coroutine since repeatOnLifecycle is a suspend function
        lifecycleScope.launch {
            // The block passed to repeatOnLifecycle is executed when the lifecycle
            // is at least STARTED and is cancelled when the lifecycle is STOPPED.
            // It automatically restarts the block when the lifecycle is STARTED again.
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Safely collect from locationFlow when the lifecycle is STARTED
                // and stops collection when the lifecycle is STOPPED
                locationProvider.locationFlow().collect {
                    // New location! Update the map
                }
            }
        }
    }
}

repeatOnLifecycle是一个挂起函数,它以Lifecycle.State作为参数,当生命周期达到该状态时,它会自动创建并启动一个新的协程,并取消正在执行该块的协程,当生命周期低于该状态时。

这避免了任何样板代码,因为当不再需要协程时,repeatOnLifecycle会自动执行取消协程的相关代码。正如你所猜想的那样,建议在activity的onCreate或fragment的onViewCreated方法中调用此API,以避免意外行为。请参考下面使用fragment的示例:

class LocationFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // ...
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                locationProvider.locationFlow().collect {
                    // New location! Update the map
                }
            }
        }
    }
}

重要提醒:在片段中触发UI更新时应始终使用viewLifecycleOwner,但DialogFragments有时可能不存在View。对于DialogFragments,您可使用lifecycleOwner

请注意:这些API在androidx.lifecycle:lifecycle-runtime-ktx:2.4.0库及更高版本中提供。

实质是repeatOnLifecycle将挂起调用的协程,在生命周期的进入和离开目标状态时重新启动块的新协程,并在生命周期销毁时恢复调用协程。最后一点非常重要:只有在生命周期销毁时,调用repeatOnLifecycle的协程才会恢复执行。

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

        // Create a coroutine
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.RESUMED) {
                // Repeat when the lifecycle is RESUMED, cancel when PAUSED
            }

            // `lifecycle` is DESTROYED when the coroutine resumes. repeatOnLifecycle
            // suspends the execution of the coroutine until the lifecycle is DESTROYED.
        }
    }
}

视觉图表

回到起点,通过使用lifecycleScope.launch启动的协程直接收集locationFlow是危险的,因为即使View在后台运行时,收集仍将继续发生。
repeatOnLifecycle可防止因资源浪费和应用程序崩溃而停止和重新启动流程收集,当生命周期进入和退出目标状态时。

从Android UI收集流的更安全方法,jetpack compose,android,ui,kotlin

Flow.flowWithLifecycle

当您只有一个要收集的Flow时,也可以使用Flow.flowWithLifecycle操作符。此API在幕后使用repeatOnLifecycle API,并在Lifecycle移动到目标状态时发出项目并取消底层生产者。

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

        // Listen to one flow in a lifecycle-aware manner using flowWithLifecycle
        lifecycleScope.launch {
            locationProvider.locationFlow()
                .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
                .collect {
                    // New location! Update the map
                }
        }
        
        // Listen to multiple flows
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // As collect is a suspend function, if you want to collect
                // multiple flows in parallel, you need to do so in 
                // different coroutines
                launch {
                    flow1.collect { /* Do something */ }   
                }
                
                launch {
                    flow2.collect { /* Do something */ }
                }
            }
        }
    }
}

注意:此 API 名称取决于 Flow.flowOn(CoroutineContext) 操作,因为 Flow.flowWithLifecycle 改变了用于收集上游流的 CoroutineContext,而不影响下游。类似于 flowOnFlow.flowWithLifecycle 添加了一个缓冲区,以防用户未跟上生产者的步伐。这是由于它的实现使用了 callbackFlow

配置底层生产者

即使使用这些 API,也要注意可能会浪费资源的热流,即使没有任何人收集它们!也有一些合法的用例,但请谨记并进行必要的文档记录。即使浪费资源,使底层流生产者保持活动状态,对某些用例可能会有益处:可以立即获得新数据,而不是赶上并暂时显示陈旧数据。根据用例决定生产者是否需要始终处于活动状态。

MutableStateFlowMutableSharedFlow API 公开了一个 subscriptionCount 字段,您可以使用它来在 subscriptionCount 为零时停止底层生产者。默认情况下,只要持有流实例的对象在内存中,它们就会保持生产者处于活动状态。不过有一些合法的用例,例如,通过 StateFlow 从 ViewModel 公开到 UI 的 UiState。这是可以的!这种用例要求 ViewModel 始终向 View 提供最新的 UI 状态。

类似地,Flow.stateIn Flow.shareIn 操作员可以配置用于此的共享开始政策。WhileSubscribed() 将在没有活动观察者时停止底层生产者!相反,Eagerly 或 Lazily 将使底层生产者保持处于活动状态,只要它们使用的CoroutineScope处于活动状态。

注意:本文展示的 API 是从 UI 收集流的良好默认值,无论流实现细节如何,都应该使用这些 API。这些 API 做他们应该做的事情:如果 UI 不在屏幕上可见,则停止收集。如果应始终处于活动状态,则由流实现决定。

在 Jetpack Compose 中进行安全的 Flow 收集

如果您正在使用 Jetpack Compose 构建 Android 应用程序,请使用 collectAsStateWithLifecycle API 以生命周期感知的方式从 UI 中收集流。

collectAsStateWithLifecycle 是一个可组合函数,它以生命周期感知的方式从流中收集值,并将最新值表示为 Compose State。每当发生新的流发射时,这个 State 对象的值就会更新。这会导致所有 Composition 中 State.value 的使用都被重新编排。

默认情况下,collectAsStateWithLifecycle 使用Lifecycle.State.STARTED启动和停止从流中收集值。这发生在生命周期移动进入和退出目标状态时。此生命周期状态是您可以在 minActiveState 参数中配置的。

以下代码片段展示了此 API 的实际运用:

@Composable
fun LocationUI(locationFlow: Flow<Location>) {

    val location by locationFlow.collectAsStateWithLifecycle()

    // Current location, do something with it
}

与LiveData的比较

您可能已经注意到,此API的行为类似于LiveData,这是正确的!LiveData了解Lifecycle,其重新启动行为使其非常适合从UI观察数据流。Lifecycle.repeatOnLifecycleFlow.flowWithLifecycle的情况也是如此!

在仅限Kotlin的应用程序中使用这些API收集flows是LiveData的自然替代品。如果您使用这些API来收集流,则LiveData没有比协程和flow更多的优势。此外,flows更灵活,因为它们可以从任何调度程序中收集,并且可以使用所有其操作符进行运行。与LiveData相反,LiveData的可用运算符有限,并且其值始终从UI线程观察。

在数据绑定中支持StateFlow

另一方面,您可能正在使用LiveData的原因是它受数据绑定的支持。Well,StateFlow也是如此!有关StateFlow在数据绑定中的支持的更多信息,请查看官方文档。

https://developer.android.com/topic/libraries/data-binding/observability#stateflow

使用Lifecycle.repeatOnLifecycleFlow.flowWithLifecycle API以安全地从Android的UI层中收集flows。文章来源地址https://www.toymoban.com/news/detail-578000.html

到了这里,关于从Android UI收集流的更安全方法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android Jetpack Compose 用计时器demo理解Compose UI 更新的关键-------状态管理(State)

    我们都知道了Compose使用了声明式的开发范式,在这样的范式中,UI的职责更加的单一,只会对数据状态的变化作出反应,如果数据状态没有发生变化,则UI就永远不会自行的改变。假如我们把Composable的执行看成是一个函数的运算的话,那么状态就是函数的参数,输出就是生成

    2024年02月09日
    浏览(53)
  • Android Jetpack Compose 用计数器demo理解Compose UI 更新的关键-------状态管理(State)

    我们都知道了Compose使用了声明式的开发范式,在这样的范式中,UI的职责更加的单一,只会对数据状态的变化作出反应,如果数据状态没有发生变化,则UI就永远不会自行的改变。假如我们把Composable的执行看成是一个函数的运算的话,那么状态就是函数的参数,输出就是生成

    2024年02月08日
    浏览(44)
  • Android开发中的前五个代码异味:Jetpack Compose UI和MVVM

    代码异味是指软件代码中潜在问题的指标,可能并不一定是错误,但可能会导致问题,如增加维护复杂性、降低性能或降低可读性。我们将探讨Android开发中的前五个代码异味,其中包括使用Jetpack Compose UI和Model-View-ViewModel(MVVM)架构的示例。 上帝对象或上帝类是指试图做太

    2024年02月02日
    浏览(40)
  • Android Jetpack Compose中使用字段验证的方法

    数据验证是创建健壮且用户友好的Android应用程序的关键部分。随着现代UI工具包Jetpack Compose的引入,处理字段验证变得更加高效和直观。在这篇文章中,我们将探讨如何在Android应用中使用Jetpack Compose进行字段验证。 字段验证是确保用户在各种输入字段中输入的数据符合特定

    2024年02月11日
    浏览(54)
  • Jetpack Compose UI架构

    Jetpack Compose是我职业生涯中最激动人心的事。它改变了我工作和问题思考的方式,引入了易用且灵活的工具,几乎可轻松实现各种功能。 早期在生产项目中尝试了Jetpack Compose后,我迅速着迷。尽管我已有使用Compose创建UI的经验,但对新的Jetpack Compose驱动特性的组织和架构引发

    2024年02月11日
    浏览(52)
  • Jetpack Compose 深入探索系列四: Compose UI

    通过 Compose runtime 集成 UI Compose UI 是一个 Kotlin 多平台框架。它提供了通过可组合函数发出 UI 的构建块和机制。除此之外,这个库还包括 Android 和 Desktop 源代码,为 Android 和 Desktop 提供集成层。 JetBrains积极维护Desktop代码库,而Google维护Android和通用代码库。Android和Desktop源代码

    2024年02月12日
    浏览(48)
  • Jetpack Compose 不止是一个UI框架~

    Jetpack Compose是用于构建原生Android UI的现代工具包。 Jetpack Compose使用更少的代码,强大的工具和直观的Kotlin API,简化并加速了Android上的UI开发。这是Android Developers 官网对它的描述。 本文不是教你Jetpack Compose 的一些基本使用方法,而是为啥我们需要Jetpack Compose 的一些简洁,让

    2024年02月03日
    浏览(54)
  • Jetpack Compose -> 声明式UI & Modifier

    本章主要介绍下 Compose 的声明式 UI 以及初级写法; 传统UI 传统 UI 方式来声明UI 是通过 xml 来进行显示的,显示文字的方式是使用 TextView,它内部显示文字的方式有两种,一种是在 xml 中直接设置,通过下面这种方式设置 这种方式是通过初始值在 xml 中进行预设置的; 还有一

    2024年02月02日
    浏览(53)
  • Jetpack Compose UI 底部弹窗实现

    使用Compose Ui的Dialog 默认是居中显示屏幕中间。 实现思路: 1.弹窗完全自定义一个全屏弹窗; 2.显示内容显示在底部区域。 3.点击其他空白区域关闭弹窗。

    2024年02月11日
    浏览(54)
  • Android Jetpack Compose 别裁

    一、简介 二、compose优缺点 三、compose好学吗 四、Android Jetpack Compose 跟 fluter 哪个更好 五、Android Jetpack Compose 跟 fluter 技能学习选择 之所以叫Android Jetpack Compose别裁是希望能取舍网上的对compose的资料,推出别出心裁的文章,文章结束都会有一个案例,通过实践学习,让学习的

    2024年02月03日
    浏览(66)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包