我的Kotlin 学习之路(六)Kotlin之coroutines 框架的使用

Coroutine -> 协程
不同于线程,协程不占用CPU,它只占用内存来处理耗时操作。Coroutine的原理有大牛的视频已经介绍了,我就不搬门弄斧了,这一章主要讲讲Kotlinx.coroutines 这个库的使用方法,有关于这个的中文讲解还不是很多!

我们依旧使用官方文档中加载google天气的例子来代替耗时操作

一、导入框架
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.16'

二、Request类

/**
 * 网络请求类
 * Created by zr on 2017/5/23.
 */
class Request (val url:String){

    fun run():String{
        Log.e(javaClass.simpleName,"*******************************************")
        Log.e(javaClass.simpleName,"url = "+url)

        var forecastJsonUrl = URL(url).readText()

        Log.e(javaClass.simpleName,"json = "+forecastJsonUrl)
        return forecastJsonUrl
    }
}

三、上最简单的协程


fun coroution(){
        launch(CommonPool){ //协程
            Request(url).run() //耗时操作在CommonPool线程池中
        }
        Log.e(TAG, "开始请求")
    }


结果
06-27 13:01:58.928 16919-16919/test.futsal.com.mykotlin E/MainActivity: 开始请求
06-27 13:01:58.928 16919-19406/test.futsal.com.mykotlin E/Request: *******************************************
06-27 13:01:58.928 16919-19406/test.futsal.com.mykotlin E/Request: url = http://api.openweathermap.org/data/2.5/forecast/daily?mode=json&units=metric&cnt=7&APPID=15646a06818f61f7b8d7823ca833e1ce&id=2038349
06-27 13:01:59.936 16919-19406/test.futsal.com.mykotlin E/Request: json = {"city":{"id":2038349,"name":"Beijing Shi","coord":{"lon":116.3971,"lat":39.9169},"country":"CN","population":0},"cod":"200","message":10.1232268,"cnt":7,"list":[{"dt":1498536000,"temp":{"day":31,"min":23.42,"max":33.85,"night":23.42,"eve":33.61,"morn":31},"pressure":996.6,"humidity":64,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":1.83,"deg":141,"clouds":0},{"dt":1498622400,"temp":{"day":30.6,"min":19.7,"max":33.27,"night":26.23,"eve":32.46,"morn":19.7},"pressure":996.96,"humidity":61,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":2.06,"deg":172,"clouds":0},{"dt":1498708800,"temp":{"day":30.33,"min":21.67,"max":33.34,"night":23.4,"eve":32.87,"morn":21.67},"pressure":997.1,"humidity":62,"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03d"}],"speed":1.63,"deg":146,"clouds":48},{"dt":1498795200,"temp":{"day":32.12,"min":19.89,"max":32.12,"night":19.89,"eve":26.48,"morn":25.23},"pressure":966.77,"humidity":0,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":1.62,"deg":178,"clouds":27},{"dt":1498881600,"temp":{"day":31.48,"min":19.94,"max":31.48,"night":19.94,"eve":26.16,"morn":24.87},"pressure":966.47,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.54,"deg":167,"clouds":43},{"dt":1498968000,"temp":{"day":32.06,"min":20.86,"max":32.06,"night":20.86,"eve":27.1,"morn":25.03},"pressure":965.1,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.05,"deg":119,"clouds":13,"rain":0.38},{"dt":1499054400,"temp":{"day":29.36,"min":21.53,"max":29.36,"night":21.53,"eve":25.73,"morn":24.72},"pressure":965.4,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.35,"deg":136,"clouds":13,"rain":2.03}]}



CommonPool 是 Coroutine的分发类的对象

/*
 * Copyright 2016-2017 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package kotlinx.coroutines.experimental

import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.experimental.CoroutineContext

/**
 * Represents common pool of shared threads as coroutine dispatcher for compute-intensive tasks.
 * It uses [java.util.concurrent.ForkJoinPool] when available, which implements efficient work-stealing algorithm for its queues, so every
 * coroutine resumption is dispatched as a separate task even when it already executes inside the pool.
 * When available, it wraps `ForkJoinPool.commonPool` and provides a similar shared pool where not.
 */
object CommonPool : CoroutineDispatcher() {
    private var usePrivatePool = false

    @Volatile
    private var _pool: ExecutorService? = null

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

    private fun createPool(): ExecutorService {
        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(): ExecutorService =
        _pool ?: createPool().also { _pool = it }

    override fun dispatch(context: CoroutineContext, block: Runnable) =
        (_pool ?: getOrCreatePoolSync()).execute(block)

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

    // used for tests
    @Synchronized
    internal fun shutdownAndRelease(timeout: Long) {
        _pool?.apply {
            shutdown()
            if (timeout > 0)
                awaitTermination(timeout, TimeUnit.MILLISECONDS)
            _pool = null
        }
        usePrivatePool = false
    }

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

四、在主线程中操作

fun coroution() = runBlocking { //runBlocking代表在主线程中
        launch(CommonPool){
            Request(url).run()
        }
        Log.e(TAG, "开始请求")
    }

五、带返回值的协程

fun coroution()= runBlocking {
        val job = async(CommonPool){ //async 替代了launch (defer已过时)
            Request(url).run()
        }
        job.join() // async()结束后才进行下面的代码,否则挂起等待
        
        var result:String = job.await() //await()获得async方法的返回值
        Log.e(TAG,"result = $result")

        result?.let {
            var forecastResult: ResponseClasses.ForecastResult
            forecastResult = Gson().fromJson(result, ResponseClasses.ForecastResult::class.java)
            longToast("成功")
            toolbar.title = forecastResult.city.name
            tv_main.text = ""
        }

    }


结果
06-27 13:17:19.214 21646-22256/test.futsal.com.mykotlin E/Request: *******************************************
06-27 13:17:19.215 21646-22256/test.futsal.com.mykotlin E/Request: url = http://api.openweathermap.org/data/2.5/forecast/daily?mode=json&units=metric&cnt=7&APPID=15646a06818f61f7b8d7823ca833e1ce&id=2038349
06-27 13:17:20.213 21646-22256/test.futsal.com.mykotlin E/Request: json = {"city":{"id":2038349,"name":"Beijing Shi","coord":{"lon":116.3971,"lat":39.9169},"country":"CN","population":0},"cod":"200","message":11.058764,"cnt":7,"list":[{"dt":1498536000,"temp":{"day":31,"min":23.42,"max":33.85,"night":23.42,"eve":33.61,"morn":31},"pressure":996.6,"humidity":64,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":1.83,"deg":141,"clouds":0},{"dt":1498622400,"temp":{"day":30.6,"min":19.7,"max":33.27,"night":26.23,"eve":32.46,"morn":19.7},"pressure":996.96,"humidity":61,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":2.06,"deg":172,"clouds":0},{"dt":1498708800,"temp":{"day":30.33,"min":21.67,"max":33.34,"night":23.4,"eve":32.87,"morn":21.67},"pressure":997.1,"humidity":62,"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03d"}],"speed":1.63,"deg":146,"clouds":48},{"dt":1498795200,"temp":{"day":32.12,"min":19.89,"max":32.12,"night":19.89,"eve":26.48,"morn":25.23},"pressure":966.77,"humidity":0,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":1.62,"deg":178,"clouds":27},{"dt":1498881600,"temp":{"day":31.48,"min":19.94,"max":31.48,"night":19.94,"eve":26.16,"morn":24.87},"pressure":966.47,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.54,"deg":167,"clouds":43},{"dt":1498968000,"temp":{"day":32.06,"min":20.86,"max":32.06,"night":20.86,"eve":27.1,"morn":25.03},"pressure":965.1,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.05,"deg":119,"clouds":13,"rain":0.38},{"dt":1499054400,"temp":{"day":29.36,"min":21.53,"max":29.36,"night":21.53,"eve":25.73,"morn":24.72},"pressure":965.4,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.35,"deg":136,"clouds":13,"rain":2.03}]}
06-27 13:17:20.214 21646-21646/test.futsal.com.mykotlin E/MainActivity: result = {"city":{"id":2038349,"name":"Beijing Shi","coord":{"lon":116.3971,"lat":39.9169},"country":"CN","population":0},"cod":"200","message":11.058764,"cnt":7,"list":[{"dt":1498536000,"temp":{"day":31,"min":23.42,"max":33.85,"night":23.42,"eve":33.61,"morn":31},"pressure":996.6,"humidity":64,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":1.83,"deg":141,"clouds":0},{"dt":1498622400,"temp":{"day":30.6,"min":19.7,"max":33.27,"night":26.23,"eve":32.46,"morn":19.7},"pressure":996.96,"humidity":61,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":2.06,"deg":172,"clouds":0},{"dt":1498708800,"temp":{"day":30.33,"min":21.67,"max":33.34,"night":23.4,"eve":32.87,"morn":21.67},"pressure":997.1,"humidity":62,"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03d"}],"speed":1.63,"deg":146,"clouds":48},{"dt":1498795200,"temp":{"day":32.12,"min":19.89,"max":32.12,"night":19.89,"eve":26.48,"morn":25.23},"pressure":966.77,"humidity":0,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":1.62,"deg":178,"clouds":27},{"dt":1498881600,"temp":{"day":31.48,"min":19.94,"max":31.48,"night":19.94,"eve":26.16,"morn":24.87},"pressure":966.47,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.54,"deg":167,"clouds":43},{"dt":1498968000,"temp":{"day":32.06,"min":20.86,"max":32.06,"night":20.86,"eve":27.1,"morn":25.03},"pressure":965.1,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.05,"deg":119,"clouds":13,"rain":0.38},{"dt":1499054400,"temp":{"day":29.36,"min":21.53,"max":29.36,"night":21.53,"eve":25.73,"morn":24.72},"pressure":965.4,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.35,"deg":136,"clouds":13,"rain":2.03}]}



六、取消

fun coroution()= runBlocking {
        var result:String? = null
        val job = launch(CommonPool){
            result = Request(url).run()
        }
    job.cancel(Exception("Activity is onPause")) //取消并告知原因
    job.join()
    Log.e(TAG,"result = $result")
        result?.let {
            var forecastResult: ResponseClasses.ForecastResult
            forecastResult = Gson().fromJson(result, ResponseClasses.ForecastResult::class.java)
            longToast("成功")
            toolbar.title = forecastResult.city.name
            
            tv_main.text = ""
        }
    }

取消后的异常

我的Kotlin 学习之路(六)Kotlin之coroutines 框架的使用_第1张图片
QQ图片20170627101735.png

七、async(CommonPool)的构造方法

/**
 * Creates new coroutine and returns its future result as an implementation of [Deferred].
 *
 * The running coroutine is cancelled when the resulting object is [cancelled][Job.cancel].
 * The [context] for the new coroutine must be explicitly specified.
 * See [CoroutineDispatcher] for the standard [context] implementations that are provided by `kotlinx.coroutines`.
 * The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
 * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
 *
 * By default, the coroutine is immediately scheduled for execution.
 * Other options can be specified via `start` parameter. See [CoroutineStart] for details.
 * An optional [start] parameter can be set to [CoroutineStart.LAZY] to start coroutine _lazily_. In this case,,
 * the resulting [Deferred] is created in _new_ state. It can be explicitly started with [start][Job.start]
 * function and will be started implicitly on the first invocation of [join][Job.join] or [await][Deferred.await].
 *
 * @param context context of the coroutine
 * @param start coroutine start option
 * @param block the coroutine code
 */
public fun  async(
    context: CoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine(newContext, active = true)
    coroutine.initParentJob(context[Job])
    start(block, coroutine, coroutine)
    return coroutine
}

试试 第二个参数 start: CoroutineStart
async(CommonPool, CoroutineStart.LAZY)
async(CommonPool, CoroutineStart.ATOMIC)
async(CommonPool, CoroutineStart.UNDISPATCHED)
再看CoroutineStart类

/*
 * Copyright 2016-2017 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package kotlinx.coroutines.experimental

import kotlinx.coroutines.experimental.CoroutineStart.*
import kotlinx.coroutines.experimental.intrinsics.startCoroutineUndispatched
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.startCoroutine

/**
 * Defines start option for coroutines builders.
 * It is used in `start` parameter of [launch], [async], and [actor][kotlinx.coroutines.experimental.channels.actor]
 * coroutine builder functions.
 *
 * The summary of coroutine start options is:
 * * [DEFAULT] -- immediately schedules coroutine for execution according to its context;
 * * [LAZY] -- starts coroutine lazily, only when it is needed;
 * * [ATOMIC] -- atomically (non-cancellably) schedules coroutine for execution according to its context;
 * * [UNDISPATCHED] -- immediately executes coroutine until its first suspension point _in the current thread_.
 */
public enum class CoroutineStart {
    /**
     * Default -- immediately schedules coroutine for execution according to its context.
     *
     * If the [CoroutineDispatcher] of the coroutine context returns `true` from [CoroutineDispatcher.isDispatchNeeded]
     * function as most dispatchers do, then the coroutine code is dispatched for execution later, while the code that
     * invoked the coroutine builder continues execution.
     *
     * Note, that [Unconfined] dispatcher always returns `false` from its [CoroutineDispatcher.isDispatchNeeded]
     * function, so starting coroutine with [Unconfined] dispatcher by [DEFAULT] is the same as using [UNDISPATCHED].
     *
     * If coroutine [Job] is cancelled before it even had a chance to start executing, then it will not start its
     * execution at all, but complete with an exception.
     *
     * Cancellability of coroutine at suspension points depends on the particular implementation details of
     * suspending functions. Use [suspendCancellableCoroutine] to implement cancellable suspending functions.
     */
    DEFAULT,

    /**
     * Starts coroutine lazily, only when it is needed.
     *
     * See the documentation for the corresponding coroutine builders for details:
     * [launch], [async], and [actor][kotlinx.coroutines.experimental.channels.actor].
     *
     * If coroutine [Job] is cancelled before it even had a chance to start executing, then it will not start its
     * execution at all, but complete with an exception.
     */
    LAZY,

    /**
     * Atomically (non-cancellably) schedules coroutine for execution according to its context.
     * This is similar to [DEFAULT], but the coroutine cannot be cancelled before it starts executing.
     *
     * Cancellability of coroutine at suspension points depends on the particular implementation details of
     * suspending functions as in [DEFAULT].
     */
    ATOMIC,

    /**
     * Immediately executes coroutine until its first suspension point _in the current thread_ as if it the
     * coroutine was started using [Unconfined] dispatcher. However, when coroutine is resumed from suspension
     * it is dispatched according to the [CoroutineDispatcher] in its context.
     *
     * This is similar to [ATOMIC] in the sense that coroutine starts executing even if it was already cancelled,
     * but the difference is that it start executing in the same thread.
     *
     * Cancellability of coroutine at suspension points depends on the particular implementation details of
     * suspending functions as in [DEFAULT].
     */
    UNDISPATCHED;

    /**
     * Starts the corresponding block as a coroutine with this coroutine start strategy.
     *
     * * [DEFAULT] uses [startCoroutineCancellable].
     * * [ATOMIC] uses [startCoroutine].
     * * [UNDISPATCHED] uses [startCoroutineUndispatched].
     * * [LAZY] does nothing.
     */
    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
        }

    /**
     * Starts the corresponding block with receiver as a coroutine with this coroutine start strategy.
     *
     * * [DEFAULT] uses [startCoroutineCancellable].
     * * [ATOMIC] uses [startCoroutine].
     * * [UNDISPATCHED] uses [startCoroutineUndispatched].
     * * [LAZY] does nothing.
     */
    public operator fun  invoke(block: suspend R.() -> T, receiver: R, completion: Continuation) =
        when (this) {
            CoroutineStart.DEFAULT -> block.startCoroutineCancellable(receiver, completion)
            CoroutineStart.ATOMIC -> block.startCoroutine(receiver, completion)
            CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
            CoroutineStart.LAZY -> Unit // will start lazily
        }

    /**
     * Returns `true` when [LAZY].
     */
    public val isLazy: Boolean get() = this === LAZY
}


(我英语不好,成人三级一直没过,请包涵,但我不会瞎说的)
DEFAULT -> 立即执行耗时操作
LAZY -> 在async 的 await()时才进行耗时操作 Starts coroutine lazily, only when it is needed.
ATOMIC -> 和DEFAULT很接近,但是不能取消 (non-cancellably)
UNDISPATCHED-> 只在当前线程执行,不切换线程,如果是在主线程,刚才的方法会抛出这样的异常

我的Kotlin 学习之路(六)Kotlin之coroutines 框架的使用_第2张图片
QQ图片20170627105748.png

我最后的代码

val url = "http://api.openweathermap.org/data/2.5/forecast/daily?mode=json&units=metric&cnt=7&APPID=15646a06818f61f7b8d7823ca833e1ce&id=2038349"

fun coroution()= runBlocking {
        val result = async(CommonPool){
            Request(url).run()
        }.await()

        Log.e(TAG,"result = $result")
        result?.let {
            var forecastResult: ResponseClasses.ForecastResult
            forecastResult = Gson().fromJson(result, ResponseClasses.ForecastResult::class.java)
            longToast("成功")
            toolbar.title = forecastResult.city.name
            tv_main.text = ""
            //加载adapter的略过
        }

    }

最后的样子

我的Kotlin 学习之路(六)Kotlin之coroutines 框架的使用_第3张图片
QQ图片20170627123059.png

你可能感兴趣的:(我的Kotlin 学习之路(六)Kotlin之coroutines 框架的使用)