‫Android Kotlin Fundamentals 09.2: WorkManager

هذا الدرس التطبيقي حول الترميز هو جزء من دورة "أساسيات Android Kotlin". يمكنك تحقيق أقصى استفادة من هذه الدورة التدريبية إذا اتبعت ترتيب الخطوات في دروس البرمجة. يتم إدراج جميع الدروس التطبيقية حول الترميز الخاصة بالدورة التدريبية في الصفحة المقصودة للدروس التطبيقية حول الترميز في دورة Android Kotlin Fundamentals.

مقدمة

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

في هذا الدرس التطبيقي حول الترميز، ستتعرّف على كيفية استخدام WorkManager لجدولة مهمة تعمل في الخلفية بطريقة محسّنة وفعّالة. لمزيد من المعلومات حول الحلول الأخرى المتاحة للمعالجة في الخلفية على Android، يُرجى الاطّلاع على دليل المعالجة في الخلفية.

ما يجب معرفته

  • كيفية استخدام ViewModel وLiveData وRoom من "مكوّنات بنية Android"
  • كيفية إجراء عمليات التحويل على فئة LiveData
  • كيفية إنشاء روتين مشترك وتشغيله
  • كيفية استخدام برامج ربط البيانات في ربط البيانات
  • كيفية تحميل البيانات المخزّنة مؤقتًا باستخدام نمط مستودع

أهداف الدورة التعليمية

  • كيفية إنشاء Worker، وهو يمثّل وحدة عمل
  • كيفية إنشاء WorkRequest لطلب تنفيذ عمل
  • كيفية إضافة قيود إلى WorkRequest لتحديد كيفية ووقت تشغيل العامل
  • كيفية استخدام WorkManager لجدولة المهام التي يتم تنفيذها في الخلفية

الإجراءات التي ستنفذّها

  • أنشئ عاملاً لتنفيذ مهمة في الخلفية من أجل جلب قائمة تشغيل فيديوهات DevBytes مسبقًا من الشبكة.
  • جدولة العامل ليتم تشغيله بشكل دوري
  • أضِف قيودًا إلى WorkRequest.
  • جدولة WorkRequest دوري يتم تنفيذه مرة واحدة في اليوم

في هذا الدرس العملي، ستعمل على تطبيق DevBytes الذي طوّرته في درس عملي سابق. (إذا لم يكن لديك هذا التطبيق، يمكنك تنزيل رمز البداية لهذا الدرس).

يعرض تطبيق DevBytes قائمة بفيديوهات DevByte، وهي عبارة عن برامج تعليمية قصيرة من إعداد فريق علاقات المطوّرين في Google Android. تقدّم الفيديوهات ميزات المطوّرين وأفضل الممارسات لتطوير تطبيقات Android.

يمكنك تحسين تجربة المستخدم في التطبيق من خلال جلب الفيديوهات مسبقًا مرة واحدة في اليوم. يضمن ذلك حصول المستخدم على محتوى جديد فور فتح التطبيق.

في هذه المهمة، عليك تنزيل رمز البداية وفحصه.

الخطوة 1: تنزيل تطبيق البداية وتشغيله

يمكنك مواصلة العمل من خلال تطبيق DevBytes الذي أنشأته في الدرس العملي السابق (إذا كان لديك). يمكنك بدلاً من ذلك تنزيل تطبيق البداية.

في هذه المهمة، ستنزّل تطبيقًا أوليًا وتشغّله وتفحص الرمز الأولي.

  1. إذا لم يكن لديك تطبيق DevBytes، نزِّل رمز بدء DevBytes الخاص بهذا الدرس التطبيقي من مشروع DevBytesRepository على GitHub.
  2. فك ضغط الرمز البرمجي وافتح المشروع في "استوديو Android".
  3. وصِّل جهاز الاختبار أو المحاكي بالإنترنت إذا لم يسبق لك ذلك. أنشئ التطبيق وشغِّله. يجلب التطبيق قائمة فيديوهات DevByte من الشبكة ويعرضها.
  4. في التطبيق، انقر على أي فيديو لفتحه في تطبيق YouTube.

الخطوة 2: استكشاف الرمز

يأتي تطبيق البداية مزوّدًا بالكثير من الرموز البرمجية التي تم تقديمها في الدرس العملي السابق. يحتوي الرمز الأولي لهذا الدرس التطبيقي حول الترميز على وحدات الشبكات وواجهة المستخدم وذاكرة التخزين المؤقت بلا إنترنت والمستودع. يمكنك التركيز على جدولة المهمة التي تعمل في الخلفية باستخدام WorkManager.

  1. في "استوديو Android"، وسِّع جميع الحِزم.
  2. استكشِف حزمة database. تحتوي الحزمة على عناصر قاعدة البيانات وقاعدة البيانات المحلية التي يتم تنفيذها باستخدام Room.
  3. استكشِف حزمة repository. تحتوي الحزمة على الفئة VideosRepository التي تجرّد طبقة البيانات من بقية التطبيق.
  4. استكشِف بقية الرمز الأولي بنفسك وبمساعدة الدرس العملي السابق.

WorkManager هي إحدى مكوّنات بنية Android وجزء من Android Jetpack. يُستخدَم WorkManager لتنفيذ المهام التي يمكن تأجيلها في الخلفية والتي تتطلّب تنفيذًا مضمونًا:

  • يشير قابلة للتأجيل إلى أنّ العمل ليس مطلوبًا تنفيذه على الفور. على سبيل المثال، يمكن تأجيل إرسال البيانات التحليلية إلى الخادم أو مزامنة قاعدة البيانات في الخلفية.
  • التنفيذ المضمون يعني أنّ المهمة سيتم تنفيذها حتى إذا تم إغلاق التطبيق أو إعادة تشغيل الجهاز.

أثناء تنفيذ WorkManager لمهام في الخلفية، يراعي التطبيق مشاكل التوافق وأفضل الممارسات للحفاظ على حالة البطارية والنظام. توفّر WorkManager توافقًا مع الإصدارات القديمة حتى مستوى واجهة برمجة التطبيقات 14. تختار WorkManager طريقة مناسبة لجدولة مهمة تعمل في الخلفية، وذلك حسب مستوى واجهة برمجة التطبيقات للجهاز. قد يستخدم JobScheduler (في المستوى 23 من واجهة برمجة التطبيقات والإصدارات الأحدث) أو مزيجًا من AlarmManager وBroadcastReceiver.

تتيح لك WorkManager أيضًا ضبط معايير تحديد وقت تنفيذ المهمة في الخلفية. على سبيل المثال، قد تريد أن يتم تنفيذ المهمة فقط عندما تستوفي حالة البطارية أو حالة الشبكة أو حالة الشحن معايير معيّنة. ستتعرّف على كيفية ضبط القيود لاحقًا في هذا الدرس البرمجي.

في هذا الدرس التطبيقي حول الترميز، ستجدول مهمة لجلب قائمة تشغيل فيديوهات DevBytes مسبقًا من الشبكة مرة واحدة في اليوم. لتحديد موعد لهذه المهمة، يمكنك استخدام مكتبة WorkManager.

  1. افتح ملف build.gradle (Module:app) وأضِف تبعية WorkManager إلى المشروع.

    إذا كنت تستخدم أحدث إصدار من المكتبة، من المفترض أن يتم تجميع تطبيق الحلّ على النحو المتوقّع. إذا لم ينجح ذلك، جرِّب حلّ المشكلة أو العودة إلى إصدار المكتبة الموضّح أدناه.
// WorkManager dependency
def work_version = "1.0.1"
implementation "android.arch.work:work-runtime-ktx:$work_version"
  1. زامِن مشروعك وتأكَّد من عدم حدوث أخطاء في التجميع.

قبل إضافة رمز إلى المشروع، تعرَّف على الفئات التالية في مكتبة WorkManager:

  • Worker
    هذا الصف هو المكان الذي تحدّد فيه العمل الفعلي (المهمة) الذي سيتم تنفيذه في الخلفية. يمكنك توسيع هذه الفئة وتجاوز طريقة doWork(). الطريقة doWork() هي المكان الذي تضع فيه الرمز المراد تنفيذه في الخلفية، مثل مزامنة البيانات مع الخادم أو معالجة الصور. عليك تنفيذ Worker في هذه المهمة.
  • WorkRequest
    يمثّل هذا الصف طلبًا لتشغيل العامل في الخلفية. استخدِم WorkRequest لضبط طريقة ووقت تنفيذ مهمة العامل، وذلك بمساعدة Constraints مثل توصيل الجهاز بمصدر طاقة أو الاتصال بشبكة Wi-Fi. يمكنك تنفيذ WorkRequest في مهمة لاحقة.
  • WorkManager
    يجدول هذا الصف WorkRequest ويشغّله. يجدول WorkManager طلبات العمل بطريقة توزع الحمل على موارد النظام، مع الالتزام بالقيود التي تحددها. يمكنك تنفيذ WorkManager في مهمة لاحقة.

الخطوة 1: إنشاء عامل

في هذه المهمة، ستضيف Worker لجلب قائمة تشغيل فيديوهات DevBytes مسبقًا في الخلفية.

  1. داخل حزمة devbyteviewer، أنشئ حزمة جديدة باسم work.
  2. داخل حزمة work، أنشئ فئة Kotlin جديدة باسم RefreshDataWorker.
  3. تمديد الصف RefreshDataWorker من الصف CoroutineWorker مرِّر context وWorkerParameters كمعلَمات للدالة الإنشائية.
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
       CoroutineWorker(appContext, params) {
}
  1. لحلّ خطأ الفئة المجردة، عليك إلغاء طريقة doWork() داخل الفئة RefreshDataWorker.
override suspend fun doWork(): Result {
  return Result.success()
}

الدالة المعلقة هي دالة يمكن إيقافها مؤقتًا واستئنافها لاحقًا. يمكن للدالة المعلّقة تنفيذ عملية تستغرق وقتًا طويلاً والانتظار إلى أن تكتمل بدون حظر سلسلة التعليمات الرئيسية.

الخطوة 2: تنفيذ doWork()

يتم استدعاء الطريقة doWork() داخل الفئة Worker في سلسلة محادثات في الخلفية. تنفّذ الطريقة العمل بشكل متزامن، ويجب أن تعرض عنصر ListenableWorker.Result. يمنح نظام Android Worker مدة أقصاها 10 دقائق لإنهاء تنفيذه وعرض عنصر ListenableWorker.Result. بعد انتهاء هذه المدة، يوقف النظام Worker بالقوة.

لإنشاء عنصر ListenableWorker.Result، استدعِ إحدى الطرق الثابتة التالية للإشارة إلى حالة اكتمال العمل في الخلفية:

في هذه المهمة، ستنفّذ طريقة doWork() لجلب قائمة تشغيل فيديوهات DevBytes من الشبكة. يمكنك إعادة استخدام الطرق الحالية في فئة VideosRepository لاسترداد البيانات من الشبكة.

  1. في الفئة RefreshDataWorker، داخل doWork()، أنشئ كائن VideosDatabase وكائن VideosRepository وقم بإنشائهما.
override suspend fun doWork(): Result {
   val database = getDatabase(applicationContext)
   val repository = VideosRepository(database)

   return Result.success()
}
  1. في الفئة RefreshDataWorker، داخل doWork()، فوق العبارة return، استدعِ الدالة refreshVideos() داخل الكتلة try. أضِف سجلاً لتتبُّع وقت تنفيذ العامل.
try {
   repository.refreshVideos( )
   Timber.d("Work request for sync is run")
   } catch (e: HttpException) {
   return Result.retry()
}

لحلّ الخطأ "مرجع لم يتم حله"، استورِد retrofit2.HttpException.

  1. في ما يلي RefreshDataWorker الكاملة للرجوع إليها:
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
       CoroutineWorker(appContext, params) {

   override suspend fun doWork(): Result {
       val database = getDatabase(applicationContext)
       val repository = VideosRepository(database)
       try {
           repository.refreshVideos()
       } catch (e: HttpException) {
           return Result.retry()
       }
       return Result.success()
   }
}

تحدّد Worker وحدة العمل، وتحدّد WorkRequest كيفية تنفيذ العمل ومتى يجب تنفيذه. هناك طريقتان لتنفيذ الفئة WorkRequest:

  • يُستخدم الصف OneTimeWorkRequest للمهام التي يتم إجراؤها لمرة واحدة. (تحدث المهمة التي تتم لمرة واحدة مرة واحدة فقط).
  • الصف PeriodicWorkRequest مخصّص للأعمال الدورية، أي الأعمال التي تتكرّر على فترات زمنية.

يمكن أن تكون المهام لمرة واحدة أو دورية، لذا اختَر الصف وفقًا لذلك. لمزيد من المعلومات حول جدولة العمل المتكرّر، يُرجى الاطّلاع على مستندات العمل المتكرّر.

في هذه المهمة، عليك تحديد WorkRequest وجدولته لتشغيل العامل الذي أنشأته في المهمة السابقة.

الخطوة 1: إعداد العمل المتكرّر

في تطبيق Android، يمثّل الصف Application الصف الأساسي الذي يحتوي على جميع المكوّنات الأخرى، مثل الأنشطة والخدمات. عند إنشاء عملية لتطبيقك أو حزمة تطبيقك، يتم إنشاء مثيل لفئة Application (أو أي فئة فرعية من Application) قبل أي فئة أخرى.

في هذا التطبيق النموذجي، الفئة DevByteApplication هي فئة فرعية من الفئة Application. يُعدّ الصف DevByteApplication مكانًا مناسبًا لتحديد موعد نشر WorkManager.

  1. في الفئة DevByteApplication، أنشئ طريقة تُسمى setupRecurringWork() لإعداد العمل المتكرّر في الخلفية.
/**
* Setup WorkManager background job to 'fetch' new network data daily.
*/
private fun setupRecurringWork() {
}
  1. داخل الدالة setupRecurringWork()، أنشئ طلب عمل دوري وفعِّله لتنفيذه مرة واحدة في اليوم باستخدام الدالة PeriodicWorkRequestBuilder(). مرِّر في الصف RefreshDataWorker الذي أنشأته في المهمة السابقة. مرِّر فاصل تكرار بقيمة 1 مع وحدة زمنية بقيمة TimeUnit.DAYS.
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
       .build()

لحلّ الخطأ، استورِد java.util.concurrent.TimeUnit.

الخطوة 2: جدولة WorkRequest باستخدام WorkManager

بعد تحديد WorkRequest، يمكنك جدولة عملية التصدير باستخدام WorkManager، وذلك باستخدام طريقة enqueueUniquePeriodicWork(). تتيح لك هذه الطريقة إضافة PeriodicWorkRequest يحمل اسمًا فريدًا إلى قائمة الانتظار، حيث يمكن أن يكون PeriodicWorkRequest واحد فقط باسم معيّن نشطًا في الوقت نفسه.

على سبيل المثال، قد تريد أن تكون عملية مزامنة واحدة فقط نشطة. إذا كانت عملية مزامنة واحدة في انتظار المراجعة، يمكنك اختيار السماح بتنفيذها أو استبدالها بعملك الجديد باستخدام ExistingPeriodicWorkPolicy.

لمزيد من المعلومات عن طرق جدولة WorkRequest، يُرجى الاطّلاع على مستندات WorkManager.

  1. في فئة RefreshDataWorker، أضِف عنصرًا مصاحبًا في بداية الفئة. حدِّد اسم عامل لتحديد هذا العامل بشكلٍ فريد.
companion object {
   const val WORK_NAME = "com.example.android.devbyteviewer.work.RefreshDataWorker"
}
  1. في الفئة DevByteApplication، في نهاية الطريقة setupRecurringWork()، جدوِل العمل باستخدام الطريقة enqueueUniquePeriodicWork(). أدخِل القيمة في التعداد KEEP لـ ExistingPeriodicWorkPolicy. مرِّر repeatingRequest كمعلَمة PeriodicWorkRequest.
WorkManager.getInstance().enqueueUniquePeriodicWork(
       RefreshDataWorker.WORK_NAME,
       ExistingPeriodicWorkPolicy.KEEP,
       repeatingRequest)

إذا كانت هناك أعمال معلّقة (لم يتم إكمالها) تحمل الاسم نفسه، ستؤدي المَعلمة ExistingPeriodicWorkPolicy.KEEP إلى احتفاظ WorkManager بالعمل الدوري السابق وتجاهل طلب العمل الجديد.

  1. في بداية فئة DevByteApplication، أنشئ عنصر CoroutineScope. مرِّر Dispatchers.Default كمعلَمة للدالة الإنشائية.
private val applicationScope = CoroutineScope(Dispatchers.Default)
  1. في الفئة DevByteApplication، أضِف طريقة جديدة باسم delayedInit() لبدء روتين فرعي.
private fun delayedInit() {
   applicationScope.launch {
   }
}
  1. داخل طريقة delayedInit()، استدعِ setupRecurringWork().
  2. انقل عملية تهيئة Timber من الطريقة onCreate() إلى الطريقة delayedInit().
private fun delayedInit() {
   applicationScope.launch {
       Timber.plant(Timber.DebugTree())
       setupRecurringWork()
   }
}
  1. في الفئة DevByteApplication، في نهاية الطريقة onCreate()، أضِف استدعاءً للطريقة delayedInit().
override fun onCreate() {
   super.onCreate()
   delayedInit()
}
  1. افتح لوحة Logcat في أسفل نافذة Android Studio. الفلترة حسب RefreshDataWorker
  2. شغِّل التطبيق. يضع WorkManager جدولاً زمنيًا لعملك المتكرّر على الفور.

    في لوحة Logcat، لاحظ عبارات السجلّ التي توضّح أنّه تم تحديد موعد لطلب العمل، ثم تم تنفيذه بنجاح.
D/RefreshDataWorker: Work request for sync is run
I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]

يتم عرض سجلّ WM-WorkerWrapper من مكتبة WorkManager، لذا لا يمكنك تغيير رسالة السجلّ هذه.

الخطوة 3: (اختيارية) جدولة WorkRequest بفاصل زمني لا يقل عن الحد الأدنى

في هذه الخطوة، يمكنك تقليل الفاصل الزمني من يوم واحد إلى 15 دقيقة. يتم ذلك حتى تتمكّن من الاطّلاع على سجلّات طلب العمل الدوري أثناء تنفيذه.

  1. في الفئة DevByteApplication، داخل الطريقة setupRecurringWork()، علِّق على تعريف repeatingRequest الحالي. أضِف طلب عمل جديدًا بفاصل تكرار دوري يبلغ 15 دقيقة.
// val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
//        .build()
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
       .build()
  1. افتح لوحة Logcat في "استوديو Android" وفلتر حسب RefreshDataWorker. لمحو السجلّات السابقة، انقر على رمز محو logcat .
  2. شغِّل التطبيق، وسيضع WorkManager جدولاً زمنيًا لعملك المتكرّر على الفور. في لوحة Logcat، لاحظ السجلات، حيث يتم تنفيذ طلب العمل مرة واحدة كل 15 دقيقة. انتظِر 15 دقيقة للاطّلاع على مجموعة أخرى من سجلّات طلبات العمل. يمكنك ترك التطبيق قيد التشغيل أو إغلاقه، وسيظل مدير العمل يعمل.

    يُرجى العِلم أنّ الفاصل الزمني يكون أحيانًا أقل من 15 دقيقة، وأحيانًا أكثر من 15 دقيقة. (يخضع التوقيت الدقيق لتحسينات البطارية في نظام التشغيل).
12:44:40 D/RefreshDataWorker: Work request for sync is run
12:44:40 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
12:59:24 D/RefreshDataWorker: Work request for sync is run
12:59:24 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:15:03 D/RefreshDataWorker: Work request for sync is run
13:15:03 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:29:22 D/RefreshDataWorker: Work request for sync is run
13:29:22 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:44:26 D/RefreshDataWorker: Work request for sync is run
13:44:26 I/WM-WorkerWrapper: Worker result SUCCESS for Work
 

تهانينا! لقد أنشأت عاملًا وجدولت طلب العمل مع WorkManager. ولكن هناك مشكلة: لم تحدّد أي قيود. سيتم جدولة العمل مرة واحدة في اليوم، حتى إذا كانت البطارية منخفضة أو كان الجهاز في وضع السكون أو لم يكن متصلاً بشبكة.WorkManager سيؤثر ذلك في بطارية الجهاز وأدائه، وقد يؤدي إلى تقديم تجربة سيئة للمستخدم.

في مهمتك التالية، ستعالج هذه المشكلة من خلال إضافة قيود.

في المهمة السابقة، استخدمت WorkManager لتحديد موعد لطلب عمل. في هذه المهمة، ستضيف معايير لتحديد وقت تنفيذ العمل.

عند تحديد WorkRequest، يمكنك تحديد قيود على وقت تنفيذ Worker. على سبيل المثال، يمكنك تحديد أنّه يجب تنفيذ العمل فقط عندما يكون الجهاز غير نشِط، أو فقط عندما يكون الجهاز موصولاً بالكهرباء ومتصلاً بشبكة Wi-Fi. يمكنك أيضًا تحديد سياسة التراجع لإعادة محاولة تنفيذ العمل. القيود المتوافقة هي طرق الضبط في Constraints.Builder. لمزيد من المعلومات، يُرجى الاطّلاع على تحديد طلبات العمل.

الخطوة 1: إضافة عنصر Constraints وتحديد أحد القيود

في هذه الخطوة، يمكنك إنشاء عنصر Constraints وتعيين قيد واحد على العنصر، وهو قيد من نوع الشبكة. (من الأسهل ملاحظة السجلات عند توفّر قيد واحد فقط. في خطوة لاحقة، يمكنك إضافة قيود أخرى).

  1. في الفئة DevByteApplication، في بداية setupRecurringWork()، حدِّد val من النوع Constraints. استخدِم طريقة Constraints.Builder().
val constraints = Constraints.Builder()

لحلّ الخطأ، استورِد androidx.work.Constraints.

  1. استخدِم الطريقة setRequiredNetworkType() لإضافة قيد من نوع الشبكة إلى العنصر constraints. استخدِم تعداد UNMETERED لكي لا يتم تنفيذ طلب العمل إلا عندما يكون الجهاز متصلاً بشبكة غير محدودة الاستخدام.
.setRequiredNetworkType(NetworkType.UNMETERED)
  1. استخدِم طريقة build() لإنشاء القيود من أداة الإنشاء.
val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .build()

عليك الآن ضبط العنصر Constraints الذي تم إنشاؤه حديثًا على طلب العمل.

  1. في الفئة DevByteApplication، داخل الطريقة setupRecurringWork()، اضبط الكائن Constraints على طلب العمل الدوري repeatingRequest. لضبط القيود، أضِف الطريقة setConstraints() فوق استدعاء الطريقة build().
       val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
               .setConstraints(constraints)
               .build()

الخطوة 2: تشغيل التطبيق وملاحظة السجلات

في هذه الخطوة، شغِّل التطبيق ولاحظ أنّه يتم تشغيل طلب العمل المقيّد في الخلفية على فترات زمنية.

  1. ألغِ تثبيت التطبيق من الجهاز أو المحاكي لإلغاء أي مهام مجدولة سابقًا.
  2. افتح لوحة Logcat في "استوديو Android". في لوحة Logcat، امحِ السجلات السابقة بالنقر على رمز محو Logcat على يمين الصفحة. الفلترة حسب work
  3. أوقِف شبكة Wi-Fi في الجهاز أو المحاكي لكي تتمكّن من معرفة طريقة عمل القيود. يضبط الرمز الحالي قيدًا واحدًا فقط، ما يشير إلى أنّه يجب تنفيذ الطلب على شبكة غير محدودة الاستخدام فقط. بما أنّ شبكة Wi-Fi غير مفعّلة، لن يكون الجهاز متصلاً بالشبكة، سواء كانت محدودة أو غير محدودة. لذلك، لن يتم استيفاء هذا القيد.
  4. شغِّل التطبيق ولاحِظ لوحة Logcat. يجدول WorkManager المهمة التي تعمل في الخلفية على الفور. لا يتم تنفيذ المهمة لأنّ شرط الشبكة لم يتم استيفاؤه.
11:31:44 D/DevByteApplication: Periodic Work request for sync is scheduled
  1. فعِّل شبكة Wi-Fi في الجهاز أو المحاكي وراقِب لوحة Logcat. يتم الآن تنفيذ مهمة الخلفية المُجدوَلة كل 15 دقيقة تقريبًا، طالما تم استيفاء شرط الشبكة.
11:31:44 D/DevByteApplication: Periodic Work request for sync is scheduled
11:31:47 D/RefreshDataWorker: Work request for sync is run
11:31:47 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]
11:46:45 D/RefreshDataWorker: Work request for sync is run
11:46:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:03:05 D/RefreshDataWorker: Work request for sync is run
12:03:05 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:16:45 D/RefreshDataWorker: Work request for sync is run
12:16:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:31:45 D/RefreshDataWorker: Work request for sync is run
12:31:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:47:05 D/RefreshDataWorker: Work request for sync is run
12:47:05 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
13:01:45 D/RefreshDataWorker: Work request for sync is run
13:01:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]

الخطوة 3: إضافة المزيد من القيود

في هذه الخطوة، يمكنك إضافة القيود التالية إلى PeriodicWorkRequest:

  • البطارية ليست منخفضة.
  • شحن الجهاز
  • وضع "الجهاز في وضع الخمول"، ويتوفّر فقط في المستوى 23 من واجهة برمجة التطبيقات (Android M) والإصدارات الأحدث.

نفِّذ ما يلي في الفئة DevByteApplication.

  1. في الفئة DevByteApplication، داخل الدالة setupRecurringWork()، حدِّد أنّه يجب تنفيذ طلب العمل فقط إذا لم يكن مستوى البطارية منخفضًا. أضِف القيد قبل استدعاء طريقة build()، واستخدِم طريقة setRequiresBatteryNotLow().
.setRequiresBatteryNotLow(true)
  1. عدِّل طلب العمل لكي يتم تنفيذه فقط عندما يكون الجهاز قيد الشحن. أضِف القيد قبل استدعاء طريقة build()، واستخدِم طريقة setRequiresCharging().
.setRequiresCharging(true)
  1. عدِّل طلب العمل لكي يتم تنفيذه فقط عندما يكون الجهاز غير نشط. أضِف القيد قبل استدعاء طريقة build()، واستخدِم طريقة setRequiresDeviceIdle(). لا يتم تنفيذ طلب العمل إلا عندما لا يكون المستخدم يستخدم الجهاز بشكل نشط. لا تتوفّر هذه الميزة إلا في الإصدار 6.0 (Marshmallow) والإصدارات الأحدث من نظام التشغيل Android، لذا أضِف شرطًا للإصدار M والإصدارات الأحدث من حزمة تطوير البرامج (SDK).
.apply {
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
       setRequiresDeviceIdle(true)
   }
}

في ما يلي التعريف الكامل لعنصر constraints.

val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .setRequiresBatteryNotLow(true)
       .setRequiresCharging(true)
       .apply {
           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
               setRequiresDeviceIdle(true)
           }
       }
       .build()
  1. داخل طريقة setupRecurringWork()، غيِّر فاصل الطلب إلى مرة واحدة في اليوم.
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
       .setConstraints(constraints)
       .build()

في ما يلي عملية التنفيذ الكاملة للطريقة setupRecurringWork()، مع سجلّ لتتمكّن من تتبُّع وقت جدولة طلب العمل الدوري.

private fun setupRecurringWork() {

       val constraints = Constraints.Builder()
               .setRequiredNetworkType(NetworkType.UNMETERED)
               .setRequiresBatteryNotLow(true)
               .setRequiresCharging(true)
               .apply {
                   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                       setRequiresDeviceIdle(true)
                   }
               }
               .build()
       val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
               .setConstraints(constraints)
               .build()
       
       Timber.d("Periodic Work request for sync is scheduled")
       WorkManager.getInstance().enqueueUniquePeriodicWork(
               RefreshDataWorker.WORK_NAME,
               ExistingPeriodicWorkPolicy.KEEP,
               repeatingRequest)
   }
  1. لإزالة طلب العمل الذي تمّت جدولته سابقًا، عليك إلغاء تثبيت تطبيق DevBytes من جهازك أو المحاكي.
  2. شغِّل التطبيق، وسيجدول WorkManager طلب العمل على الفور. يتم تنفيذ طلب العمل مرة واحدة في اليوم، عند استيفاء جميع القيود.
  3. سيتم تنفيذ طلب العمل هذا في الخلفية طالما أنّ التطبيق مثبّت، حتى إذا لم يكن التطبيق قيد التشغيل. لهذا السبب، يجب إلغاء تثبيت التطبيق من الهاتف.

رائع! لقد نفّذت طلب عمل مناسبًا للبطارية وجدولته من أجل الجلب المسبق اليومي للفيديوهات في تطبيق DevBytes. ستعمل WorkManager على جدولة العمل وتنفيذه، ما يؤدي إلى تحسين موارد النظام. سيكون المستخدمون والبطاريات في غاية السعادة.

مشروع "استوديو Android": DevBytesWorkManager

  • تسهّل واجهة برمجة التطبيقات WorkManager جدولة المهام غير المتزامنة والقابلة للتأجيل التي يجب تنفيذها بشكل موثوق.
  • تحتاج معظم التطبيقات إلى تنفيذ مهام طويلة الأمد في الخلفية. لجدولة مهمة تعمل في الخلفية بطريقة محسّنة وفعّالة، استخدِم WorkManager.
  • الفئات الرئيسية في مكتبة WorkManager هي Worker وWorkRequest وWorkManager.
  • يمثّل الصف Worker وحدة عمل. لتنفيذ مهمة الخلفية، عليك توسيع فئة Worker وتجاوز طريقة doWork().
  • يمثّل الصف WorkRequest طلبًا لتنفيذ وحدة عمل. ‫WorkRequest هي الفئة الأساسية لتحديد مَعلمات العمل الذي تجدوله في WorkManager.
  • هناك تنفيذان ملموسان لفئة WorkRequest: OneTimeWorkRequest للمهام غير المتكررة، وPeriodicWorkRequest لطلبات العمل الدورية.
  • عند تحديد WorkRequest، يمكنك تحديد Constraints للإشارة إلى وقت تنفيذ Worker. تشمل القيود ما إذا كان الجهاز موصولاً بالكهرباء أو غير نشط أو متصلاً بشبكة Wi-Fi.
  • لإضافة قيود إلى WorkRequest، استخدِم طرق الضبط المُدرَجة في مستندات Constraints.Builder. على سبيل المثال، للإشارة إلى أنّه يجب عدم تشغيل WorkRequest إذا كانت بطارية الجهاز منخفضة، استخدِم طريقة الضبط setRequiresBatteryNotLow().
  • بعد تحديد WorkRequest، يمكنك تسليم المهمة إلى نظام Android. لإجراء ذلك، جدوِل المهمة باستخدام إحدى طرق WorkManager enqueue.
  • يعتمد الوقت الدقيق الذي يتم فيه تنفيذ Worker على القيود المستخدَمة في WorkRequest وعلى عمليات تحسين النظام. تم تصميم WorkManager لتقديم أفضل سلوك ممكن في ظل هذه القيود.

دورة Udacity التدريبية:

مستندات مطوّري تطبيقات Android:

غير ذلك:

يسرد هذا القسم مهامًا منزلية محتملة للطلاب الذين يعملون على هذا الدرس التطبيقي العملي كجزء من دورة تدريبية يقودها مدرّب. على المعلّم تنفيذ ما يلي:

  • حدِّد واجبًا منزليًا إذا لزم الأمر.
  • توضيح كيفية إرسال الواجبات المنزلية للطلاب
  • وضع درجات للواجبات المنزلية

يمكن للمدرّبين استخدام هذه الاقتراحات بالقدر الذي يريدونه، ويجب ألا يترددوا في تكليف الطلاب بأي واجبات منزلية أخرى يرونها مناسبة.

إذا كنت تعمل على هذا الدرس العملي بنفسك، يمكنك استخدام مهام الواجب المنزلي هذه لاختبار معلوماتك.

السؤال 1

ما هي عمليات التنفيذ الملموسة للفئة WorkRequest؟

OneTimeWorkPeriodicRequest

‫▢ OneTimeWorkRequest وPeriodicWorkRequest

‫▢ OneTimeWorkRequest وRecurringWorkRequest

‫▢ OneTimeOffWorkRequest وRecurringWorkRequest

السؤال 2

أي من الفئات التالية تستخدمها WorkManager لجدولة مهمة تعمل في الخلفية على الإصدار 23 من واجهة برمجة التطبيقات والإصدارات الأحدث؟

JobScheduler فقط

‫▢ BroadcastReceiver وAlarmManager

‫▢ AlarmManager وJobScheduler

‫▢ Scheduler وBroadcastReceiver

السؤال 3

ما هي واجهة برمجة التطبيقات التي تستخدمها لإضافة قيود إلى WorkRequest؟

setConstraints()

addConstraints()

setConstraint()

addConstraintsToWorkRequest()

الانتقال إلى الدرس التالي: 10.1 الأنماط والمظاهر

للحصول على روابط تؤدي إلى دروس تطبيقية أخرى في هذه الدورة التدريبية، اطّلِع على الصفحة المقصودة الخاصة بالدروس التطبيقية حول أساسيات Android Kotlin.