【问题标题】:Android Kotlin Coroutine on screen rotation屏幕旋转上的 Android Kotlin 协程
【发布时间】:2019-05-05 00:01:30
【问题描述】:

我正在启动一个协程,它会在指定的延迟后在屏幕上显示一个计数器值。

job = launch(UI) {
    var count= 0
      while (true) {
        textView.text = "${count++}"
        delay(200L)
      }
   }

现在在屏幕旋转时,我希望 UI 不断更新正确的计数器值。有人知道如何在配置(例如屏幕旋转)更改时恢复工作。

【问题讨论】:

    标签: android kotlin kotlinx.coroutines


    【解决方案1】:

    是否有人知道如何在配置(例如屏幕旋转)更改时恢复工作。

    您的工作从未停止运行,但您一直坚持并更新不再显示在屏幕上的TextView。配置更改后,您的 Activity 及其整个视图层次结构被抓取。

    虽然从技术上讲,您可以将应用配置为不重新创建轮换活动,但 Google 强烈建议您不要这样做。该应用似乎适用于轮换情况,但随后会因另一种配置更改(如时区、位置等)而中断。您只需要硬着头皮让您的应用在活动娱乐事件中正常工作。

    我通过我设置的 Fragment 使我的协程在活动娱乐中工作

    retainInstance = true
    

    这意味着您的片段实例在其父 Activity 死亡后仍然存在,并且当新 Activity 替换它时,Android 会将您的片段注入其中而不是创建一个新片段。它确实不阻止视图层次结构的破坏,您必须编写更新片段状态的代码以反映这些更改。这很有帮助,因为它允许您保持片段的状态,而不是为包裹化而烦恼。

    在配置更改时,您的片段将经历这些生命周期事件:

    1. onDestroyView
    2. onCreateView

    它不会通过onPause/onResume,这只会在您切换活动或退出应用程序时发生。您可以在onResume 中启动您的协程并在onPause 中取消它。

    从最近发布的kotlinx.coroutines 0.23 版开始,launch 成为一个扩展函数:您必须在控制生成作业生命周期的某些CoroutineScope 的上下文中调用它。您应该将其生命周期绑定到片段,因此让您的片段实现CoroutineScope。另一个变化是 UI 协程上下文现在已弃用,取而代之的是 Dispatchers.Main。

    这是一个简短的例子,展示了我提到的所有要点:

    class MyFragment : Fragment, CoroutineScope {
        private var textView: TextView? = null
        private var rootJob = Job()
    
        override val coroutineContext: CoroutineContext 
            get() = Dispatchers.Main + rootJob
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            retainInstance = true
        }
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
        ): View {
            val rootView = inflater.inflate(R.layout.frag_id, container, false)
            this.textView = rootView.findViewById(R.id.textview)
            return rootView
        }
    
        override fun onDestroyView() {
            this.textView = null
        }
    
        override fun onResume() {
            this.launch {
                var count = 0
                while (true) {
                    textView?.text = "$count"
                    count++
                    delay(200L)
                }
            }    
        }
    
        override fun onPause() {
            rootJob.cancel()
            rootJob = Job()
        }
    }
    

    现在,随着视图层次结构的重建,您的协程将自动获取 textView 的当前实例。如果在重建 UI 的过程中发生了不方便的计时器滴答,协程将静默跳过更新视图并在下一个滴答时重试。

    【讨论】:

      【解决方案2】:

      默认情况下,旋转会终止活动并重新启动它。这意味着您的 textview 将不再是屏幕上的那个,它将是属于旧 Activity 的那个。

      您的选择是:

      1) 将 configSettings 添加到您的清单以关闭此行为。

      2) 使用可以在活动重启时持续存在的东西,例如视图模型、加载器、注入的事件总线等。

      除非你有不同的纵向和横向布局,否则我个人会选择数字 1,它更容易。

      【讨论】:

        【解决方案3】:

        您可以在 ViewModel 而不是 Activity 中执行此操作。

        【讨论】:

        • 我想我确实以错误的方式回答了问题。基本上我的问题与 kotlin coroutine 的工作重启有关。我可以存储 Job 本身并恢复它吗?
        • 你还需要一个地方来存放作业。
        猜你喜欢
        • 1970-01-01
        • 2011-02-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多