これは何ですか
ドキュメントをまだ読んでいない方-よく理解しておくことを強くお勧めします。
Jetbrainの記述:
コルーチンは非同期プログラミングを簡素化し、すべての複雑さをライブラリ内に残します。 プログラムロジックはコルーチンで順番に表現でき、ベースライブラリはそれを非同期に実装します。 ライブラリは、対応するイベントにサブスクライブするコールバックでユーザーコードの対応する部分をラップし、さまざまなスレッド(または異なるマシンにさえ)に実行をディスパッチできます。 コードは、厳密にシーケンシャルに実行される場合と同じくらい簡単です。
簡単に言えば、これは同期/非同期コード実行のためのライブラリです。
なんで?
RxJavaはもはや流行ではないからです(冗談です)。
第一に、私は何か新しいことを試してみたかったです。第二に、コルチンと他の方法の速度の比較という記事に出会いました。
例
たとえば、バックグラウンドで操作を実行する必要があります。
はじめに-コルチンへのbuild.gradleの依存関係を追加します。
|
dependencies { |
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1" |
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1" |
|
.... |
|
} |
コードでメソッドを使用します。
|
suspend fun <T> withContext( |
|
context: CoroutineContext, |
|
block: suspend CoroutineScope.() -> T |
|
) |
コンテキスト内-必要なスレッドプールを示します-単純な場合、これらはIO、メイン、デフォルトです
IO-APIを使用した簡単な操作、データベースを使用した操作、優先設定の共有など。
メイン-ビューにアクセスできるUIスレッド
デフォルト-CPU負荷が高い重い操作用
(この記事の詳細)
ブロック-実行したいラムダ
|
var result = 1.0 |
|
withContext(IO) { |
|
for (i in 1..1000) { |
|
result += i * i |
|
} |
|
} |
|
Log.d("coroutines example", "result = $result") |
原則として、それだけです。1〜1000の平方和の結果を取得し、同時にメインスレッドをブロックしません。つまり、ANRはありません。
ただし、コルーチンが20秒間実行され、この間にデバイスを2回転させた場合、同時に実行されるブロックは3つになります。 おっと
そして、ブロックへのアクティビティへのリンクを渡した場合-リークと古いブロック内のビューで操作を実行する機能の欠如。 おっと。
それではどうしますか?
より良くする
|
private var viewModelJob = Job() |
|
private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob) |
|
|
|
fun doWork() { |
|
var result = 1.0 |
|
viewModelScope.launch { |
|
withContext(IO) { |
|
for (i in 1..1000) { |
|
result += i * i |
|
} |
|
} |
|
} |
|
Log.d("coroutines example", " result = $result") |
|
} |
|
|
|
fun cancelJob() { |
|
viewModelJob.cancel() |
|
} |
したがって、たとえば画面が回転したときなど、ストリーム内のコードの実行を停止することができました。
CoroutineScopeにより、ネストされたすべてのコルーチンのスコープを結合し、job.cancel()を呼び出すと実行が停止されました。
実行を停止した後にスコープを再利用する予定がある場合は、job.cancel()の代わりにjob.cancelChildren()を使用する必要があります。
同時に、フローを制御する機会がまだあります。
|
fun doWork() { |
|
var result = 1.0 |
|
var result2 = 1.0 |
|
viewModelScope.launch { |
|
withContext(IO) { |
|
for (i in 1..1000) { |
|
result += i * i |
|
} |
|
} |
|
withContext(Default) { |
|
for (i in 1..1000) { |
|
result2 += i * i |
|
} |
|
} |
|
} |
|
Log.d("coroutines example", "running result = $result, result 2 = $result2") |
|
} |
|
|
|
fun cancelJob() { |
|
viewModelJob.cancel() |
|
} |
retrofit2を接続します
あられに依存関係を追加します。
|
|
|
dependencies { |
|
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" |
|
implementation "com.squareup.retrofit2:converter-moshi:$converterMoshiVersion" |
|
implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$retrofitCoroutinesVersion" |
|
... |
|
} |
ペンhttps://my-json-server.typicode.com/typicode/demo/postsを例として使用します
レトロフィットインターフェイスについて説明します。
|
interface RetrofitPosts { |
|
|
|
@GET("posts") |
|
fun getPosts(): Deferred<Response<List<Post>>> |
|
|
|
} |
返されたPostモデルを説明します。
|
data class Post(val id: Int, val title: String) |
BaseRepository:
|
abstract class BaseRepository<Params, Result> { |
|
|
|
abstract suspend fun doWork(params: Params): Result |
|
|
|
} |
PostsRepositoryの実装:
|
class PostsRepository : |
|
BaseRepository<PostsRepository.Params, PostsRepository.Result>() { |
|
|
|
override suspend fun doWork(params: Params): Result { |
|
val retrofitPosts = Retrofit |
|
.Builder() |
|
.baseUrl("https://jsonplaceholder.typicode.com") |
|
.addConverterFactory(MoshiConverterFactory.create()) |
|
.addCallAdapterFactory(CoroutineCallAdapterFactory()) |
|
.build() |
|
.create(RetrofitPosts::class.java) |
|
val result = retrofitPosts |
|
.getPosts() |
|
.await() |
|
|
|
return Result(result.body()) |
|
} |
|
|
|
class Params |
|
data class Result(val posts: List<Post>?) |
|
} |
BaseUseCase:
|
abstract class BaseUseCase<Params, Result> { |
|
|
|
abstract suspend fun doWork(params: Params): Result |
|
|
|
} |
GetPostsListUseCaseの実装:
|
class GetListOfPostsUseCase |
|
: BaseUseCase<GetListOfPostsUseCase.Params, GetListOfPostsUseCase.Result>() { |
|
|
|
override suspend fun doWork(params: Params): Result { |
|
return Result( |
|
PostsRepository() |
|
.doWork(PostsRepository.Params()) |
|
.response |
|
.posts |
|
) |
|
} |
|
|
|
class Params |
|
class Result(val posts: List<Post>?) |
|
} |
結果は次のとおりです。
|
fun doWork() { |
|
val useCase = GetListOfPostsUseCase() |
|
viewModelScope.launch { |
|
withContext(Dispatchers.IO) { |
|
|
|
val result = useCase.doWork( |
|
GetListOfPostsUseCase.Params() |
|
) |
|
Log.d("coroutines example", "get list of posts = ${result.posts}") |
|
} |
|
} |
|
|
|
} |
|
|
|
fun cancelJob() { |
|
viewModelJob.cancel() |
|
} |
より良くする
私は怠け者で、コードシート全体をドラッグしたくないたびに、BaseViewModelで必要なメソッドを作成しました。
|
abstract class BaseViewModel : ViewModel() { |
|
|
|
private var viewModelJob = Job() |
|
private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob) |
|
private var isActive = true |
|
|
|
// Do work in IO |
|
fun <P> doWork(doOnAsyncBlock: suspend CoroutineScope.() -> P) { |
|
doCoroutineWork(doOnAsyncBlock, viewModelScope, IO) |
|
} |
|
|
|
// Do work in Main |
|
// doWorkInMainThread {...} |
|
fun <P> doWorkInMainThread(doOnAsyncBlock: suspend CoroutineScope.() -> P) { |
|
doCoroutineWork(doOnAsyncBlock, viewModelScope, Main) |
|
} |
|
|
|
// Do work in IO repeately |
|
// doRepeatWork(1000) {...} |
|
// then we need to stop it calling stopRepeatWork() |
|
fun <P> doRepeatWork(delay: Long, doOnAsyncBlock: suspend CoroutineScope.() -> P) { |
|
isActive = true |
|
viewModelScope.launch { |
|
while (this@BaseViewModel.isActive) { |
|
withContext(IO) { |
|
doOnAsyncBlock.invoke(this) |
|
} |
|
if (this@BaseViewModel.isActive) { |
|
delay(delay) |
|
} |
|
} |
|
} |
|
} |
|
|
|
fun stopRepeatWork() { |
|
isActive = false |
|
} |
|
|
|
override fun onCleared() { |
|
super.onCleared() |
|
isActive = false |
|
viewModelJob.cancel() |
|
} |
|
|
|
private inline fun <P> doCoroutineWork( |
|
crossinline doOnAsyncBlock: suspend CoroutineScope.() -> P, |
|
coroutineScope: CoroutineScope, |
|
context: CoroutineContext |
|
) { |
|
coroutineScope.launch { |
|
withContext(context) { |
|
doOnAsyncBlock.invoke(this) |
|
} |
|
} |
|
} |
|
} |
投稿リストの取得は次のようになります。
|
class PostViewModel : BaseViewModel() { |
|
|
|
val lengthOfPostsList = MutableLiveData<String>() |
|
|
|
fun getListOfPosts() { |
|
doWork { |
|
val result = GetListOfPostsUseCase() |
|
.doWork(GetListOfPostsUseCase.Params()) |
|
Log.d("coroutines example", "get list of posts = ${result.posts}") |
|
lengthOfPostsList.postValue(result.posts?.size.toString()) |
|
} |
|
} |
おわりに
私はprodでコルーチンを使用しましたが、コードは実際にクリーンで読みやすいことが判明しました。
UPD:
レトロフィット例外処理の説明コメントを参照