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 = ""
}
}
取消后的异常
七、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-> 只在当前线程执行,不切换线程,如果是在主线程,刚才的方法会抛出这样的异常
我最后的代码
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的略过
}
}
最后的样子