إدارة العمل

بعد تحديد Worker وWorkRequest، تتمثّل الخطوة الأخيرة في وضع عملك في قائمة الانتظار. أبسط طريقة لإضافة عمل إلى قائمة الانتظار هي استدعاء طريقة enqueue() في WorkManager، مع تمرير WorkRequest الذي تريد تنفيذه.

Kotlin

val myWork: WorkRequest = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork)

Java

WorkRequest myWork = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork);

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

العمل الفريد

العمل الفريد هو مفهوم قوي يضمن عدم توفّر سوى نسخة واحدة من العمل بالاسم نفسه في كل مرة. على عكس المعرّفات، تكون الأسماء الفريدة قابلة للقراءة من قِبل المستخدم ويحدّدها المطوّر بدلاً من أن يتم إنشاؤها تلقائيًا بواسطة WorkManager. على عكس العلامات، لا ترتبط الأسماء الفريدة إلا بنسخة واحدة من العمل.

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

تقبل كلتا الطريقتين 3 وسيطات:

  • uniqueWorkName: String يُستخدَم لتحديد طلب العمل بشكل فريد.
  • existingWorkPolicy: هي enum تحدّد لـ WorkManager الإجراء الذي يجب اتّخاذه إذا كانت هناك سلسلة غير مكتملة من المهام تحمل هذا الاسم الفريد. لمزيد من المعلومات، يُرجى الاطّلاع على سياسة حلّ التعارض.
  • استبدِل work بـ WorkRequest لتحديد موعد.

باستخدام العمل الفريد، يمكننا حلّ مشكلة الجدولة المكرّرة التي أشرنا إليها سابقًا.

Kotlin

val sendLogsWorkRequest =
       PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS)
           .setConstraints(Constraints.Builder()
               .setRequiresCharging(true)
               .build()
            )
           .build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
           "sendLogs",
           ExistingPeriodicWorkPolicy.KEEP,
           sendLogsWorkRequest
)

Java

PeriodicWorkRequest sendLogsWorkRequest = new
      PeriodicWorkRequest.Builder(SendLogsWorker.class, 24, TimeUnit.HOURS)
              .setConstraints(new Constraints.Builder()
              .setRequiresCharging(true)
          .build()
      )
     .build();
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
     "sendLogs",
     ExistingPeriodicWorkPolicy.KEEP,
     sendLogsWorkRequest);

الآن، إذا تم تنفيذ الرمز البرمجي بينما كانت مهمة sendLogs في قائمة الانتظار، سيتم الاحتفاظ بالمهمة الحالية ولن تتم إضافة مهمة جديدة.

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

سياسة حلّ النزاعات

عند جدولة عمل فريد، عليك إخبار WorkManager بالإجراء الذي يجب اتخاذه عند حدوث تعارض. يمكنك إجراء ذلك من خلال تمرير تعداد عند وضع العمل في قائمة الانتظار.

بالنسبة إلى العمل لمرة واحدة، عليك تقديم ExistingWorkPolicy، الذي يتيح 4 خيارات للتعامل مع التعارض.

  • REPLACE يجب أن تتوافق المهام الحالية مع المهام الجديدة. يؤدي هذا الخيار إلى إلغاء العمل الحالي.
  • KEEP العمل الحالي وتجاهل العمل الجديد
  • APPEND العمل الجديد في نهاية العمل الحالي. ستؤدي هذه السياسة إلى ربط عملك الجديد بالعمل الحالي، وسيتم تنفيذه بعد انتهاء العمل الحالي.

يصبح العمل الحالي شرطًا أساسيًا للعمل الجديد. إذا كان العمل الحالي CANCELLED أو FAILED، سيكون العمل الجديد أيضًا CANCELLED أو FAILED. إذا كنت تريد تشغيل العمل الجديد بغض النظر عن حالة العمل الحالي، استخدِم APPEND_OR_REPLACE بدلاً من ذلك.

  • تعمل الدالة APPEND_OR_REPLACE بطريقة مشابهة للدالة APPEND، إلا أنّها لا تعتمد على حالة العمل المسبق. إذا كان العمل الحالي CANCELLED أو FAILED، سيستمر تشغيل العمل الجديد.

بالنسبة إلى العمل الدوري، عليك تقديم ExistingPeriodicWorkPolicy، الذي يتيح خيارَين، REPLACE وKEEP. تعمل هذه الخيارات بالطريقة نفسها التي تعمل بها الخيارات المشابهة في ExistingWorkPolicy.

ملاحظة عملك

في أي وقت بعد إضافة العمل إلى قائمة الانتظار، يمكنك التحقّق من حالته من خلال طلب البحث من WorkManager باستخدام name أو id أو tag مرتبط به.

Kotlin

// by id
workManager.getWorkInfoById(syncWorker.id) // ListenableFuture<WorkInfo>

// by name
workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture<List<WorkInfo>>

// by tag
workManager.getWorkInfosByTag("syncTag") // ListenableFuture<List<WorkInfo>>

Java

// by id
workManager.getWorkInfoById(syncWorker.id); // ListenableFuture<WorkInfo>

// by name
workManager.getWorkInfosForUniqueWork("sync"); // ListenableFuture<List<WorkInfo>>

// by tag
workManager.getWorkInfosByTag("syncTag"); // ListenableFuture<List<WorkInfo>>

يعرض طلب البحث ListenableFuture لكائن WorkInfo، والذي يتضمّن id للعمل، وعلاماته، وState الحالي، وأي مجموعة بيانات ناتجة باستخدام Result.success(outputData).

تتيح لك صيغتا LiveData وFlow لكل طريقة مراقبة التغييرات التي تطرأ على WorkInfo من خلال تسجيل أداة معالجة. على سبيل المثال، إذا أردت عرض رسالة للمستخدم عند انتهاء بعض العمليات بنجاح، يمكنك إعدادها على النحو التالي:

Kotlin

workManager.getWorkInfoByIdFlow(syncWorker.id)
          .collect{ workInfo ->
              if(workInfo?.state == WorkInfo.State.SUCCEEDED) {
                  Snackbar.make(requireView(),
                      R.string.work_completed, Snackbar.LENGTH_SHORT)
                      .show()
              }
          }

Java

workManager.getWorkInfoByIdLiveData(syncWorker.id)
        .observe(getViewLifecycleOwner(), workInfo -> {
    if (workInfo.getState() != null &&
            workInfo.getState() == WorkInfo.State.SUCCEEDED) {
        Snackbar.make(requireView(),
                    R.string.work_completed, Snackbar.LENGTH_SHORT)
                .show();
   }
});

طلبات البحث المعقّدة المتعلقة بالعمل

يتيح الإصدار 2.4.0 من WorkManager والإصدارات الأحدث إجراء طلبات بحث معقّدة عن المهام التي تمت إضافتها إلى قائمة الانتظار باستخدام عناصر WorkQuery. تتيح WorkQuery البحث عن العمل من خلال مجموعة من علاماته وحالته واسم العمل الفريد.

يوضّح المثال التالي كيف يمكنك العثور على جميع المهام التي تحمل العلامة "syncTag"، والتي تكون في الحالة FAILED أو CANCELLED، والتي تحمل اسمًا فريدًا إما "preProcess" أو "sync".

Kotlin

val workQuery = WorkQuery.Builder
       .fromTags(listOf("syncTag"))
       .addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
       .addUniqueWorkNames(listOf("preProcess", "sync")
    )
   .build()

val workInfos: ListenableFuture<List<WorkInfo>> = workManager.getWorkInfos(workQuery)

Java

WorkQuery workQuery = WorkQuery.Builder
       .fromTags(Arrays.asList("syncTag"))
       .addStates(Arrays.asList(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
       .addUniqueWorkNames(Arrays.asList("preProcess", "sync")
     )
    .build();

ListenableFuture<List<WorkInfo>> workInfos = workManager.getWorkInfos(workQuery);

يتم ربط كل مكوّن (علامة أو حالة أو اسم) في WorkQuery ببقية المكوّنات باستخدام AND. يتم OR كل قيمة في أحد المكوّنات، على سبيل المثال: (name1 OR name2 OR ...) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...).

تعمل WorkQuery أيضًا مع ما يعادل LiveData، وهو getWorkInfosLiveData()، وما يعادل Flow، وهو getWorkInfosFlow().

إلغاء العمل وإيقافه

إذا لم تعُد بحاجة إلى تنفيذ العمل الذي سبق أن تمت إضافته إلى قائمة الانتظار، يمكنك طلب إلغائه. يمكن إلغاء العمل من خلال name أو id أو tag المرتبط به.

Kotlin

// by id
workManager.cancelWorkById(syncWorker.id)

// by name
workManager.cancelUniqueWork("sync")

// by tag
workManager.cancelAllWorkByTag("syncTag")

Java

// by id
workManager.cancelWorkById(syncWorker.id);

// by name
workManager.cancelUniqueWork("sync");

// by tag
workManager.cancelAllWorkByTag("syncTag");

في الخلفية، يتحقّق WorkManager من State للعمل. إذا كان العمل منتهيًا، لن يحدث أي شيء. بخلاف ذلك، سيتم تغيير حالة العمل إلى CANCELLED ولن يتم تشغيل العمل في المستقبل. أي مهام WorkRequest تعتمد على هذا العمل سيتم CANCELLED أيضًا.

يتلقّى حساب العمل RUNNING مكالمة إلى ListenableWorker.onStopped(). يمكنك إلغاء هذه الطريقة للتعامل مع أي عملية تنظيف محتملة. يمكنك الاطّلاع على إيقاف عامل قيد التشغيل لمزيد من المعلومات.

إيقاف عامل قيد التشغيل

هناك بعض الأسباب المختلفة التي قد تؤدي إلى إيقاف Worker قيد التشغيل بواسطة WorkManager:

  • طلبت إلغاءه بشكل صريح (من خلال الاتصال بالرقم WorkManager.cancelWorkById(UUID) مثلاً).
  • في حالة العمل الفريد، عليك إضافة WorkRequest جديد بشكل صريح إلى قائمة الانتظار باستخدام ExistingWorkPolicy بقيمة REPLACE. يتم على الفور اعتبار الاشتراك القديم WorkRequest مُلغى.
  • لم يعُد عملك يستوفي القيود.
  • أصدر النظام تعليمات إلى تطبيقك لإيقاف عملك لسبب ما. يمكن أن يحدث ذلك إذا تجاوزت الموعد النهائي للتنفيذ البالغ 10 دقائق. سيتم إعادة محاولة تنفيذ المهمة في وقت لاحق.

في ظلّ هذه الشروط، يتم إيقاف العامل.

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

onStopped() callback

يستدعي WorkManager ListenableWorker.onStopped() فور إيقاف Worker. يمكنك تجاهل هذه الطريقة لإغلاق أي موارد قد تحتفظ بها.

السمة isStopped()

يمكنك استدعاء الطريقة ListenableWorker.isStopped() للتحقّق مما إذا كان العامل قد تم إيقافه. إذا كنت تنفّذ عمليات طويلة الأمد أو متكرّرة في Worker، عليك التحقّق من هذه السمة بشكل متكرّر واستخدامها كإشارة لإيقاف العمل في أقرب وقت ممكن.

ملاحظة: تتجاهل WorkManager قيمة Result التي تم ضبطها بواسطة Worker تلقّى الإشارة onStop، لأنّ Worker يُعدّ متوقفًا.

مراقبة حالة سبب الإيقاف

لتحديد سبب توقّف Worker، يمكنك تسجيل سبب التوقّف من خلال استدعاء WorkInfo.getStopReason():

Kotlin

workManager.getWorkInfoByIdFlow(syncWorker.id)
  .collect { workInfo ->
      if (workInfo != null) {
        val stopReason = workInfo.stopReason
        logStopReason(syncWorker.id, stopReason)
      }
  }

Java

  workManager.getWorkInfoByIdLiveData(syncWorker.id)
    .observe(getViewLifecycleOwner(), workInfo -> {
        if (workInfo != null) {
          int stopReason = workInfo.getStopReason();
          logStopReason(syncWorker.id, workInfo.getStopReason());
        }
  });