Android 别三日,当 deprecated 相待。

我其时写程序的时候有这种需求,期望某一段协程在某一个生命周期开端(下文假定生命周期为 onStart)进行,并且就履行一次。假如履行一次的话,单走一个lifecycleScope.launch其实也行,但并不安全。我去谷歌查阅了资料,又受到 IDE 提示的影响,了解到了“奇特”的lifecycleScope.launchWhenStarted,这个函数刚好满足我的需求。可是贴上去发现,弃用了!

launchWhenX 给我弃用了,这可咋整?

好家伙,让我去用repeatOnLifecycle?我心想这不对啊,我要履行一次,而这个函数的重点是repeat,还是不符合我要求。

我又转向了LifecycleOwner::whenStarted,该函数跟上面那个函数作用是相同的,只不过作用域不太相同。我想看看他是让你怎样做的。

launchWhenX 给我弃用了,这可咋整?

好家伙,让我去用withStarted?可是withStarted里边只能放 non-suspending work(非挂起使命),这还是不符合我的要求啊。

其实假如你不怎样在乎警告,并且这种代码并没有给你的软件带来多大损失,你能够持续这么用着。可是在这个函数受众面这么广泛的状况下,为什么官方这么执意把它删去呢?

repeatOnLifecycle 的问题

优势

咱们或许都看过一张比较优势图:

launchWhenX 给我弃用了,这可咋整?

能够看到,repeatOnLifecycle 会在每次重复时都从头开端履行协程,并在生命周期状况降低到指定状况以下时撤销。关于搜集大多数流来说,它非常适合,由于在不需求时能完全撤销流,从而节省了资源。

launchWhenX 系列函数不会撤销协程并从头启动。它只会推迟启动时刻,并在生命周期状况低于指定状况时挂起。

举一个简略的比如,

delay(5000)
Log.d("test", "test")

假如被repeatOnLifecycle(Lifecycle.State.STARTED)包裹起来,且 1s 后回来主界面,该挂起函数会立即撤销,由于撤销了,所以你再等 4s,也不会有任何反响。你回来程序再等 5s 后,log 信息会出现在操控台中,由于从头履行了。

假如被launchWhenStarted包裹起来,且 1s 后回来主界面,该挂起函数会立即挂起,由于挂起了,所以你再等 4s,也不会有任何反响,可是它还是一直在后台进行着 delay 的。当你过了 4s 多打开程序后,log 信息会立即出现在操控台中,由于挂起恢复了。

详细比如就不从这里细说了,能够参阅一下这篇文章:使用更为安全的办法搜集 Android UI 数据流

局限

咱们从这篇文章也能够看到,这里边处理的是一个方位信息。方位信息,咱们自然需求最新的,并且后台把该流撤销掉,然后回来后从头创建也是能够承受的。

弃用launchWhenX办法,根本就是为了有效遏制挂起函数在后台还在运转,导致资源糟蹋甚至程序溃散。

可是实践的需求是无穷无尽的,假如需求显示一个一次性 SnackBar,或许打开一个一次性 window,这些需求假如用repeatOnLifecycle,让我想起了LiveData当年的痛,有点相似处理粘性事情了,但又不太相同。

把 repeatOnLifecycle 用成 launchWhenX 的样子

官方在 Alternative APIs for running one-time suspend code when lifecycle state is at least X? 中提供了三种解决方案,适用于不同需求

  1. 让挂起的代码持续运转至完毕

    这样做的优点是整个代码块以原子办法运转,也就是说不会在半途被撤销。

    lifecycleScope.launch {
      // 直到 STARTED 前一直挂起
      withStarted { }
      // 之前你写在 launchWhenStarted 中的代码写到这下面
      doYourOneTimeWork()
      // 注意:即便生命周期低于 STARTED 对应的 onStop,代码也将持续运转,
      // 这个和 launchWhenStarted 还不太相同!
    }
    

    这种类型的代码假定你的一次性使命不依赖于保持在某个生命周期状况以上,但这是一个库无法知道的状况。举个比如,假如在挂起使命之后运转了一个 FragmentTransaction,那么在该调用发生之前或许会保存状况。

  2. 撤销挂起的代码,并在回来到该状况以上时从头启动它

    每次回到给定的生命周期状况以上时再从头运转代码块确实是 repeatOnLifecycle 的规定。然而,关于一个库来说,是无法知道是否能够安全地从头运转整个代码块,或许是否需求更频繁地进行检查点操作的。

    简略来说,假如能够多次运转整个代码块直到成功完结,那么只需求用一个 isComplete 来符号:

    lifecycleScope.launch {
      var isComplete = false
      repeatOnLifecycle(Lifecycle.State.STARTED) {
        if (!isComplete) {
          // 之前你写在 launchWhenStarted 中的代码写到这下面
          // 假如在运转时生命周期走向 STARTED 对应的 onStop 时会被直接撤销
          doYourOneTimeWork()
          // 符号是否成功
          isComplete = true
        }
      }
    }
    

    当然不能确保假如半途被撤销,从头运转该代码块就一定是安全的。

  3. 撤销挂起的代码,不从头启动

    这个跟上一个相似,可是这个无论完没完结,都会在 finally 块中设置isComplete,让它完全撤销。

    lifecycleScope.launch {
      var isComplete = false
      repeatOnLifecycle(Lifecycle.State.STARTED) {
        if (!isComplete) {
          try {
            // 之前你写在 launchWhenStarted 中的代码写到这下面
            // 假如在运转时生命周期走向 STARTED 对应的 onStop 时会被直接撤销
            doYourOneTimeWork()
          } finally {
            // 即便一次性使命运转了一半没运转完,也当它运转完了,
            // 并且在 finally 块中符号该使命已完结,所以该代码块不会重启。
            isComplete = true
          }
        }
      }
    }
    

五种办法怎样选?

我不太懂 PS,后边几个用画图软件 P 的,能看懂意思就行

首先遵从以下的 lifecycle 活动:

launchWhenX 给我弃用了,这可咋整?

launchWhenStarted

不安全,且已被弃用,但能够后台加载,最重要的是一次性

launchWhenX 给我弃用了,这可咋整?

repeatOnLifecycle(Lifecycle.State.STARTED)

安全,但或许产生粘性事情

launchWhenX 给我弃用了,这可咋整?

解决方案1

不安全,慎重使用

注意和lifecycleScope.launch的区别

launchWhenX 给我弃用了,这可咋整?

解决方案2

安全,能够说是代替launchWhenStarted的较优方案。缺陷是不能后台加载

launchWhenX 给我弃用了,这可咋整?

解决方案3

安全,但或许没处理完就完毕了,不太适用于界面处理

launchWhenX 给我弃用了,这可咋整?