コルーチンおよびレトロフィットの経験2

これは何ですか



ドキュメントをまだ読んでいない方-よく理解しておくことを強くお勧めします。







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"
....
}
view raw build.gradle hosted with ❤ by GitHub


コードでメソッドを使用します。







suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
)
view raw gistfile1.txt hosted with ❤ by GitHub


コンテキスト内-必要なスレッドプールを示します-単純な場合、これらは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")
view raw Example.kt hosted with ❤ by GitHub


原則として、それだけです。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()
}
view raw Example.kt hosted with ❤ by GitHub


したがって、たとえば画面が回転したときなど、ストリーム内のコードの実行を停止することができました。







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()
}
view raw Example.kt hosted with ❤ by GitHub


retrofit2を接続します



あられに依存関係を追加します。







dependencies {
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-moshi:$converterMoshiVersion"
implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$retrofitCoroutinesVersion"
...
}
view raw build.gradle hosted with ❤ by GitHub


ペンhttps://my-json-server.typicode.com/typicode/demo/postsをとして使用します







レトロフィットインターフェイスについて説明します。







interface RetrofitPosts {
@GET("posts")
fun getPosts(): Deferred<Response<List<Post>>>
}
view raw RetrofitPosts.kt hosted with ❤ by GitHub


返されたPostモデルを説明します。







data class Post(val id: Int, val title: String)
view raw Post.kt hosted with ❤ by GitHub


BaseRepository:







abstract class BaseRepository<Params, Result> {
abstract suspend fun doWork(params: Params): Result
}
view raw BaseRepository.kt hosted with ❤ by GitHub


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>?)
}
view raw PostsRepository.kt hosted with ❤ by GitHub


BaseUseCase:







abstract class BaseUseCase<Params, Result> {
abstract suspend fun doWork(params: Params): Result
}
view raw BaseUseCase.kt hosted with ❤ by GitHub


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()
}
view raw Example.kt hosted with ❤ by GitHub


より良くする



私は怠け者で、コードシート全体をドラッグしたくないたびに、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)
}
}
}
}
view raw BaseViewModel.kt hosted with ❤ by GitHub


投稿リストの取得は次のようになります。







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())
}
}
view raw PostViewModel.kt hosted with ❤ by GitHub


おわりに



私はprodでコルーチンを使用しましたが、コードは実際にクリーンで読みやすいことが判明しました。







UPD:

レトロフィット例外処理の説明コメントを参照








All Articles