在处理viewModel内部的协同程序时,最好让所述viewModel实现CoroutineScope
,以便在清除viewModel时取消所有协同程序。通常我看到coroutineContext
被定义为Dispatchers.Main + _job
,因此默认情况下协同程序在主UI线程中执行。通常这是在一个开放类上完成的,这样所有的viewmodel都可以在没有样板代码的情况下扩展它并获得范围。
由于Dispatchers.Main
不可用,尝试对所述viewModels进行单元测试时会出现问题,并且尝试使用它会引发异常。我正在努力寻找一个好的解决方案,它不涉及外部库,也不涉及太多的子视图模型。
我目前的解决方案是将主上下文添加为构造函数参数中心,并将Dispatchers.Main
作为默认值。然后在单元测试中,在测试viewModel之前,我将其设置为Dispatchers.Default
。我不喜欢这个解决方案,因为它公开了协同程序的上下文实现细节,让每个人都能看到并更改:
open class ScopedViewModel(var maincontext = Dispatchers.Main) : ViewModel(), CoroutineScope {
private val _job = Job()
override val coroutineContext: CoroutineContext
get() = maincontext + _job
override fun onCleared() {
super.onCleared()
_job.cancel()
}
}
class MyViewModel : ScopedViewModel() {}
在测试中:
fun setup(){
viewModel = MyViewModel()
viewModel.maincontext = Dispacther.Default
}
就我个人而言,我从RxJava2复制了一个解决方案:如果您的测试针对包括两个或多个不同调度器的RxJava 2流运行,那么您当然希望所有调度器都能在单个线程中运行。以下是RxJava2测试的方法:
@BeforeClass
public static void prepare() {
RxJavaPlugins.setComputationSchedulerHandler(scheduler -> Schedulers.trampoline());
RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline());
RxJavaPlugins.setNewThreadSchedulerHandler(scheduler -> Schedulers.trampoline());
RxJavaPlugins.setSingleSchedulerHandler(scheduler -> Schedulers.trampoline());
RxAndroidPlugins.setMainThreadSchedulerHandler(scheduler -> Schedulers.trampoline());
}
我在郊游时也这样做。只是创建了一个收集调度器的类,但这些调度器可以更改。
object ConfigurableDispatchers {
@JvmStatic
@Volatile
var Default: CoroutineDispatcher = Dispatchers.Default
@JvmStatic
@Volatile
var Main: MainCoroutineDispatcher = Dispatchers.Main
...
}
在@BeforeClass
方法中,我称之为
@ExperimentalCoroutinesApi
fun setInstantMainDispatcher() {
Main = object : MainCoroutineDispatcher() {
@ExperimentalCoroutinesApi
override val immediate: MainCoroutineDispatcher
get() = this
override fun dispatch(context: CoroutineContext, block: Runnable) {
block.run()
}
}
}
这将保证该块将在调用线程中执行。
这是我发现的构造函数注入的唯一替代方案。