What? 你还不知道Kotlin Coroutine?

clipboard.png

今天咱们来聊聊Kotlin Coroutine,若是你尚未了解过,那么我要提早恭喜你,由于你将掌握一个新技能,对你的代码方面的提高将是很好的助力。java

What Coroutine

简单的来讲,Coroutine是一个并发的设计模式,你能经过它使用更简洁的代码来解决异步问题。android

例如,在Android方面它主要可以帮助你解决如下两个问题:git

  1. 在主线程中执行耗时任务致使的主线程阻塞,从而使App发生ANR。
  2. 提供主线程安全,同时对来自于主线程的网络回调、磁盘操提供保障。

这些问题,在接下来的文章中我都会给出解决的示例。github

Callback

说到异步问题,咱们先来看下咱们常规的异步处理方式。首先第一种是最基本的callback方式。数据库

callback的好处是使用起来简单,但你在使用的过程当中可能会遇到以下情形设计模式

GatheringVoiceSettingRepository.getInstance().getGeneralSettings(RequestLanguage::class.java)
                .observe(this, { language ->
                    convertResult(language, { enable -> 
                        // todo something
                    })
                })

这种在其中一个callback中回调另外一个callback回调,甚至更多的callback都是可能存在。这些状况致使的问题是代码间的嵌套层级太深,致使逻辑嵌套复杂,后续的维护成本也要提升,这不是咱们所要看到的。api

那么有什么方法可以解决呢?固然有,其中的一种解决方法就是我接下来要说的第二种方式。安全

Rx系列

对多嵌套回调,Rx系列在这方面处理的已经很是好了,例如RxJava。下面咱们来看一下RxJava的解决案例微信

disposable = createCall().map {
            // return RequestType
        }.subscribeWith(object : SMDefaultDisposableObserver<RequestType>{
            override fun onNext(t: RequestType) {
                // todo something
            }
        })

RxJava丰富的操做符,再结合Observable与Subscribe可以很好的解决异步嵌套回调问题。可是它的使用成本就相对提升了,你要对它的操做符要很是了解,避免在使用过程当中滥用或者过分使用,这样天然复杂度就提高了。网络

那么咱们渴望的解决方案是可以更加简单、全面与健壮,而咱们今天的主题Coroutine就可以达到这种效果。

Coroutine在Kotlin中的基本要点

在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: 从它被挂起的位置继续执行,而且挂起时保存的数据也被还原

解释的有点生硬,简单的来讲就是suspend能够将该任务挂起,使它暂时不在调用的线程中,以致于当前线程能够继续执行别的任务,一旦被挂起的任务已经执行完毕,那么就会经过resume将其从新插入到当前线程中。

因此上面的示例展现的是,当get还在请求的时候,fetch方法将会被挂起,直到get结束,此时才会插入到主线程中并返回结果。

一图胜千言,我作了一张图,但愿能有所帮助。

clipboard.png

另外须要注意的是,suspend方法只可以被其它的suspend方法调用或者被一个coroutine调用,例如launch。

Dispatchers

另外一方面Coroutine使用Dispatchers来负责调度协调程序执行的线程,这一点与RxJava的schedules有点相似,但不一样的是Coroutine必定要执行在Dispatchers调度中,由于Dispatchers将负责resume被suspend的任务。

Dispatchers提供三种模式切换,分别为

  1. Dispatchers.Main: 使Coroutine运行中主线程,以便UI操做
  2. Dispatchers.IO: 使Coroutine运行在IO线程,以便执行网络或者I/O操做
  3. Dispatchers.Default: 在主线程以外提升对CPU的利用率,例如对list的排序或者JSON的解析。

再来看上面的示例

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函数。

建立Coroutine

上面提到suspend函数只能在相应的suspend中或者Coroutine中调用。那么Coroutine又该如何建立呢?

有两种方式,分别为launch与async

  1. launch: 开启一个新的Coroutine,但不返回结果
  2. async: 开启一个新的Coroutine,但返回结果

仍是上面的例子,若是咱们须要执行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 Components

若是你使用了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}"
}

ViewModelScope

在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已经清除了,那么数据获取也会中止,防止资源的浪费。

LifecycleScope

对于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中能够直接使用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技术!

clipboard.png

相关文章
相关标签/搜索