为什么在调用 ViewModel onCleared() 方法后 ViewModelScoped 协程无法使用

Why is ViewModelScoped coroutine unusable after ViewModel onCleared() method called

我在当前 Android 应用程序中的多个片段之间共享一个 ActivityScoped viewModel。

viewModel 使用协程作用域viewModelScope.launch{}

我的问题是 .launch{} 仅在调用拥有 ViewModel onCleared() 方法之前有效。

这就是 ViewModel 作用域协程的工作方式吗?

是否有一种方法可以用于 "Reset" viewModelScope,以便 .launch{} 在调用 onCleared() 方法后工作?

这是我的代码::

片段

RxSearchView.queryTextChangeEvents(search)
        .doOnSubscribe {
            compositeDisposable.add(it)
        }
        .throttleLast(300, TimeUnit.MILLISECONDS)
        .debounce(300, TimeUnit.MILLISECONDS)
        .map { event -> event.queryText().toString() }
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe { charactersResponse ->
            launch {
                viewModel.search(charactersResponse.trim())
            }
        }

。 . .

override fun onDetach() {
    super.onDetach()
    viewModel.cancelSearch()
    compositeDisposable.clear()
}

ViewModel

suspend fun search(searchString: String) {
    cancelSearch()

    if (TextUtils.isEmpty(searchString)) {
        return
    }

    job = viewModelScope.launch {
        repository.search(searchString)
    }
}

fun cancelSearch() {
    job?.cancelChildren()
}

。 . .

override fun onCleared() {
    super.onCleared()
    repository.onCleared()
 }

我做错了什么?

更新

如果我将我的启动代码修改为这个

job = GlobalScope.launch {
    repository.search(searchString)
}

它解决了我的问题,但是这是达到我想要的结果的唯一方法吗?

我的印象是 GlobalScope 是 "Bad"

following a cal to onCleared() my viewModelScoped cororoutine Launch stops executing

这是一个功能,不是错误。

一旦 ViewModel 被清除,你不应该在那个 ViewModel 或它的 LifecycleOwner 中做 任何事情。所有这些现在都已失效,不应再使用。

however is this the only way to achieve my desired result?

正确的解决方案是删除 ViewModel 中的代码。如果您希望某些后台工作超过 activity 或片段的生命周期,那么该代码不属于 activity/fragment 或其关联的视图模型。它属于与您正在尝试做的工作具有匹配生命周期的东西。

repository.onCleared()

此方法不应属于存储库。

事实上,存储库不应该是有状态的。

如果你检查 Google 的样本,the Repository creates a LiveData that contains a Resource, and the reason why this is relevant is because the actual data loading and caching mechanic is inside this resource, triggered by LiveData.onActive(在这个样本中,MediatorLiveData.addSource,但从技术上讲,这在语义上是一样的)。

    .subscribe { charactersResponse ->
        launch {
            viewModel.search(charactersResponse.trim())

片段不应启动协程。它应该说

.subscribe {
    viewModel.updateSearchText(charactersResponse.trim())
}

还有

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java, factory)
    viewModel.searchResults.observe(viewLifecycleOwner, Observer { results ->
        searchAdapter.submitList(results)
    })
}

然后 ViewModel 会

class MyViewModel(
    private val repository: MyRepository
): ViewModel() {
    private val searchText = MutableLiveData<String>()

    fun updateSearchText(searchText: String) {
        this.searchText.value = searchText
    }

    val searchResults: LiveData<List<MyData>> = Transformations.switchMap(searchText) {
        repository.search(searchText)
    }
}

这就是 ViewModel 中应该有的所有内容,那么 "who owns the coroutine scope" 的问题呢?这取决于何时应该取消任务。

如果"no longer observing"应该取消任务,那么应该LiveData.onInactive()取消任务。

如果 "no longer observing but not cleared" 应该保留任务,那么 ViewModel 的 onCleared 确实应该管理 ViewModel 中的 SupervisorJob,它将在 onCleared() 中取消,并且 search 应该在其中启动范围,这可能只有在将 CoroutineScope 传递给 search 方法时才有可能。

suspend fun search(scope: CoroutineScope, searchText: String): LiveData<List<T>> =
    scope.launch {
        withContext(Dispatchers.IO) { // or network or something
            val results = networkApi.fetchResults(searchText)
            withContext(Dispatchers.MAIN) {
                MutableLiveData<List<MyData>>().apply { // WARNING: this should probably be replaced with switchMap over the searchText
                    this.value = results
                }
            }
        }
    }

这行得通吗?不确定,我实际上并没有使用协同程序,但我认为应该这样做。但是,此示例不处理 LiveData 中的 switchMap-ing 等价物,也不处理协程。