أندرويد Kotlin Coroutine أفضل الممارسات

وهو مجموعة حافظت باستمرار من أفضل الممارسات لاستخدام Kotlin Coroutines على الروبوت. يرجى التعليق أدناه إذا كان لديك أي اقتراحات بشأن أي شيء يجب إضافته.

  1. التعامل مع دورات حياة أندرويد

بطريقة مماثلة تستخدمها CompositeDisposables مع RxJava ، يجب إلغاء Kotlin Coroutines في الوقت المناسب مع إدراك أندرويد Livecycles مع الأنشطة والشظايا.

أ) استخدام Android Viewmodels

هذه هي أسهل طريقة لإعداد coroutes بحيث يتم إيقافها في الوقت المناسب ، ولكنها تعمل فقط داخل Android ViewModel الذي يحتوي على وظيفة onCleared التي يمكن إلغاء وظائف coroutine بشكل موثوق من:

private val viewModelJob = Job ()
val uiScope = CoroutineScope (Dispatchers.Main + viewModelJob)
تجاوز المتعة onCleared () {
 super.onCleared ()
 uiScope.coroutineContext.cancelChildren ()
}

ملاحظة: اعتبارا من ViewModels 2.1.0-alpha01، لم يعد هناك حاجة إليها. لم تعد مضطرًا إلى جعل نموذج العرض الخاص بك يقوم بتنفيذ CoroutineScope أو onCleared أو إضافة وظيفة. مجرد استخدام "viewModelscope.launch {}". لاحظ أن 2.x تعني أن تطبيقك يجب أن يكون على AndroidX لأنني لست متأكدًا من أنهم يخططون لإعادة نقل هذا الإصدار إلى الإصدار 1.x من ViewModels.

ب) استخدام مراقبي دورة الحياة

تنشئ هذه التقنية الأخرى نطاقًا تعلقه على نشاط أو جزء (أو أي شيء آخر ينفذ دورة حياة Android):

/ **
 * سياق Coroutine الذي يتم إلغاؤه تلقائيًا عند إتلاف واجهة المستخدم
 * /
class UiLifecycleScope: CoroutineScope، LifecycleObserver {

    أواخر القطاع الخاص: الوظيفة
    تجاوز val coroutineContext: CoroutineContext
        الحصول على () = وظيفة + المرسلين

    OnLifecycleEvent (Lifecycle.Event.ON_START)
    متعة onCreate () {
        الوظيفة = الوظيفة ()
    }

    OnLifecycleEvent (Lifecycle.Event.ON_PAUSE)
    متعة تدمير () = job.cancel ()
}
... داخل دعم ليب النشاط أو جزء
خاصة uiScope = UiLifecycleScope ()
تجاوز متعة OnCreate الخاص (savedInstanceState: حزمة) {
  super.onCreate (savedInstanceState)
  lifecycle.addObserver (uiScope)
}

ج) GlobalScope

إذا كنت تستخدم GlobalScope ، فهو نطاق يدوم مدى عمر التطبيق. يمكنك استخدام هذا لإجراء التزامن في الخلفية ، وتحديثات إعادة الشراء ، وما إلى ذلك (غير مرتبطة بدورة حياة النشاط).

د) الخدمات

يمكن للخدمات إلغاء وظائفهم في onDestroy:

private val serviceJob = Job ()
خاص فال serviceScope = CoroutineScope (Dispatchers.Main + serviceJob)
تجاوز المتعة onCleared () {
 super.onCleared ()
 serviceJob.cancel ()
}

2. التعامل مع الاستثناءات

أ) في المتزامن مقابل إطلاق مقابل runBlocking

من المهم ملاحظة أن الاستثناءات في كتلة التشغيل {} ستعطل التطبيق دون معالج استثناء. قم دائمًا بإعداد معالج استثناء افتراضي لتمريره كمعلمة لبدء التشغيل.

واستثناء داخل كتلة runBlocking {} تحطم التطبيق إلا إذا قمت بإضافة الصيد المحاولة. إضافة دائما حاول / catch إذا كنت تستخدم runBlocking. من الناحية المثالية، فقط استخدام runBlocking للاختبارات وحدة.

لن يتم نشر أو تشغيل استثناء تم طرحه داخل كتلة غير متزامنة حتى يتم انتظار الكتلة لأنها بالفعل Java Deferred أسفلها. يجب استدعاء الدالة / الأسلوب الاستثناءات.

ب) اصطياد استثناءات

إذا كنت تستخدم المزامنة لتشغيل تعليمات برمجية قد تؤدي إلى استثناءات ، فيجب عليك لف الشفرة في coroutineScope لالتقاط الاستثناءات بشكل صحيح (بفضل LouisC على سبيل المثال):

محاولة {
    coroutineScope {
        val mayFailAsync1 = async {
            mayFail1 ()
        }
        val mayFailAsync2 = async {
            mayFail2 ()
        }
        useResult (mayFailAsync1.await ()، mayFailAsync2.await ())
    }
} catch (e: IOException) {
    // امسك هذا
    رمي MyIoException ("خطأ في القيام IO" ، ه)
} catch (e: AnotherException) {
    / / التعامل مع هذا أيضا
    رمي MyOtherException ("خطأ في القيام بشيء ما" ، هـ)
}

عندما تلتقط الاستثناء ، قم بلفه في استثناء آخر (على غرار ما تفعله من أجل RxJava) بحيث تحصل على خط stacktrace في التعليمات البرمجية الخاصة بك بدلاً من رؤية stacktrace مع رمز coroutine فقط.

ج) تسجيل استثناءات

إذا كنت تستخدم GlobalScope.launch أو فاعل، يمر دائما في معالج الاستثناء الذي يمكن تسجيل استثناءات. مثلا

فال errorHandler = CoroutineExceptionHandler {_، استثناء ->
  // log إلى Crashlytics ، logcat ، إلخ.
}
فال العمل = GlobalScope.launch (errorHandler) {
...
}

دائمًا تقريبًا ، يجب عليك هيكلة النطاقات على Android ويجب استخدام معالج:

فال errorHandler = CoroutineExceptionHandler {_، استثناء ->
  // log إلى Crashlytics ، logcat ، إلخ .؛ يمكن أن يكون حقن التبعية
}
val supervisor = SupervisorJob () // ألغي مع دورة حياة النشاط
مع (CoroutineScope (coroutineContext + supervisor)) {
  فال شيء = الإطلاق (errorHandler) {
    ...
  }
}

وإذا كنت تستخدم المزامنة وتنتظر ، فعليك دائمًا بالالتفاف على try / catch كما هو موضح أعلاه ، ولكن سجل حسب الحاجة.

د) النظر في النتيجة / خطأ الطبقة المختومة

ضع في اعتبارك استخدام فئة مختومة بنتيجة يمكن أن تحتوي على خطأ بدلاً من طرح الاستثناءات:

الطبقة المختومة النتيجة  {
  نجاح فئة البيانات (بيانات val: T): النتيجة ()
  خطأ فئة البيانات (خطأ val: E): النتيجة ()
}

ه) اسم السياق Coroutine

عند إعلان لامدا غير متزامن ، يمكنك أيضًا تسميته على هذا النحو:

async (CoroutineName ("MyCoroutine")) {}

إذا كنت تنشئ سلسلة رسائل خاصة بك لتعمل بها ، يمكنك أيضًا تسميتها عند إنشاء برنامج تنفيذ سلسلة الرسائل هذا:

newSingleThreadContext ( "MyCoroutineThread")

3. برك التنفيذ وحجم التجمع الافتراضي

Coroutines تعاونية بالفعل تعدد المهام (بمساعدة مترجم) على حجم تجمع مؤشر ترابط محدود. هذا يعني أنك إذا قمت بحجب شيء في coroutine الخاص بك (على سبيل المثال ، استخدام واجهة برمجة تطبيقات للحظر) ، فستربط مؤشر الترابط بالكامل حتى تتم عملية الحظر. لن يتم تعليق coroutine أيضًا إلا إذا كنت تحقق عائدًا أو تأخيرًا ، لذا إذا كان لديك حلقة معالجة طويلة ، فتأكد من التحقق مما إذا تم إلغاء coroutine (اتصل بـ "confirmActive ()" على النطاق) حتى تتمكن من تحرير الخيط؛ هذا مشابه لكيفية عمل RxJava.

يحتوي corotines Kotlin على عدد قليل من أجهزة الإرسال المدمجة (أي ما يعادل أجهزة الجدولة في RxJava). المرسل الرئيسي (إذا لم تحدد أي شيء لتشغيله) هو واجهة المستخدم ؛ يجب عليك فقط تغيير عناصر واجهة المستخدم في هذا السياق. هناك أيضًا Dispatchers. غير محدد يمكنه القفز بين مؤشرات ترابط UI وخلفية الخلفية بحيث لا يكون في سلسلة رسائل واحدة. هذا عموما لا ينبغي أن تستخدم إلا في اختبارات وحدة. هناك Dispatchers.IO لمعالجة IO (مكالمات الشبكة التي تتوقف كثيرًا). أخيرًا ، هناك Dispatchers.Default وهو تجمع مؤشرات ترابط الخلفية الرئيسي ولكن هذا يقتصر على عدد وحدات المعالجة المركزية (CPU).

في الممارسة العملية ، يجب عليك استخدام واجهة للمرسلين الشائعة التي يتم تمريرها عبر مُنشئ الفصل الدراسي الخاص بك بحيث يمكنك مبادلة الواجهات المختلفة للاختبار. على سبيل المثال:

واجهة CoroutineDispatchers {
  فال UI: مرسل
  فال IO: المرسل
  فال حساب: مرسل
  متعة newThread (اسم فال: سلسلة): مرسل
}

4. تجنب تلف البيانات

ليس لديك وظائف تعليق تعديل البيانات خارج الوظيفة. على سبيل المثال ، يمكن أن يكون لهذا تعديل غير مقصود للبيانات في حالة تشغيل الطريقتين من مؤشرات ترابط مختلفة:

قائمة val = mutableListOf (1 ، 2)
تعليق المرح updateList1 () {
  قائمة [0] = قائمة [0] + 1
}
تعليق التحديث المرح List2 () {
  list.clear ()
}

يمكنك تجنب هذا النوع من المشكلات عن طريق:
- بعد أن تعيد coroutines جسمًا ثابتًا بدلاً من الوصول إلى أحد الأشياء وتغييره
- قم بتشغيل جميع هذه coroutines في سياق مترابط واحد تم إنشاؤه عبر: newSingleThreadContext ("contextname")

5. جعل Proguard سعيد

يجب إضافة هذه القواعد لبنيات إصدار تطبيقك:

-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
-keepclassmembernames class kotlinx. ** {volatile ؛ }

6. التفاعل مع جافا

إذا كنت تعمل على تطبيق قديم ، فلا شك أن لديك جزءًا كبيرًا من كود جافا. يمكنك استدعاء coroutines من Java عن طريق إرجاع CompleteableFuture (تأكد من تضمين قطعة أثرية kotlinx-coroutines-jdk8):

doSomethingAsync (): CompleteableFuture > =
   GlobalScope.future {doSomething ()}

7. التحديثية لا تحتاج withContext

إذا كنت تستخدم مهايئ Retrofit coroutines ، فستحصل على مؤجل يستخدم اتصال مزامنة okhttp أسفل الغطاء. لذلك لن تحتاج إلى إضافة withContext (Dispatchers.IO) كما يجب أن تفعل مع RxJava للتأكد من أن الشفرة تعمل على سلسلة IO ؛ إذا لم تستخدم محول Corrofines Retrofit وقمت بالاتصال بـ Retrofit Call مباشرة ، فأنت تحتاج withContext.

يعمل أيضًا نظام Android Arch Components Room DB تلقائيًا في سياق غير واجهة المستخدم ، لذلك لا تحتاج إلى استخدام Context.

المراجع:

  • https://medium.com/capital-one-tech/kotlin-coroutines-on-android-things-i-wish-i-knew-at-the-beginning-c2f0b1f16cff
  • https://speakerdeck.com/elizarov/fresh-async-with-kotlin
  • https://medium.com/@michaelbukachi/coroutines-and-idling-resources-c1866bfa5b5d
  • https://blog.kotlin-academy.com/kotlin-coroutines-cheat-sheet-8cf1e284dc35
  • https://medium.com/androiddevelopers/room-coroutines-422b786dc4c5؟linkId=63267803
  • https://proandroiddev.com/managing-exceptions-in-nested-coroutine-scopes-9f23fd85e61