今天咱们来聊聊Kotlin Coroutine,若是你尚未了解过,那么我要提早恭喜你,由于你将掌握一个新技能,对你的代码方面的提高将是很好的助力。java
简单的来讲,Coroutine是一个并发的设计模式,你能经过它使用更简洁的代码来解决异步问题。android
例如,在Android方面它主要可以帮助你解决如下两个问题:git
这些问题,在接下来的文章中我都会给出解决的示例。github
说到异步问题,咱们先来看下咱们常规的异步处理方式。首先第一种是最基本的callback方式。数据库
callback的好处是使用起来简单,但你在使用的过程当中可能会遇到以下情形设计模式
GatheringVoiceSettingRepository.getInstance().getGeneralSettings(RequestLanguage::class.java) .observe(this, { language -> convertResult(language, { enable -> // todo something }) })
这种在其中一个callback中回调另外一个callback回调,甚至更多的callback都是可能存在。这些状况致使的问题是代码间的嵌套层级太深,致使逻辑嵌套复杂,后续的维护成本也要提升,这不是咱们所要看到的。api
那么有什么方法可以解决呢?固然有,其中的一种解决方法就是我接下来要说的第二种方式。安全
对多嵌套回调,Rx系列在这方面处理的已经很是好了,例如RxJava。下面咱们来看一下RxJava的解决案例微信
disposable = createCall().map { // return RequestType }.subscribeWith(object : SMDefaultDisposableObserver<RequestType>{ override fun onNext(t: RequestType) { // todo something } })
RxJava丰富的操做符,再结合Observable与Subscribe可以很好的解决异步嵌套回调问题。可是它的使用成本就相对提升了,你要对它的操做符要很是了解,避免在使用过程当中滥用或者过分使用,这样天然复杂度就提高了。网络
那么咱们渴望的解决方案是可以更加简单、全面与健壮,而咱们今天的主题Coroutine就可以达到这种效果。
在Android里,咱们都知道网络请求应该放到子线程中,相应的回调处理通常都是在主线程,即ui线程。正常的写法就很少说了,那么使用Coroutine又该是怎么样的呢?请看下面代码示例:
private suspend fun get(url: String) = withContext(Dispatchers.IO) { // to do network request url } private suspend fun fetch() { // 在Main中调用 val result = get("https://rousetime.com") // 在IO中调用 showToast(result) // 在Main中调用 }
若是fetch方法在主线程调用,那么你会发现使用Coroutine来处理异步回调就像是在处理同步回调同样,简洁明了、行云流水,同时再也没有嵌套的逻辑了。
注意看方法,Coroutine为了可以实现这种简单的操做,增长了两个操做来解决耗时任务,分别为suspend与resume
解释的有点生硬,简单的来讲就是suspend能够将该任务挂起,使它暂时不在调用的线程中,以致于当前线程能够继续执行别的任务,一旦被挂起的任务已经执行完毕,那么就会经过resume将其从新插入到当前线程中。
因此上面的示例展现的是,当get还在请求的时候,fetch方法将会被挂起,直到get结束,此时才会插入到主线程中并返回结果。
一图胜千言,我作了一张图,但愿能有所帮助。
另外须要注意的是,suspend方法只可以被其它的suspend方法调用或者被一个coroutine调用,例如launch。
另外一方面Coroutine使用Dispatchers来负责调度协调程序执行的线程,这一点与RxJava的schedules有点相似,但不一样的是Coroutine必定要执行在Dispatchers调度中,由于Dispatchers将负责resume被suspend的任务。
Dispatchers提供三种模式切换,分别为
再来看上面的示例
private suspend fun get(url: String) = withContext(Dispatchers.IO) { // to do network request url } private suspend fun fetch() { // 在Main中调用 val result = get("https://rousetime.com") // 在IO中调用 showToast(result) // 在Main中调用 }
为了让get操做运行在IO线程,咱们使用withContext方法,对该方法传入Dispatchers.IO,使得它闭包下的任务都处于IO线程中,同时witchContext也是一个suspend函数。
上面提到suspend函数只能在相应的suspend中或者Coroutine中调用。那么Coroutine又该如何建立呢?
有两种方式,分别为launch与async
仍是上面的例子,若是咱们须要执行fetch方法,可使用launch建立一个Coroutine
private fun excute() { CoroutineScope(Dispatchers.Main).launch { fetch() } }
另外一种async,由于它返回结果,若是要等全部async执行完毕,可使用await或者awaitAll
private suspend fun fetchAll() { coroutineScope { val deferredFirst = async { get("first") } val deferredSecond = async { get("second") } deferredFirst.await() deferredSecond.await() // val deferred = listOf( // async { get("first") }, // async { get("second") } // ) // deferred.awaitAll() } }
因此经过await或者awaitAll能够保证全部async完成以后再进行resume调用。
若是你使用了Architecture Component,那么你也能够在其基础上使用Coroutine,由于Kotlin Coroutine已经提供了相应的api而且定制了CoroutineScope。
若是你还不了解Architecture Component,强烈推荐你阅读个人 Android Architecture Components 系列
在使用以前,须要更新architecture component的依赖版本,以下所示
object Versions { const val arch_version = "2.2.0-alpha01" const val arch_room_version = "2.1.0-rc01" } object Dependencies { val arch_lifecycle = "androidx.lifecycle:lifecycle-extensions:${Versions.arch_version}" val arch_viewmodel = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.arch_version}" val arch_livedata = "androidx.lifecycle:lifecycle-livedata-ktx:${Versions.arch_version}" val arch_runtime = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.arch_version}" val arch_room_runtime = "androidx.room:room-runtime:${Versions.arch_room_version}" val arch_room_compiler = "androidx.room:room-compiler:${Versions.arch_room_version}" val arch_room = "androidx.room:room-ktx:${Versions.arch_room_version}" }
在ViewModel中,为了可以使用Coroutine提供了viewModelScope.launch,同时一旦ViewModel被清除,对应的Coroutine也会自动取消。
fun getAll() { viewModelScope.launch { val articleList = withContext(Dispatchers.IO) { articleDao.getAll() } adapter.clear() adapter.addAllData(articleList) } }
在IO线程经过articleDao从数据库取数据,一旦数据返回,在主线程进行处理。若是在取数据的过程当中ViewModel已经清除了,那么数据获取也会中止,防止资源的浪费。
对于Lifecycle,提供了LifecycleScope,咱们能够直接经过launch来建立Coroutine
private fun coroutine() { lifecycleScope.launch { delay(2000) showToast("coroutine first") delay(2000) showToast("coroutine second") } }
由于Lifecycle是能够感知组件的生命周期的,因此一旦组件onDestroy了,相应的LifecycleScope.launch闭包中的调用也将取消中止。
lifecycleScope本质是Lifecycle.coroutineScope
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope get() = lifecycle.coroutineScope override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { if (lifecycle.currentState <= Lifecycle.State.DESTROYED) { lifecycle.removeObserver(this) coroutineContext.cancel() } }
它会在onStateChanged中监听DESTROYED状态,同时调用cancel取消Coroutine。
另外一方面,lifecycleScope还能够根据Lifecycle不一样的生命状态进行suspend处理。例如对它的STARTED进行特殊处理
private fun coroutine() { lifecycleScope.launchWhenStarted { } lifecycleScope.launch { whenStarted { } delay(2000) showToast("coroutine first") delay(2000) showToast("coroutine second") } }
不论是直接调用launchWhenStarted仍是在launch中调用whenStarted都能达到一样的效果。
LiveData中能够直接使用liveData,在它的参数中会调用一个suspend函数,同时会返回LiveData对象
fun <T> liveData( context: CoroutineContext = EmptyCoroutineContext, timeoutInMs: Long = DEFAULT_TIMEOUT, @BuilderInference block: suspend LiveDataScope<T>.() -> Unit ): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)
因此咱们能够直接使用liveData来是实现Coroutine效果,咱们来看下面一段代码
// Room @Query("SELECT * FROM article_model WHERE title = :title LIMIT 1") fun findByTitle(title: String): ArticleModel? // ViewModel fun findByTitle(title: String) = liveData(Dispatchers.IO) { MyApp.db.articleDao().findByTitle(title)?.let { emit(it) } } // Activity private fun checkArticle() { vm.findByTitle("Android Architecture Components Part1:Room").observe(this, Observer { }) }
经过title从数据库中取数据,数据的获取发生在IO线程,一旦数据返回,再经过emit方法将返回的数据发送出去。因此在View层,咱们能够直接使用checkArticle中的方法来监听数据的状态。
另外一方面LiveData有它的active与inactive状态,对于Coroutine也会进行相应的激活与取消。对于激活,若是它已经完成了或者非正常的取消,例如抛出CancelationException异常,此时将不会自动激活。
对于发送数据,还可使用emitSource,它与emit共同点是在发送新的数据以前都会将原数据清除,而不一样点是,emitSource会返回一个DisposableHandle对象,以即可以调用它的dispose方法进行取消发送。
最后我使用Architecture Component与Coroutine写了个简单的Demo,你们能够在Github中进行查看
源码地址: https://github.com/idisfkj/an...
Android Architecture Components Part1:Room
Android Architecture Components Part2:LiveData
Android Architecture Components Part3:Lifecycle
Android Architecture Components Part4:ViewModel
扫描二维码,关注微信公众号,获取独家最新IT技术!