【问题标题】:Clean way to retain CoroutineScope through config changes without ViewModel通过没有 ViewModel 的配置更改保留 CoroutineScope 的干净方法
【发布时间】:2020-01-23 15:02:52
【问题描述】:

我知道建议是在我们的 Activity 中使用 ViewModel,因此我们可以使用它的viewModelScope。由于 ViewModel 比 Activity 寿命更长,我们不必取消 activity.onDestroy() 中的作业。

但是,有时您的 Activity 非常简单。例如,它可以使用已安装的过滤包填充列表视图。您可以非常简单地使用委托为活动创建范围,并取消 onDestroy() 中的作业:

class MyActivity(): AppCompatActivity(), CoroutineScope by MainScope() {

    private val listAdapter = MyAdapter()

    override fun onCreate() {
        super.onCreate()
        setContentView(R.layout.my_activity)
        recycler_view.apply {
            layoutManager = LinearLayoutManager(this)
            adapter = listAdapter
        }
        launch {
            val packages = getOrgPackagesWithIcons()
            adapter.apply {
                data = packages
                notifyDataSetChanged()
            }
        }

    }

    override fun onDestroy() {
        super.onDestroy()
        cancel() // CoroutineContext
    }

    private suspend fun getOrgPackagesWithIcons() = withContext(Dispatchers.Default) {
        var toNextYield = 20
        packageManager.getInstalledPackages(0)
            .filter { it.packageName.startsWith("org")
            .take(100)
            .map { 
                     if (--toNextYield == 0) { // Make it cancellable
                         toNextYield = 20
                         yield()
                     }
                     MyPackageData(
                         it.applicationInfo.loadLabel(packageManager).toString(),
                         it.packageName,
                         it.applicationInfo.loadIcon(packageManager)
                     )
                 }
        }

}

对于这种情况,ViewModel 感觉有点矫枉过正。它只是抽象 PackageManager 的另一层,它本身就是一个视图模型。

上面的代码可以很容易地在后台组装数据。问题是当屏幕旋转时,或在其他配置更改期间,协程被取消并重新启动。有没有一个干净的方法可以通过像这样一个非常简单的 Activity 的配置更改来保持 CoroutineScope 活着?

onRetainNonConfigurationInstance() 已弃用。我想我们可以把它放在一个 Fragment 中并使用retainInstance = true,但是在这样一个简单的 Activity 中引入一个 Fragment 层也感觉有点矫枉过正。

也许有一种方法可以创建一个空的 ViewModel 实现,以便我们可以借用它的范围?

【问题讨论】:

    标签: android kotlin kotlin-coroutines


    【解决方案1】:

    对于这种情况,ViewModel 感觉有点矫枉过正。

    我会反对,但我仍然认为这将是 AndroidViewModel 的一个很好的用例。

    我认为仅仅因为Activity 可以访问PackageManager 而获取包列表不是Activity 的责任。 Activity 应该只负责显示列表。

    使用AndroidViewModel 可以让您在ViewModel 实例中访问ContextviewModelScope

    【讨论】:

    • 这是一个公平的观点。尤其是在我的示例中,我不仅做一个简单的查询,而且随后过滤它并加载图标。我认为 Activity 扩展 Context 而不是像 Fragment 那样简单地持有对它的引用是一个设计错误。但如果使用 AndroidViewModel,你如何将它绑定到可能会被重新实例化的特定 Activity?如果该 Activity 因配置更改以外的原因被销毁,您仍然希望取消该 Activity 的关联作业。
    • 据我所知,ViewModelAndroidViewModel 之间的唯一区别是AndroidViewModel 允许您通过传递给其构造函数的Application 实例访问Context。基本上,AndroidViewModel 只是“持有”一个Context 供您使用。两者的作业取消和生命周期问题是相同的。如果 Activity 由于进程死亡而被销毁(这是首先想到的不是配置更改),那么 ViewModelAndroidViewModel 都不会幸免于难。
    • 那么在用户导航离开活动的情况下,取消作业的正确位置在哪里?将if (isFinishing) viewModel.viewModelScope.cancel() 放入onPause() 就这么简单吗?不确定isFinishing 是否在配置更改时返回 true。
    • 所以如果我理解正确的话,这更像是一个关于viewModelScope 是如何实现的问题?默认情况下,当不再需要 ViewModel 时,即调用 ViewModelonCleared() 时,它会取消所有作业。我不知道是否有一种方法可以手动取消在onPause() 中运行的作业,而不是在ViewModel 上调用一个固有地调用viewModelScope.cancel() 的方法。
    • 我的问题发生了变化,因为我认为您已经让我相信,即使在这样一个简单的示例中,在像 Activity 这样的 UI 类中进行数据采集也是糟糕的设计。重新熟悉 ViewModel,我发现它的行为已经如我所愿,在拥有的活动完成时自动取消作业(但不是在配置更改时)。如果您的数据需要超过特定活动,您可以创建一个 repo 单例类,使用 Application 引用检索其实例,您可以使用 AndroidViewModel 的 application 实例来检索它。
    【解决方案2】:

    同意上面的答案,ViewModel 仍然是这里的最佳选择,因为您正在执行不真正属于活动的数据操作。也就是说,如果您查看 如何 ViewModel 保留数据 - 它是简单的静态数据。这里没有引用完整的链条,只是将它们结合在一起的部分

    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    

    NonConfigurationInstances 在哪里:

    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
    }
    

    把它放在这里是为了解决你关于“空 ViewModel 实现”的问题——这就是它在那里完成的方式,你可以做类似的事情——也许放在你自己的 Application 类中。但话又说回来,它就在那里,使用简单,并提供其他好处......所以我总是使用视图模型/androidviewmodel,而且我的脑海中根本没有开销,恰恰相反,代码组织得很好。

    【讨论】:

    • its simple static -- 这是什么意思?我看到mViewModelStore,它可能不是静态的,而且这里的 sn-ps 都没有显示静态字段声明。
    • 没错,我也不明白!
    猜你喜欢
    • 1970-01-01
    • 2014-11-06
    • 1970-01-01
    • 2023-03-07
    • 2015-11-11
    • 2020-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多