如何正确的在 Android 上使用协程?( 二 )


GlobalScope 创建的协程没有父协程 , GlobalScope 通常也不与任何生命周期组件绑定 。除非手动管理 , 否则很难满足我们实际开发中的需求 。所以 , GlobalScope 能不用就尽量不用 。
MainScope
官方文档中提到要使用自定义的协程作用域 , 当然 , Kotlin 已经给我们提供了合适的协程作用域 MainScope。看一下 MainScope 的定义:
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)记着这个定义 , 在后面 ViewModel 的协程使用中也会借鉴这种写法 。
给我们的 Activity 实现自己的协程作用域:
class BasicCorotineActivity : AppCompatActivity(), CoroutineScope by MainScope() {}通过扩展函数 launch() 可以直接在主线程中启动协程 , 示例代码如下:
private fun launchFromMainScope() { launch { val deferred = async(Dispatchers.IO) { // network request delay(3000) "Get it" } mainScope.text = deferred.await() Toast.makeText(applicationContext, "MainScope", Toast.LENGTH_SHORT).show() }}最后别忘了在 onDestroy() 中取消协程 , 通过扩展函数 cancel() 来实现:
override fun onDestroy() { super.onDestroy() cancel()}现在来测试一下 launchFromMainScope() 方法吧!你会发现这完全符合你的需求 。实际开发中可以把 MainScope 整合到 BaseActivity 中 , 就不需要重复书写模板代码了 。
ViewModelScope
如果你使用了 MVVM 架构 , 根本就不会在 Activity 上书写任何逻辑代码 , 更别说启动协程了 。这个时候大部分工作就要交给 ViewModel 了 。那么如何在 ViewModel 中定义协程作用域呢?还记得上面 MainScope() 的定义吗?没错 , 搬过来直接使用就可以了 。
class ViewModelOne : ViewModel() { private val viewModelJob = SupervisorJob() private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) val mMessage: MutableLiveData<String> = MutableLiveData() fun getMessage(message: String) { uiScope.launch { val deferred = async(Dispatchers.IO) { delay(2000) "post $message" } mMessage.value = https://www.isolves.com/it/cxkf/ydd/Android/2019-10-22/deferred.await() } } override fun onCleared() { super.onCleared() viewModelJob.cancel() }}这里的 uiScope 其实就等同于 MainScope 。调用 getMessage() 方法和之前的 launchFromMainScope() 效果也是一样的 , 记得在 ViewModel 的 onCleared() 回调里取消协程 。
你可以定义一个 BaseViewModel 来处理这些逻辑 , 避免重复书写模板代码 。然而 Kotlin 就是要让你做同样的事 , 写更少的代码 , 于是 viewmodel-ktx 来了 。看到 ktx  , 你就应该明白它是来简化你的代码的 。引入如下依赖:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha03"然后 , 什么都不需要做 , 直接使用协程作用域 viewModelScope 就可以了 。viewModelScope 是 ViewModel 的一个扩展属性 , 定义如下:
val ViewModel.viewModelScope: CoroutineScope get() { val scope: CoroutineScope? = this.getTag(JOB_KEY) if (scope != null) { return scope } return setTagIfAbsent(JOB_KEY, CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main)) }看下代码你就应该明白了 , 还是熟悉的那一套 。当 ViewModel.onCleared() 被调用的时候 , viewModelScope 会自动取消作用域内的所有协程 。使用示例如下:
fun getMessageByViewModel() { viewModelScope.launch { val deferred = async(Dispatchers.IO) { getMessage("ViewModel Ktx") } mMessage.value = https://www.isolves.com/it/cxkf/ydd/Android/2019-10-22/deferred.await() }}写到这里 , viewModelScope 是能满足需求的最简写法了 。实际上 , 写完全篇 , viewModelScope 仍然是我认为的最好的选择 。
LiveData
Kotlin 同样为 LiveData 赋予了直接使用协程的能力 。添加如下依赖:
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha03"直接在 liveData {} 代码块中调用需要异步执行的挂起函数 , 并调用 emit() 函数发送处理结果 。示例代码如下所示:
val mResult: LiveData<String> = liveData { val string = getMessage("LiveData Ktx") emit(string)}你可能会好奇这里好像并没有任何的显示调用 , 那么 , liveData 代码块是在什么执行的呢?当 LiveData 进入 active 状态时 , liveData{ } 会自动执行 。当 LiveData 进入 inactive 状态时 , 经过一个可配置的 timeout 之后会自动取消 。如果它在完成之前就取消了 , 当 LiveData 再次 active 的时候会重新运行 。如果上一次运行成功结束了 , 就不会再重新运行 。也就是说只有自动取消的 liveData{ } 可以重新运行 。其他原因(比如 CancelationException)导致的取消也不会重新运行 。


推荐阅读