Qu'est ce que c'est
Qui n'a pas encore lu la documentation - je vous recommande fortement de vous familiariser.
Ce que Jetbrain écrit:
Les coroutines simplifient la programmation asynchrone, laissant toutes les complications Ă l'intĂ©rieur des bibliothĂšques. La logique du programme peut ĂȘtre exprimĂ©e sĂ©quentiellement en coroutines, et la bibliothĂšque de base l'implĂ©mentera de maniĂšre asynchrone pour nous. La bibliothĂšque peut encapsuler les parties correspondantes du code utilisateur dans des rappels souscrivant aux Ă©vĂ©nements correspondants et envoyer l'exĂ©cution Ă diffĂ©rents threads (ou mĂȘme Ă diffĂ©rentes machines!). Le code restera aussi simple que s'il Ă©tait exĂ©cutĂ© strictement sĂ©quentiellement.
En termes simples, il s'agit d'une bibliothÚque pour l'exécution de code synchrone / asynchrone.
Pourquoi?
Parce que RxJava n'est plus Ă la mode (je plaisante).
PremiÚrement, je voulais essayer quelque chose de nouveau, et deuxiÚmement, je suis tombé sur un article - une comparaison de la vitesse de la corutine et d'autres méthodes.
Exemple
Par exemple, vous devez effectuer une opération en arriÚre-plan.
Pour commencer - ajoutez à notre dépendance build.gradle à la corutine:
| dependencies { |
| implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1" |
| implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1" |
| .... |
| } |
Nous utilisons la méthode dans notre code:
| suspend fun <T> withContext( |
| context: CoroutineContext, |
| block: suspend CoroutineScope.() -> T |
| ) |
OĂč dans le contexte - nous spĂ©cifions le pool de threads dont nous avons besoin - dans les cas simples, ce sont IO, Main et Default
IO - pour les opérations simples avec l'API, les opérations avec la base de données, les préférences partagées, etc.
Principal - Fil d'interface utilisateur, d'oĂč nous pouvons accĂ©der Ă la vue
Par défaut - pour les opérations lourdes avec une charge CPU élevée
(Plus dans cet article )
Block - le lambda que nous voulons exécuter
| var result = 1.0 |
| withContext(IO) { |
| for (i in 1..1000) { |
| result += i * i |
| } |
| } |
| Log.d("coroutines example", "result = $result") |
En principe, c'est tout, nous obtenons le rĂ©sultat de la somme des carrĂ©s de 1 Ă 1000 et en mĂȘme temps nous ne bloquons pas le thread principal, ce qui signifie pas d'ANR
Cependant, si notre coroutine est exécutée pendant 20 secondes et pendant ce temps, nous avons effectué 2 tours de l'appareil, nous aurons alors 3 blocs en cours d'exécution. Oups
Et si nous passions un lien vers l'activité vers le bloc - une fuite et le manque de capacité à effectuer des opérations avec vue dans les anciens blocs. Oups deux fois.
Alors que faire?
Faire mieux
| 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() |
| } |
Ainsi, nous avons pu arrĂȘter l'exĂ©cution de notre code dans le flux, par exemple, lorsque l'Ă©cran est tournĂ©.
CoroutineScope a permis de combiner la portĂ©e de toutes les coroutines imbriquĂ©es et, lors de l'appel de job.cancel (), a arrĂȘtĂ© leur exĂ©cution
Si vous prĂ©voyez de rĂ©utiliser la portĂ©e aprĂšs l'arrĂȘt de l'exĂ©cution, vous devez utiliser job.cancelChildren () au lieu de job.cancel () merci Neikist pour le commentaire
Dans le mĂȘme temps, nous avons encore la possibilitĂ© de contrĂŽler les flux:
| 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() |
| } |
Nous connectons retrofit2
Ajoutez des dĂ©pendances Ă la grĂȘle:
| |
| dependencies { |
| implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" |
| implementation "com.squareup.retrofit2:converter-moshi:$converterMoshiVersion" |
| implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$retrofitCoroutinesVersion" |
| ... |
| } |
Nous utilisons le stylet https://my-json-server.typicode.com/typicode/demo/posts comme exemple
Nous décrivons l'interface de retrofit:
| interface RetrofitPosts { |
| |
| @GET("posts") |
| fun getPosts(): Deferred<Response<List<Post>>> |
| |
| } |
Décrivez le modÚle de message retourné:
| data class Post(val id: Int, val title: String) |
Notre référentiel de base:
| abstract class BaseRepository<Params, Result> { |
| |
| abstract suspend fun doWork(params: Params): Result |
| |
| } |
Implémentation de 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>?) |
| } |
Notre BaseUseCase:
| abstract class BaseUseCase<Params, Result> { |
| |
| abstract suspend fun doWork(params: Params): Result |
| |
| } |
Implémentation de 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>?) |
| } |
Voici le résultat:
| 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() |
| } |
Le rendre meilleur
Je suis une créature paresseuse et chaque fois que je ne veux pas faire glisser toute la feuille de code, j'ai donc fait les méthodes nécessaires dans 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) |
| } |
| } |
| } |
| } |
Maintenant, la liste des messages ressemble Ă ceci:
| 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()) |
| } |
| } |
Conclusion
J'ai utilisé des coroutines dans la prod et le code s'est avéré vraiment plus propre et plus lisible.
UPD:
Retrofit exception handling description see comment