تجربة مع Coroutines و Retrofit2

ما هذا


من لم يقرأ الوثائق بعد - أنصحك بشدة أن تتعرف على نفسك.


ما يكتب 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"
....
}
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 و Main و Default

IO - للعمليات البسيطة باستخدام API ، والعمليات مع قاعدة البيانات ، والأفضليات المشتركة ، إلخ.
الرئيسية - موضوع واجهة المستخدم ، من حيث يمكننا الوصول إلى العرض
افتراضي - للعمليات الثقيلة مع تحميل وحدة المعالجة المركزية عالية
(المزيد في هذا المقال )


كتلة - لامدا نريد تنفيذها


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


ومع ذلك ، إذا تم تنفيذ 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()
}
view raw Example.kt hosted with ❤ by GitHub

وبالتالي ، تمكنا من إيقاف تنفيذ التعليمات البرمجية الخاصة بنا في البث ، على سبيل المثال ، عند تدوير الشاشة.


جعل 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()
}
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

وصف نموذج البريد المرتجع:


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

مستودعنا قاعدة:


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

تنفيذ المستودع


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
}
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

استنتاج


اعتدت coroutines في همز ورمز تحولت حقا أنظف وأكثر قابلية للقراءة.


UPD:
التعديل التحديثي وصف التعامل مع انظر التعليق

Source: https://habr.com/ru/post/ar445242/


All Articles