前言
Kotlin Coroutine 简介
Kotlin Coroutine Version
Kotlin Coroutine 生态
接入Coroutine
dependencies {
// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.32"
// 协程核心库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
// 协程Android支持库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"
// 协程Java8支持库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.4.3"
// lifecycle对于协程的扩展封装
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
}Coroutine 基本使用
suspend
简单用法
fun test1() {
GlobalScope.launch {
val arg1 = sunpendF1()
var arg2 = sunpendF2()
doLog("suspend finish arg1:$arg1 arg2:$arg2 result:${arg1 + arg2}")
}
}
private suspend fun sunpendF1(): Int {
delay(1000)
doLog("suspend fun 1")
return 2
}
private suspend fun sunpendF2(): Int {
delay(1000)
doLog("suspend fun 2")
return 4
}01-07 19:21:49.626 9616-10074/com.yy.yylite.kotlinshare I/suspend: suspend fun 1 01-07 19:21:50.633 9616-10074/com.yy.yylite.kotlinshare I/suspend: suspend fun 2 suspend finish arg1:2 arg2:4 result:6
Suspend 挂起函数原理
suspend fun test1() {
KLog.i("test1") { "test1" }
val homeItemInfo = HomeItemInfo()
homeItemInfo.adId = "89"
delay(100)
KLog.i("test1") { "test1-end" }
}public final Object test1(@NotNull Continuation var1) {
Object $continuation;
label28: {
if (var1 instanceof ) {
$continuation = ()var1;
if (((()$continuation).getLabel() & Integer.MIN_VALUE) != 0) {
(()$continuation).setLabel((()$continuation).getLabel() - Integer.MIN_VALUE);
break label28;
}
}
$continuation = new CoroutineImpl(var1) {
// $FF: synthetic field
Object data;
// $FF: synthetic field
Throwable exception;
Object L$0;
Object L$1;
@Nullable
public final Object doResume(@Nullable Object data, @Nullable Throwable throwable) {
this.data = data;
this.exception = throwable;
super.label |= Integer.MIN_VALUE;
return SuspendTest.this.test1(this);
}
// $FF: synthetic method
final int getLabel() {
return super.label;
}
// $FF: synthetic method
final void setLabel(int var1) {
super.label = var1;
}
};
}
Object var3 = (()$continuation).data;
Throwable var4 = (()$continuation).exception;
Object var6 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
HomeItemInfo homeItemInfo;
switch((()$continuation).getLabel()) {
case 0:
if (var4 != null) {
throw var4;
}
KLog.INSTANCE.i("test1", (Function0)null.INSTANCE);
homeItemInfo = new HomeItemInfo();
homeItemInfo.adId = "89";
(()$continuation).L$0 = this;
(()$continuation).L$1 = homeItemInfo;
(()$continuation).setLabel(1);
if (DelayKt.delay(100, (Continuation)$continuation) == var6) {
return var6;
}
break;
case 1:
homeItemInfo = (HomeItemInfo)(()$continuation).L$1;
SuspendTest var7 = (SuspendTest)(()$continuation).L$0;
if (var4 != null) {
throw var4;
}
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
KLog.INSTANCE.i("test1", (Function0)null.INSTANCE);
return Unit.INSTANCE;
} public interface Continuation{ /** * Context of the coroutine that corresponds to this continuation. */ // todo: shall we provide default impl with EmptyCoroutineContext? public val context: CoroutineContext /** * Resumes the execution of the corresponding coroutine passing successful or failed [result] as the * return value of the last suspension point. */ public fun resumeWith(result: Result ) }
kotlinx/coroutines/Deferred.delay (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
创建协程
CoroutineScope.launch()
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
/**
* 使用官方库的 MainScope()获取一个协程作用域用于创建协程
*/
private val mScope = MainScope()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 创建一个默认参数的协程,其默认的调度模式为Main 也就是说该协程的线程环境是Main线程
val job1 = mScope.launch {
// 这里就是协程体
// 延迟1000毫秒 delay是一个挂起函数
// 在这1000毫秒内该协程所处的线程不会阻塞
// 协程将线程的执行权交出去,该线程该干嘛干嘛,到时间后会恢复至此继续向下执行
delay(1000)
}
// 创建一个指定了调度模式的协程,该协程的运行线程为IO线程
val job2 = mScope.launch(Dispatchers.IO) {
// 此处是IO线程模式
// 切线程 将协程所处的线程环境切至指定的调度模式Main
withContext(Dispatchers.Main) {
// 现在这里就是Main线程了 可以在此进行UI操作了
}
}
// 下面直接看一个例子: 从网络中获取数据 并更新UI
// 该例子不会阻塞主线程
mScope.launch(Dispatchers.IO) {
// 执行getUserInfo方法时会将线程切至IO去执行
val userInfo = getUserInfo()
// 获取完数据后 切至Main线程进行更新UI
withContext(Dispatchers.Main) {
// 更新UI
}
}
}
/**
* 获取用户信息 该函数模拟IO获取数据
* @return String
*/
private suspend fun getUserInfo(): String {
return withContext(Dispatchers.IO) {
delay(2000)
"Kotlin"
}
}
override fun onDestroy() {
super.onDestroy()
// 取消协程 防止协程泄漏 如果使用lifecycleScope则不需要手动取消
mScope.cancel()
}
}CoroutineScope.async()
fun asyncTest() {
mScope.launch {
// 开启一个IO模式的线程 并返回一个Deferred,Deferred可以用来获取返回值
// 代码执行到此处时会新开一个协程 然后去执行协程体 父协程的代码会接着往下走
val deferred = async(Dispatchers.IO) {
// 模拟耗时
delay(2000)
// 返回一个值
"Quyunshuo"
}
// 等待async执行完成获取返回值 此处并不会阻塞线程 而是挂起 将线程的执行权交出去
// 等到async的协程体执行完毕后 会恢复协程继续往下执行
val date = deferred.await()
}
}fun asyncTest2() {
mScope.launch {
// 此处有一个需求 同时请求5个接口 并且将返回值拼接起来
val job1 = async {
// 请求1
delay(5000)
"1"
}
val job2 = async {
// 请求2
delay(5000)
"2"
}
val job3 = async {
// 请求3
delay(5000)
"3"
}
val job4 = async {
// 请求4
delay(5000)
"4"
}
val job5 = async {
// 请求5
delay(5000)
"5"
}
// 代码执行到此处时 5个请求已经同时在执行了
// 等待各job执行完 将结果合并
Log.d(
"TAG",
"asyncTest2: ${job1.await()} ${job2.await()} ${job3.await()} ${job4.await()} ${job5.await()}"
)
// 因为我们设置的模拟时间都是5000毫秒 所以当job1执行完时 其他job也均执行完成
}
}Coroutine的深入
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext, // 协程上下文
start: CoroutineStart = CoroutineStart.DEFAULT, // 协程启动模式
block: suspend CoroutineScope.() -> Unit // 运行在协程的逻辑
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}CoroutineContext - 协程上下文
fun main() {
val coroutineContext = Job() + Dispatchers.Default + CoroutineName("myContext")
println("$coroutineContext,${coroutineContext[CoroutineName]}")
val newCoroutineContext = coroutineContext.minusKey(CoroutineName)
println("$newCoroutineContext")
}[JobImpl{Active}@7eda2dbb, CoroutineName(myContext), Dispatchers.Default],CoroutineName(myContext)
[JobImpl{Active}@7eda2dbb, Dispatchers.Default]public interface CoroutineContext {
public operator fun get(key: Key): E?
public fun fold(initial: R, operation: (R, Element) -> R): R
public operator fun plus(context: CoroutineContext): CoroutineContext{...}
public fun minusKey(key: Key<*>): CoroutineContext
public interface Key
public interface Element : CoroutineContext {...}
} Job & Deferred - 任务
Job 的状态
| State | [isActive] | [isCompleted] | [isCancelled] |
|---|---|---|---|
| New (optional initial state) |
false |
false |
false |
| Active (default initial state) |
true |
false |
false |
| Completing (transient state) |
true |
false |
false |
| Cancelling (transient state) |
false |
false |
true |
| Cancelled (final state) |
false |
true |
true |
| Completed (final state) |
false |
true |
false |
wait children +-----+ start +--------+ complete +-------------+ finish +-----------+ | New | -----> | Active | ---------> | Completing | -------> | Completed | +-----+ +--------+ +-------------+ +-----------+ | cancel / fail | | +----------------+ | | V V +------------+ finish +-----------+ | Cancelling | --------------------------------> | Cancelled | +------------+ +-----------+
Job 的常用函数
Deferred
public interface Deferred: Job { public val onAwait: SelectClause1 public suspend fun await(): T @ExperimentalCoroutinesApi public fun getCompleted(): T @ExperimentalCoroutinesApi public fun getCompletionExceptionOrNull(): Throwable? }
SupervisorJob
/**
* Creates a _supervisor_ job object in an active state.
* Children of a supervisor job can fail independently of each other.
*
* A failure or cancellation of a child does not cause the supervisor job to fail and does not affect its other children,
* so a supervisor can implement a custom policy for handling failures of its children:
*
* * A failure of a child job that was created using [launch][CoroutineScope.launch] can be handled via [CoroutineExceptionHandler] in the context.
* * A failure of a child job that was created using [async][CoroutineScope.async] can be handled via [Deferred.await] on the resulting deferred value.
*
* If [parent] job is specified, then this supervisor job becomes a child job of its parent and is cancelled when its
* parent fails or is cancelled. All this supervisor's children are cancelled in this case, too. The invocation of
* [cancel][Job.cancel] with exception (other than [CancellationException]) on this supervisor job also cancels parent.
*
* @param parent an optional parent job.
*/
@Suppress("FunctionName")
public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)/**
* Creates the main [CoroutineScope] for UI components.
*
* Example of use:
* ```
* class MyAndroidActivity {
* private val scope = MainScope()
*
* override fun onDestroy() {
* super.onDestroy()
* scope.cancel()
* }
* }
* ```
*
* The resulting scope has [SupervisorJob] and [Dispatchers.Main] context elements.
* If you want to append additional elements to the main scope, use [CoroutineScope.plus] operator:
* `val scope = MainScope() + CoroutineName("MyActivity")`.
*/
@Suppress("FunctionName")
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)CoroutineDispatcher - 调度器
CoroutineStart - 协程启动模式
CoroutineScope - 协程作用域
CoroutineScope 接口
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}分类及行为规则
常用作用域
GlobalScope - 不推荐使用
public object GlobalScope : CoroutineScope {
/**
* Returns [EmptyCoroutineContext].
*/
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}runBlocking{} - 主要用于测试
/** * Runs a new coroutine and **blocks** the current thread _interruptibly_ until its completion. * This function should not be used from a coroutine. It is designed to bridge regular blocking code * to libraries that are written in suspending style, to be used in `main` functions and in tests. * * The default [CoroutineDispatcher] for this builder is an internal implementation of event loop that processes continuations * in this blocked thread until the completion of this coroutine. * See [CoroutineDispatcher] for the other implementations that are provided by `kotlinx.coroutines`. * * When [CoroutineDispatcher] is explicitly specified in the [context], then the new coroutine runs in the context of * the specified dispatcher while the current thread is blocked. If the specified dispatcher is an event loop of another `runBlocking`, * then this invocation uses the outer event loop. * * If this blocked thread is interrupted (see [Thread.interrupt]), then the coroutine job is cancelled and * this `runBlocking` invocation throws [InterruptedException]. * * See [newCoroutineContext][CoroutineScope.newCoroutineContext] for a description of debugging facilities that are available * for a newly created coroutine. * * @param context the context of the coroutine. The default value is an event loop on the current thread. * @param block the coroutine code. */ @Throws(InterruptedException::class) public funrunBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } val currentThread = Thread.currentThread() val contextInterceptor = context[ContinuationInterceptor] val eventLoop: EventLoop? val newContext: CoroutineContext if (contextInterceptor == null) { // create or use private event loop if no dispatcher is specified eventLoop = ThreadLocalEventLoop.eventLoop newContext = GlobalScope.newCoroutineContext(context + eventLoop) } else { // See if context's interceptor is an event loop that we shall use (to support TestContext) // or take an existing thread-local event loop if present to avoid blocking it (but don't create one) eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() } ?: ThreadLocalEventLoop.currentOrNull() newContext = GlobalScope.newCoroutineContext(context) } val coroutine = BlockingCoroutine (newContext, currentThread, eventLoop) coroutine.start(CoroutineStart.DEFAULT, coroutine, block) return coroutine.joinBlocking() }
MainScope() - 可用于开发
/**
* Creates the main [CoroutineScope] for UI components.
*
* Example of use:
* ```
* class MyAndroidActivity {
* private val scope = MainScope()
*
* override fun onDestroy() {
* super.onDestroy()
* scope.cancel()
* }
* }
* ```
*
* The resulting scope has [SupervisorJob] and [Dispatchers.Main] context elements.
* If you want to append additional elements to the main scope, use [CoroutineScope.plus] operator:
* `val scope = MainScope() + CoroutineName("MyActivity")`.
*/
@Suppress("FunctionName")
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)LifecycleOwner.lifecycleScope - 推荐使用
/** * [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle]. * * This scope will be cancelled when the [Lifecycle] is destroyed. * * This scope is bound to * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]. */ val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope get() = lifecycle.coroutineScope
ViewModel.viewModelScope - 推荐使用
/**
* [CoroutineScope] tied to this [ViewModel].
* This scope will be canceled when ViewModel will be cleared, i.e [ViewModel.onCleared] is called
*
* This scope is bound to
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
*/
val ViewModel.viewModelScope: CoroutineScope
get() {
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if (scope != null) {
return scope
}
return setTagIfAbsent(JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
}coroutineScope & supervisorScope
/** * Creates a [CoroutineScope] with [SupervisorJob] and calls the specified suspend block with this scope. * The provided scope inherits its [coroutineContext][CoroutineScope.coroutineContext] from the outer scope, but overrides * context's [Job] with [SupervisorJob]. * * A failure of a child does not cause this scope to fail and does not affect its other children, * so a custom policy for handling failures of its children can be implemented. See [SupervisorJob] for details. * A failure of the scope itself (exception thrown in the [block] or cancellation) fails the scope with all its children, * but does not cancel parent job. */ public suspend funsupervisorScope(block: suspend CoroutineScope.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return suspendCoroutineUninterceptedOrReturn { uCont -> val coroutine = SupervisorCoroutine(uCont.context, uCont) coroutine.startUndispatchedOrReturn(coroutine, block) } } /** * Creates a [CoroutineScope] and calls the specified suspend block with this scope. * The provided scope inherits its [coroutineContext][CoroutineScope.coroutineContext] from the outer scope, but overrides * the context's [Job]. * * This function is designed for _parallel decomposition_ of work. When any child coroutine in this scope fails, * this scope fails and all the rest of the children are cancelled (for a different behavior see [supervisorScope]). * This function returns as soon as the given block and all its children coroutines are completed. * A usage example of a scope looks like this: * * ``` * suspend fun showSomeData() = coroutineScope { * val data = async(Dispatchers.IO) { // <- extension on current scope * ... load some UI data for the Main thread ... * } * * withContext(Dispatchers.Main) { * doSomeWork() * val result = data.await() * display(result) * } * } * ``` * * The scope in this example has the following semantics: * 1) `showSomeData` returns as soon as the data is loaded and displayed in the UI. * 2) If `doSomeWork` throws an exception, then the `async` task is cancelled and `showSomeData` rethrows that exception. * 3) If the outer scope of `showSomeData` is cancelled, both started `async` and `withContext` blocks are cancelled. * 4) If the `async` block fails, `withContext` will be cancelled. * * The method may throw a [CancellationException] if the current job was cancelled externally * or may throw a corresponding unhandled [Throwable] if there is any unhandled exception in this scope * (for example, from a crashed coroutine that was started with [launch][CoroutineScope.launch] in this scope). */ public suspend fun coroutineScope(block: suspend CoroutineScope.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return suspendCoroutineUninterceptedOrReturn { uCont -> val coroutine = ScopeCoroutine(uCont.context, uCont) coroutine.startUndispatchedOrReturn(coroutine, block) } }
协程的取消和异常
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
/**
* 使用官方库的 MainScope()获取一个协程作用域用于创建协程
*/
private val mScope = MainScope()
companion object {
const val TAG = "Kotlin Coroutine"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mScope.launch(Dispatchers.Default) {
delay(500)
Log.e(TAG, "Child 1")
}
mScope.launch(Dispatchers.Default) {
delay(1000)
Log.e(TAG, "Child 2")
throw RuntimeException("--> RuntimeException <--")
}
mScope.launch(Dispatchers.Default) {
delay(1500)
Log.e(TAG, "Child 3")
}
}
}
打印结果:
E/Kotlin Coroutine: Child 1
E/Kotlin Coroutine: Child 2
E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-3
Process: com.quyunshuo.kotlincoroutine, PID: 24240
java.lang.RuntimeException: --> RuntimeException <--
at com.quyunshuo.kotlincoroutine.MainActivity$onCreate$2.invokeSuspend(MainActivity.kt:31)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
E/Kotlin Coroutine: Child 3override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mScope.launch(Dispatchers.Default) {
delay(500)
Log.e(TAG, "Child 1")
}
// 在Child 2的上下文添加了异常处理
mScope.launch(Dispatchers.Default + CoroutineExceptionHandler { coroutineContext, throwable ->
Log.e(TAG, "CoroutineExceptionHandler: $throwable")
}) {
delay(1000)
Log.e(TAG, "Child 2")
throw RuntimeException("--> RuntimeException <--")
}
mScope.launch(Dispatchers.Default) {
delay(1500)
Log.e(TAG, "Child 3")
}
}
输出结果:
E/Kotlin Coroutine: Child 1
E/Kotlin Coroutine: Child 2
E/Kotlin Coroutine: CoroutineExceptionHandler: java.lang.RuntimeException: --> RuntimeException <--
E/Kotlin Coroutine: Child 3import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "Kotlin Coroutine"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val scope = CoroutineScope(Job() + Dispatchers.Default)
scope.launch(CoroutineExceptionHandler { coroutineContext, throwable ->
Log.e(TAG, "CoroutineExceptionHandler: $throwable")
}) {
supervisorScope {
launch {
delay(500)
Log.e(TAG, "Child 1 ")
}
launch {
delay(1000)
Log.e(TAG, "Child 2 ")
throw RuntimeException("--> RuntimeException <--")
}
launch {
delay(1500)
Log.e(TAG, "Child 3 ")
}
}
}
}
}
输出结果:
E/Kotlin Coroutine: Child 1
E/Kotlin Coroutine: Child 2
E/Kotlin Coroutine: CoroutineExceptionHandler: java.lang.RuntimeException: --> RuntimeException <--
E/Kotlin Coroutine: Child 3结语





