コルーチンとは
Androidのkotlinのコルーチンについて、コルーチンはkotlinが公式に提供しているスレッド切り替えAPI群で、スレッドプールと同じ機能を持ちますが、非同期のコードを同期的に記述できるのが利点です。コルーチンは jvm 上で実行されるため、jvm にはコルーチンの概念がありません。
Kotlin のコルーチンが現在のスレッドで実行されている場合、それは現在のスレッドのハンドラー キューにタスクをポストすることと同じであり、これによってもブロックが発生します。コルーチンがスレッドを切り替えない場合も、ブロックが発生する可能性があることに注意してください。これについては後で確認します。
さて、それでは使ってみましょう
コルーチンを使用する場合
Kotlin では、コルーチンは標準ライブラリに含まれていないため、使用するときに引き続き導入する必要があります。
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"
2 番目のライブラリは Android 環境によって使用されます。
コルーチンを開始するには 2 つの方法があります。
launch { }
async { }
これら 2 つの方法を見てみましょう
打ち上げ
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
}
非同期
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine<T>(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
これら 2 つのメソッドは CoroutineScope (コルーチン スコープ) クラスの拡張関数であることがわかり、これら 2 つの関数はコルーチン スコープ オブジェクトを使用して呼び出す必要があります。
違いは、lanch は実行結果を返さないのに対し、async は Deferred オブジェクトの await() を通じて Deferred オブジェクトを返すことです。
このメソッドはコルーチンの実行結果を取得できます。
まだ1つあります
withContext(Dispatchers.Main){ }
彼は実は
非同期 { }.await()
await() メソッドは一時停止関数であるため、コルーチンがブロックされます。
いくつかの用語を説明します。
CoroutineScope: コルーチン スコープ
public interface CoroutineScope {
/**
* The context of this scope.
* Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
* Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
*
* By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
*/
public val coroutineContext: CoroutineContext
}
コルーチン スコープには、コルーチン コンテキストという属性が 1 つだけあります。これは、 coroutineScope がCoroutineContextのカプセル化であることを意味します。これは、コルーチンの実行スコープ (またはライフサイクル) を指し、ユーザーがコルーチンの継承とキャンセル、および操作の停止に使用します。自分をコントロールできる
コルーチンコンテキスト:
コルーチンのコンテキストは、実行中のコルーチンの構成情報、コルーチンの名前、コルーチンが実行されるスレッドなど、コルーチンの実行に必要な構成として理解できます。
coroutineContext クラスを見てみましょう。これは実際には、ハッシュ ストレージと同様に、さまざまな属性を格納するコンテナーに相当します。
public interface CoroutineContext {
//重写了+号运算符
public operator fun plus(context: CoroutineContext): CoroutineContext =
if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
context.fold(this) { acc, element ->
val removed = acc.minusKey(element.key)
if (removed === EmptyCoroutineContext) element else {
// make sure interceptor is always last in the context (and thus is fast to get when present)
val interceptor = removed[ContinuationInterceptor]
if (interceptor == null) CombinedContext(removed, element) else {
val left = removed.minusKey(ContinuationInterceptor)
if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
CombinedContext(CombinedContext(left, element), interceptor)
}
}
}
public interface Key<E : Element>
public val key: Key<*>
public override operator fun <E : Element> get(key: Key<E>): E? =
@Suppress("UNCHECKED_CAST")
if (this.key == key) this as E else null
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(initial, this)
public override fun minusKey(key: Key<*>): CoroutineContext =
if (this.key == key) EmptyCoroutineContext else this
}
}
それで彼はそれをこのように使うことができます
起動関数を見てみましょう。coroutineContext を渡すことができます。
このように使えます
launch(Dispatchers.Main+CoroutineName("协程名称")) { }
CoroutineDispatcher コルーチン スケジューラ
Kotlin は 4 つのスケジューラーを提供します
public actual object Dispatchers {
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
public val IO: CoroutineDispatcher = DefaultScheduler.IO
}
デフォルト: デフォルトのスケジューラである CPU 集中型のタスク スケジューラは、通常、いくつかの単純なコンピューティング タスク、または実行時間の短いタスクを処理します。たとえば、データ計算
IO: IO スケジューラ、IO 集約型タスク スケジューラ。IO 関連の操作の実行に適しています。例: ネットワーク リクエスト、データベース操作、ファイル操作など
メイン: UI スケジューラ、UI プログラミング プラットフォームでのみ意味があり、Android のメイン スレッドなどの UI を更新するために使用されます 制限なし: 制限のないスケジューラ、スケジューラなし、
現在コルーチンはどのスレッドでも実行可能
CoroutineStart: コルーチンの起動モード
public enum class CoroutineStart {
DEFAULT,
LAZY,
ATOMIC,
UNDISPATCHED;
}
CoroutineStart は 4 つの型を持つ列挙クラスであることがわかります。
DEFAULT はデフォルトの起動モードです。スケジューリングはコルーチンの作成後すぐに開始されます。すぐに実行されるのではなく、すぐにスケジュールされることに注意してください。実行前にキャンセルされる場合があります。
LAZY 遅延起動モードでは、作成後にスケジューリング動作が行われず、実行が必要になるまでスケジューリングは行われません。スケジュールは、ジョブの start、join、または await 関数を手動で呼び出す必要がある場合にのみ開始されます。
ATOMICはコルーチン作成後すぐにスケジューリングを開始しますが、DEFAULTモードとは異なり、コルーチン起動後、キャンセル操作に応答する前に最初の一時停止ポイントまで実行する必要があります。
このモードでは、UNDISPATCHED コルーチンは、最初の一時停止ポイントに到達するまで、現在のスレッドで直接実行を開始します。ATOMIC とよく似ていますが、UNDISPATCHED はスケジューラの影響を大きく受けます。
仕事
launch() メソッドは、コルーチンのライフサイクルを検出し、コルーチンの実行をキャンセルできるジョブ オブジェクトを返します。
public interface Job : CoroutineContext.Element {
public companion object Key : CoroutineContext.Key<Job>
//判断协程是否是活的
public val isActive: Boolean
//判断协程是否完成
public val isCompleted: Boolean
//判断协程是否取消
public val isCancelled: Boolean
public fun getCancellationException(): CancellationException
//开始协程
public fun start(): Boolean
//取消协程
public fun cancel(cause: CancellationException? = null)
遅延は実際にはジョブを継承します
したがって、コルーチンを操作するメソッドもあり、コルーチンによって返された値を取得するための重要なメソッドである await() メソッドも提供されています。await は一時停止関数であるため、ここで実行されると、現在のコルーチンはブロックされます。 . .
public interface Deferred<out T> : Job {
public suspend fun await(): T
サスペンドは中国語で一時停止を意味します
サスペンド関数は現在のコルーチンをブロックしますが、コルーチンを開始したスレッドはブロックしません。
関数の前でサスペンドが宣言されると、その関数はサスペンド関数になります。サスペンド関数はサスペンド関数内でのみ呼び出すことができますが、これは単なる注意としてのみ機能します。実際にサスペンドの役割を果たすのは関数内のコードです。したがって、サスペンド関数を宣言しても、その中に実際のサスペンド操作が存在しない場合、コンパイラはその関数をグレー表示します。これは、この関数を宣言する必要がないことを意味します。では、この宣言は何に役立つのでしょうか? これは、作成者が呼び出し元にリマインダーを与えるために使用され、この関数はハング関数であり、時間がかかる関数である可能性があることを呼び出し元に伝えます。
サスペンド機能の機能:
コルーチンが一時停止された関数に到達すると、ここで一時停止します。一時停止された関数の実行に切り替わります。一時停止された関数の実行後、スイッチを戻して再び実行されます。実際には、これはコールバック + ステート マシンのメカニズムです。
システムが提供するリアルサスペンド機能を追加すれば大丈夫です。
親コルーチンと子コルーチンの関係
1.子コルーチンのコルーチン コンテキスト情報は、自分で変更しない限り、親コルーチンにコピーされます。
2. 親コルーチンが終了すると、その中にあるすべての子コルーチンが終了します。
最初の点を証明してください:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GlobalScope.launch(Dispatchers.Main+CoroutineName("父协程名字")) {
Log.e("---","协程上下文-1=${this.coroutineContext}")
launch {
Log.e("---","协程上下文-2=${this.coroutineContext}")
}
withContext(Dispatchers.IO+CoroutineName("子协程")){
Log.e("---","协程上下文-2=${this.coroutineContext}")
}
}
}
印刷結果:
E/---: コルーチン コンテキスト-1=[CoroutineName(親コルーチン名), StandaloneCoroutine{Active}@d2a3906, Dispatchers.Main]
E/---: コルーチン コンテキスト-3=[CoroutineName(子コルーチン名) ), DispatchedCoroutine{Active}@8fe4bc7, Dispatchers.IO]
E/---: コルーチン context-2=[CoroutineName (親コルーチン名), StandaloneCoroutine{Active}@24715f4, Dispatchers.Main]
コルーチン 2 と親コルーチンのコルーチン コンテキストの構成情報は同じですが、コルーチン 3 は異なっていることがわかり、子コルーチンは親コルーチンのコルーチン構成情報を継承することがわかります。
CoroutineExceptionHandler コルーチン例外処理
コードを書いていると必ず異常事態に遭遇するもので、例外処理には通常try...catchを使いますが、抜け漏れが発生するのは避けられません。CoroutineExceptionHandler は、コルーチンでの例外のキャッチに特化したクラスです。コルーチンで発生した例外は、CoroutineExceptionHandler の handleException メソッドによってキャッチされ、処理のために返されます。
public interface CoroutineExceptionHandler : CoroutineContext.Element {
public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>
public fun handleException(context: CoroutineContext, exception: Throwable)
}
handleException は 2 つのパラメータを返します。最初のパラメータは例外が発生したコルーチンで、2 番目のパラメータは発生した例外です。
例は次のとおりです。NullPointerException 例外を手動でスローし、CoroutineExceptionHandler を作成してコルーチンに割り当てます。
val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
Log.e("捕获异常", "${coroutineContext[CoroutineName]} :$throwable")
}
GlobalScope.launch(Dispatchers.Main + CoroutineName("主协程")+exceptionHandler) {
Log.e("协程的coroutineContext",this.coroutineContext.toString())
throw NullPointerException()
}
打印结果:
协程的coroutineContext: [CoroutineName(主协程), com.jf.simple.ThirdActivity$onCreate$$inlined$CoroutineExceptionHandler$1@288ff9, StandaloneCoroutine{Active}@b95b3e, Dispatchers.Main]
捕获异常: CoroutineName(主协程) :java.lang.NullPointerException
問題分析
1. 実行中のコルーチンが、コルーチンを開始する現在のスレッドで実行されている場合、それはスレッドのハンドラー タスク スタックにタスクをポストすることと同じですか?
テストコード:
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GlobalScope.launch(Dispatchers.Main+CoroutineName("父协程名字")) {
Log.e("---","111")
}
Log.e("---","222")
}
結果を出力します。
com.example.mytest2 E 222
com.example.mytest2 E 111
印刷結果 222 は 111 の前にあり、タスクが実際に投稿されたことを示しています。
コルーチンスコープの作成方法
コルーチンを開始するには 2 つの方法があることはわかっています。実際、一種の runBlocking もありますが、この種類の開始コルーチンは現在のスレッドをブロックします。コルーチンが終了する場合にのみ、コルーチンを開始したスレッドが終了します。メモリ リークが発生するため、一般的なプロジェクトでは使用されません。
打ち上げ()
非同期()
これらは CoroutineScope の拡張関数に属しているため、それらを呼び出すには CoroutineScope オブジェクトが必要です。
方法 1:
GlobalScope はコルーチン スコープのシングルトンであるため、直接使用できます。
@DelicateCoroutinesApi
public object GlobalScope : CoroutineScope {
/**
* Returns [EmptyCoroutineContext].
*/
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
GlobalScope.launch() { }
方法 2:
class MainActivity : AppCompatActivity() {
lateinit var coroutine:CoroutineScope
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
coroutine= CoroutineScope(Dispatchers.IO) //创建一个协程作用域
coroutine.launch {
Log.e("---","开始了协程")
}
}
override fun onDestroy() {
super.onDestroy()
coroutine.cancel() //协程取消
}
}
しかし、ここで問題が発生し、多くのコルーチン スコープを開く必要がある場合、多くの cancel を呼び出す必要がありますか?
次のように:
package com.example.mytest2
import android.annotation.SuppressLint
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.Settings.Global
import android.util.Log
import android.view.View
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.math.log
import kotlin.system.measureTimeMillis
import kotlin.time.measureTimedValue
class MainActivity : AppCompatActivity() {
lateinit var coroutine:CoroutineScope
lateinit var coroutine1:CoroutineScope
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
coroutine= CoroutineScope(Dispatchers.IO)
coroutine.launch {
Log.e("---","开始了协程")
}
coroutine1= CoroutineScope(Dispatchers.IO)
coroutine1.launch {
Log.e("---","开始了协程")
}
}
override fun onDestroy() {
super.onDestroy()
coroutine.cancel()
coroutine1.cancel()
}
}
最適化: ジョブがコルーチンを管理することはわかっていますが、CoroutineScope はどのようにコルーチンを管理するのでしょうか?
public fun CoroutineScope.cancel(cause: cancelException? = null) { val job = coroutineContext[Job] ?: error("ジョブがないためスコープをキャンセルできません: $this") job.cancel(cause) }
彼が実際にジョブのキャンセルを呼び出したことが判明したため、ジョブを作成し、ジョブ オブジェクトで複数のコルーチンのキャンセルを管理させました。
class MainActivity : AppCompatActivity() {
val job=Job()
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
CoroutineScope(Dispatchers.IO+job).launch {
Log.e("---","开始了协程")
}
CoroutineScope(Dispatchers.IO+job).launch {
Log.e("---","开始了协程")
}
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
}
上記に注意してください
CoroutineScope(Dispatchers.IO+job) はメソッドであり、オブジェクトは作成されませんが、メソッドはオブジェクトを返します。
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
ContextScope(if (context[Job] != null) context else context + Job())
方法 3
CoroutineScopeインターフェイスを実装します。CoroutineScopeインターフェイスには実装されていないプロパティがあるため、継承者はこのプロパティを初期化する必要があります。デリゲート クラスを使用して実装できます。
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
实现CoroutineScope 的接口,协程的上下文让MainScope()方法创建的类实现实现
class MainActivity : AppCompatActivity() ,CoroutineScope by MainScope() {
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
launch { //这个协程是运行在主线程的,因为mainScope创建的协程上下文中的线程是主线程
}
}
override fun onDestroy() {
super.onDestroy()
cancel()
}
}
MainScope() メソッドを見てみましょう。
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
注意这个方法创建的协程上下文的指定线程是主线程,所以说如果子协程不指定线程的话,子协程也是在主线程中运行
方法 4:
コルーチンを自分で管理すると面倒ですし、キャンセルを忘れてメモリリークを起こしやすいため、Android ではいくつかのライブラリが用意されています。
//第四种: android提供的和lifecycle相关的库
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
lifecycleScope.launch { } 用在activity和fragment中
viewModelScope.launch{} 用在viewModel中
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
公式サイトアドレス
コルーチンの原理の分析
コルーチンのノンブロッキング原理は、実際にはコールバック + ステート マシン メカニズムを使用して実装されます。
参考記事:
Kotlin 構文の高度化 - コルーチン (2) 関数一時停止の原理_あの頃街の上を飛んでのブログ - CSDN ブログ
サスペンド:一時停止するという意味
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_coroutine)
val launch = GlobalScope.launch(Dispatchers.Main) { //实质上相当于往主线程中post了一个新任务,这个任务就是{}闭包的内容
println("thread name= ${Thread.currentThread().name}")
println("======12")
delay(500)
println("======23")
}
println("--end--")
}
起動関数を見てみましょう。
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
}
このコードを分析します。
class CoroutineActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_coroutine)
运行到挂起函数的时候,相当于线程执行到这里切断了,一部分去执行挂起函数,一部分去按顺序去执行
val launch = GlobalScope.launch(Dispatchers.Main) { //挂起函数
println("thread name= ${Thread.currentThread().name}")
println("======12")
delay(500)
println("======23")
}
println("--end--") //接下来的内容
}
}
stop キーワードは単なるプロンプトであり、一時停止の役割を果たすことはできないことに注意してください。一時停止メソッドには、withcontent()、launch などが含まれます。