ما هذا
من لم يقرأ الوثائق بعد - أنصحك بشدة أن تتعرف على نفسك.
ما يكتب 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:
التعديل التحديثي وصف التعامل مع انظر التعليق