ما هذا
من لم يقرأ الوثائق بعد - أنصحك بشدة أن تتعرف على نفسك.
ما يكتب Jetbrain:
يبسط Coroutines البرمجة غير المتزامنة ، تاركًا كل التعقيدات داخل المكتبات. يمكن التعبير عن منطق البرنامج بالتتابع في coroutines ، وستنفذه المكتبة الأساسية بشكل غير متزامن بالنسبة لنا. يمكن للمكتبة التفاف الأجزاء المقابلة من رمز المستخدم في عمليات الاسترجاعات للاشتراك في الأحداث ذات الصلة ، وإرسال التنفيذ إلى مؤشرات ترابط مختلفة (أو حتى إلى أجهزة مختلفة!). سيبقى الرمز بسيطًا كما لو أنه تم تنفيذه بشكل متسلسل.
بعبارات بسيطة ، هذه مكتبة لتنفيذ التعليمات البرمجية المتزامنة / غير المتزامنة.
لماذا؟
لأن RxJava لم يعد في الموضة (مجرد مزاح).
أولاً ، أردت أن أجرب شيئًا جديدًا ، وثانيًا ، صادفت مقالًا - مقارنة بين سرعة corutin والأساليب الأخرى.
مثال
على سبيل المثال ، تحتاج إلى إجراء عملية في الخلفية.
للبدء - أضف إلى اعتماد build.gradle الخاص بنا على corutin:
|  | 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 و Main و Default
IO - للعمليات البسيطة باستخدام API ، والعمليات مع قاعدة البيانات ، والأفضليات المشتركة ، إلخ.
الرئيسية - موضوع واجهة المستخدم ، من حيث يمكننا الوصول إلى العرض
افتراضي - للعمليات الثقيلة مع تحميل وحدة المعالجة المركزية عالية
(المزيد في هذا المقال )
كتلة - لامدا نريد تنفيذها
|  | var result = 1.0 | 
|  | withContext(IO) { | 
|  | for (i in 1..1000) { | 
|  | result += i * i | 
|  | } | 
|  | } | 
|  | Log.d("coroutines example", "result = $result") | 
من حيث المبدأ ، هذا كل شيء ، نحصل على نتيجة مجموع المربعات من 1 إلى 1000 ، وفي الوقت نفسه لا نحظر الخيط الرئيسي ، مما يعني عدم وجود ANR
ومع ذلك ، إذا تم تنفيذ coroutine لدينا لمدة 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 من الممكن الجمع بين نطاق جميع coroutines المتداخلة ، وعند استدعاء job.cancel () ، توقف تنفيذها
إذا كنت تخطط لإعادة استخدام النطاق بعد إيقاف التنفيذ ، فأنت بحاجة إلى استخدام job.cancelChildren () بدلاً من job.cancel () شكرًا لـ Neikist على التعليق
في الوقت نفسه ، لا تزال لدينا الفرصة للتحكم في التدفقات:
|  | 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>>> | 
|  |  | 
|  | } | 
وصف نموذج البريد المرتجع:
|  | data class Post(val id: Int, val title: String) | 
مستودعنا قاعدة:
|  | abstract class BaseRepository<Params, Result> { | 
|  |  | 
|  | abstract suspend fun doWork(params: Params): Result | 
|  |  | 
|  | } | 
تنفيذ المستودع
|  | 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()) | 
|  | } | 
|  | } | 
استنتاج
اعتدت coroutines في همز ورمز تحولت حقا أنظف وأكثر قابلية للقراءة.
UPD:
التعديل التحديثي وصف التعامل مع انظر التعليق