kotlin中异步处理框架-协程(Coroutines)的使用和配置

前言:
本人通过对kotlin由浅到深使用了一年半,并且多个项目已基本达到纯kotlin开发。越来越觉得kotlin对于开发Android来说,不仅仅是多了一门开发语言,更是提升开发效率和优化程序设计的利器!值得Android开发者放心学习和语言转换,有Google做靠山,不用担心kotlin的前景问题。用了kotlin,你会发现其他所有语言在开发Android时,都显得那么臃肿和啰嗦。

今天我们主要简单讲一下kotlin的基础配置和异步处理框架-协程(coroutines)的配置。之所以将kotlin的基础配置和协程放在一起,是因为协程的使用只能在kotlin语言环境里,不能在Java里使用,况且我们任何一个App的开发,肯定都会涉及到异步耗时任务的执行至于为什么要特意讲一下协程,你在网上搜一下就知道,这个框架是多么强大和神奇!

不仅是我们处理异步任务时的简单高效的工具,更是结合了原来
Thread+Handler+Runable+RxAndroid这几大切换器和控件的使用场景,是我们处理异步任务的不二选择!

下面我们直接通过代码看看我们需要干的几件事:
1、项目根目录gradle中:

buildscript {
    ext.kotlin_version = '1.2.71'
        ......
    dependencies {
        .....
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
        .....
    }
}

2、app/gradle中:

.....
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
......
kotlin {
    experimental {
        coroutines 'enable'
    }
}
dependencies {
    ....
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    //coroutines的配置重点是这里
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.22.5'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.22.5'
    .....
}

通过以上两步,基本完成了对kotlin和coroutines的基础配置,那么代码里我们可以直接使用了:

fun getData(){
        //启动线程
        val job = launch {
            //加载数据任务一
            val load = loadData()
            //刷新界面
            launch(UI) {
                Toast.makeText(this@MainActivity, load, Toast.LENGTH_SHORT).show()
                Log.d("launch", load)
            }
              //加载数据任务二
            val select = selectData()
            launch(UI) {
                Toast.makeText(this@MainActivity, select, Toast.LENGTH_SHORT).show()
                Log.d("launch", select)
            }
        }
}

//封装的界面刷新方法,不用每次都调用launch(UI)了
fun launchUi(start: CoroutineStart = CoroutineStart.DEFAULT,
                 parent: Job? = null,
                 block: suspend CoroutineScope.() -> Unit) = launch(UI, start, parent, block)

如果你想让 getData()的执行进行加锁,直接这样就可以:

fun getData()= runBlocking{
......
}

如果你想在界面关闭时停止任务:

if (job.isActive)
   job.cancelAndJoin()

如果要在任务里delay一会:

val launch = launch {
...
//等待执行,相当于Thread.sleep(1000)
delay(1000)
...
}

原理

1、构造参数:
既然coroutines这么好用,那么我们来看看它内部到底怎么实现的:
打开launch:

public actual fun launch(
    context: CoroutineContext = DefaultDispatcher,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    parent: Job? = null,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context, parent)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

/** @suppress **Deprecated**: Binary compatibility */
@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
public fun launch(
    context: CoroutineContext = DefaultDispatcher,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job =
    launch(context, start, block = block)

/**
 * @suppress **Deprecated**: Use `start = CoroutineStart.XXX` parameter
 */
@Deprecated(message = "Use `start = CoroutineStart.XXX` parameter",
    replaceWith = ReplaceWith("launch(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block)"))
public fun launch(context: CoroutineContext, start: Boolean, block: suspend CoroutineScope.() -> Unit): Job =
    launch(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block = block)

我们会发现它有如上三种调用方式,默认我们会使用第一种方式。不管哪种方式都有一个DefaultDispatcher的参数,这个参数就是我们启动异步的上下文,继续进入DefaultDispatcher:

@Suppress("PropertyName")
public actual val DefaultDispatcher: CoroutineDispatcher = CommonPool

到这貌似看出一点端倪了,继续进入CommonPool:

object CommonPool : CoroutineDispatcher() {
    private var usePrivatePool = false

    @Volatile
    private var _pool: Executor? = null

    private inline fun  Try(block: () -> T) = try { block() } catch (e: Throwable) { null }

    private fun createPool(): ExecutorService {
        if (System.getSecurityManager() != null) return createPlainPool()
        val fjpClass = Try { Class.forName("java.util.concurrent.ForkJoinPool") }
            ?: return createPlainPool()
        if (!usePrivatePool) {
            Try { fjpClass.getMethod("commonPool")?.invoke(null) as? ExecutorService }
                ?.let { return it }
        }
        Try { fjpClass.getConstructor(Int::class.java).newInstance(defaultParallelism()) as? ExecutorService }
            ?. let { return it }
        return createPlainPool()
    }

    private fun createPlainPool(): ExecutorService {
        val threadId = AtomicInteger()
        return Executors.newFixedThreadPool(defaultParallelism()) {
            Thread(it, "CommonPool-worker-${threadId.incrementAndGet()}").apply { isDaemon = true }
        }
    }

    private fun defaultParallelism() = (Runtime.getRuntime().availableProcessors() - 1).coerceAtLeast(1)

    @Synchronized
    private fun getOrCreatePoolSync(): Executor =
        _pool ?: createPool().also { _pool = it }

    override fun dispatch(context: CoroutineContext, block: Runnable) =
        try { (_pool ?: getOrCreatePoolSync()).execute(timeSource.trackTask(block)) }
        catch (e: RejectedExecutionException) {
            timeSource.unTrackTask()
            DefaultExecutor.execute(block)
        }

    // used for tests
    @Synchronized
    internal fun usePrivatePool() {
        shutdown(0)
        usePrivatePool = true
        _pool = null
    }

    // used for tests
    @Synchronized
    internal fun shutdown(timeout: Long) {
        (_pool as? ExecutorService)?.apply {
            shutdown()
            if (timeout > 0)
                awaitTermination(timeout, TimeUnit.MILLISECONDS)
            shutdownNow().forEach { DefaultExecutor.execute(it) }
        }
        _pool = Executor { throw RejectedExecutionException("CommonPool was shutdown") }
    }

    // used for tests
    @Synchronized
    internal fun restore() {
        shutdown(0)
        usePrivatePool = false
        _pool = null
    }

    override fun toString(): String = "CommonPool"
}

到这已经非常清楚了,这是一个由ExecutorService构建的线程池,由上面的defaultParallelism()完成。
除此之外,我们看到了DefaultDispatcher继承了一个CoroutineDispatcher的类:


public expect abstract class CoroutineDispatcher constructor() : AbstractCoroutineContextElement, ContinuationInterceptor {
    public open fun isDispatchNeeded(context: CoroutineContext): Boolean
    public abstract fun dispatch(context: CoroutineContext, block: Runnable)
    public override fun  interceptContinuation(continuation: Continuation): Continuation
}

public expect interface Runnable {
    public fun run()
}

@Suppress("PropertyName")
public expect val DefaultDispatcher: CoroutineDispatcher

打开这个类我们发现就是一些要实现的抽象方法,没啥别的了。

回到上面开始讲的launch的构造方式中,除了CoroutineContext,还有第二个参数start,这个我们通过源码可以看出它是一个枚举类,它的意思是选择协程启动的模式:DEFAULT、LAZY、ATOMIC、UNDISPATCHED:

public operator fun  invoke(block: suspend () -> T, completion: Continuation) =
        when (this) {
            CoroutineStart.DEFAULT -> block.startCoroutineCancellable(completion)
            CoroutineStart.ATOMIC -> block.startCoroutine(completion)
            CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(completion)
            CoroutineStart.LAZY -> Unit // will start lazily
        }

参数三Job就是我们要构造的返回对象,这里也就是任务的主要执行处。参数四CoroutineScope作用时任务执行外界可回调的状态、上下文对象。

2、任务的执行
构造参数的父类中通过接收子类传递的参数再次讲参数封装完整后,最后调用start()方式启动:

val newContext = newCoroutineContext(context, parent)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)

我们进入start里:

public abstract class AbstractCoroutine{
........
public fun  start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
        initParentJob()
        start(block, receiver, this)
    }
........
    }

继续进入initParentJob:

public actual interface Job : CoroutineContext.Element {
......
internal actual fun initParentJobInternal(parent: Job?) {
        check(parentHandle == null)
        if (parent == null) {
            parentHandle = NonDisposableHandle
            return
        }
        parent.start() // make sure the parent is started
        @Suppress("DEPRECATION")
        val handle = parent.attachChild(this)
        parentHandle = handle
        // now check our state _after_ registering (see updateState order of actions)
        if (isCompleted) {
            handle.dispose()
            parentHandle = NonDisposableHandle // release it just in case, to aid GC
        }
    }
......
}

到这里算是我们能跟踪的都跟完了。以上是coroutine的整个执行流程,源码大概不算太复杂,内部的维护也算很周全,例如加锁、取消、delay、多任务并发它都做了处理,所以我们不妨在开发中尝试一下。

你可能感兴趣的:(开源框架,框架)