الإشعارات الفورية والخدمات الخلفية

القيود وإعادة المحاولة ودورة حياة المهام في Workmanager

15 دقيقة الدرس 9 من 11

القيود وإعادة المحاولة ودورة حياة المهام في Workmanager

يتيح لك Workmanager جدولة مهام الخلفية التي تعمل في ظل ظروف محددة للجهاز. من خلال إرفاق قيود بالمهمة، تضمن تنفيذها فقط عندما يستوفي الجهاز متطلباتك—مثل وجود اتصال شبكي نشط أو توصيل الجهاز بالشاحن. يغطي هذا الدرس كيفية تعريف القيود، وضبط سياسات التراجع لإعادة المحاولة التلقائية، وإلغاء المهام بالاسم أو الوسم، وتفسير قيم النتائج التي ترجعها مهمتك.

تعريف قيود التنفيذ

يُمرَّر كائن Constraints إلى registerOneOffTask أو registerPeriodicTask عبر المعامل المسمى constraints. أكثر خصائص القيود استخداماً هي:

  • networkTypeNetworkType.connected أو NetworkType.metered أو NetworkType.unmetered أو NetworkType.not_required. استخدم unmetered لعمليات الرفع/التنزيل الكبيرة حتى تعمل على Wi-Fi فقط.
  • requiresBatteryNotLowtrue يؤجل التنفيذ عندما تنخفض البطارية دون حد النظام (نحو 20%).
  • requiresChargingtrue يضمن تشغيل المهمة فقط أثناء توصيل الجهاز بالشاحن.
  • requiresDeviceIdletrue يؤخر المهمة حتى يكون الجهاز في وضع الخمول (Android فقط؛ يُتجاهل في iOS).
  • requiresStorageNotLowtrue يتخطى التنفيذ عندما تكون مساحة التخزين المتاحة منخفضة جداً.

تسجيل مهمة مع قيود

import 'package:workmanager/workmanager.dart';

Future<void> scheduleConstrainedSync() async {
  await Workmanager().registerOneOffTask(
    'syncUserData',               // اسم المهمة الفريد
    'syncUserDataTask',           // معرّف المهمة (يُمرَّر إلى callbackDispatcher)
    initialDelay: const Duration(minutes: 5),
    constraints: Constraints(
      networkType: NetworkType.unmetered,   // Wi-Fi فقط
      requiresBatteryNotLow: true,          // تخطي إذا كانت البطارية منخفضة
      requiresCharging: false,              // الشاحن غير مطلوب
    ),
  );
}
ملاحظة: في iOS، يستخدم Workmanager BGTaskScheduler تحت الغطاء. ليس لكل قيود Android مكافئات في iOS. يُعيَّن networkType وrequiresBatteryNotLow بشكل معقول، لكن requiresDeviceIdle وrequiresStorageNotLow يُتجاهلان بصمت في iOS. اختبر دائماً على كلتا المنصتين.

سياسات التراجع وسلوك إعادة المحاولة

عندما يعيد callbackDispatcher قيمة Future.value(false)، يُعلّم Workmanager المهمة كـفاشلة ويجدول إعادة محاولة وفقاً لسياسة التراجع المضبوطة. سياستان متاحتان:

  • BackoffPolicy.linear — يتزايد تأخير إعادة المحاولة بشكل خطي: initialDelay × المحاولة.
  • BackoffPolicy.exponential — يتضاعف تأخير إعادة المحاولة في كل محاولة (محدود بنحو 5 ساعات في Android). هذه هي السياسة الافتراضية ومفضلة للمهام التي تعتمد على الشبكة لتجنب مشكلة القطيع الرعدي.

اضبط تأخير التراجع الأولي مع backoffPolicyDelay واختر السياسة مع backoffPolicy:

ضبط سياسة التراجع

Future<void> scheduleWithRetry() async {
  await Workmanager().registerOneOffTask(
    'uploadReport',
    'uploadReportTask',
    constraints: Constraints(networkType: NetworkType.connected),
    backoffPolicy: BackoffPolicy.exponential,
    backoffPolicyDelay: const Duration(minutes: 2),
    // أول إعادة محاولة بعد ~2 دقيقة، ثم ~4 دقائق، ~8 دقائق، إلخ.
  );
}

// في callbackDispatcher:
// return Future.value(true);   // نجاح — تُزال المهمة من قائمة الانتظار
// return Future.value(false);  // فشل — سيعيد Workmanager المحاولة
// throw Exception('fatal');    // يُعامَل أيضاً كفشل؛ ستتم إعادة المحاولة
نصيحة: أعد دائماً Future.value(true) للمهام التي اكتملت بنجاح، حتى لو لم تُنتج البيانات أي نتيجة. إعادة false دون داعٍ تهدر البطارية بتشغيل إعادة المحاولات. أعد false فقط عندما تفشل العملية فعلاً وتستحق المحاولة لاحقاً (مثل خطأ مؤقت في الشبكة).

قيم نتائج المهمة

تتحكم قيمة الإعادة من دالة الاستدعاء العلوية في دورة حياة المهمة:

  • Future.value(true)نجاح. تُعتبر المهمة منتهية ولن تُعاد محاولتها (للمهام الفردية) أو ستُجدوَل للفترة التالية (للمهام الدورية).
  • Future.value(false)فشل. ستُعاد محاولة المهمة وفقاً لسياسة التراجع، حتى حد إعادة المحاولة المحدد من النظام.
  • استثناء غير معالج — يُعامَل بنفس طريقة false؛ ستحدث إعادة المحاولة. التفاف الكود الحرج في try/catch لتتمكن من تسجيل الأخطاء قبل إعادة false.

إلغاء المهام

يوفر Workmanager ثلاث طرق للإلغاء. استخدم الطريقة المناسبة لكيفية تسجيل المهمة في الأصل:

  • Workmanager().cancelByUniqueName('uniqueName') — يلغي مهمة فردية أو دورية محددة بسلسلة الاسم الفريد.
  • Workmanager().cancelByTag('tagName') — يلغي جميع المهام التي سُجِّلت بمعامل tag مطابق. مفيد للإلغاء الجماعي (مثلاً إلغاء جميع مهام مستخدم قام بتسجيل الخروج).
  • Workmanager().cancelAll() — يلغي كل مهام Workmanager المعلقة في التطبيق. استخدمه بحذر؛ لا يمكن التراجع عنه.

إلغاء المهام بالاسم والوسم

// تسجيل مهام بوسم مشترك
Future<void> registerUserTasks(String userId) async {
  const tag = 'user_tasks';

  await Workmanager().registerPeriodicTask(
    'sync_$userId',
    'syncTask',
    tag: tag,
    frequency: const Duration(hours: 1),
    constraints: Constraints(networkType: NetworkType.connected),
  );

  await Workmanager().registerOneOffTask(
    'avatar_$userId',
    'avatarUploadTask',
    tag: tag,
  );
}

// عند تسجيل الخروج: إلغاء كل مهمة تخص هذا المستخدم
Future<void> cancelUserTasks() async {
  await Workmanager().cancelByTag('user_tasks');
}

// أو إلغاء مهمة واحدة باسمها الفريد
Future<void> cancelSyncOnly(String userId) async {
  await Workmanager().cancelByUniqueName('sync_$userId');
}
تحذير: cancelAll() يلغي أيضاً أي مهام دورية لا تزال تحتاجها، مثل مهمة مجدولة لتنظيف ذاكرة التخزين المؤقت أو رفع السجلات. فضّل cancelByTag مع سلاسل وسم ذات معنى لتجنب إيقاف عمل الخلفية غير المرتبط عن طريق الخطأ.

ملخص دورة حياة المهمة الكاملة

فهم دورة الحياة الكاملة يمنع الأخطاء الشائعة:

  • مسجلة — تدخل المهمة قائمة انتظار عمل نظام التشغيل. قد لا تبدأ فوراً إذا كانت القيود غير مستوفاة.
  • تعمل — القيود مستوفاة؛ يُنفّذ موزّع الاستدعاء دالة مهمتك.
  • نجحت (true) — تُزال المهمة من قائمة الانتظار. تُعاد جدولة المهام الدورية للفترة التالية.
  • فشلت (false أو استثناء) — تُعاد محاولة المهمة بعد تأخير التراجع، حتى حد إعادة المحاولة.
  • مُلغاة — تُزال المهمة من قائمة الانتظار؛ لن تعمل أو تُعاد محاولتها.
النقطة الرئيسية: ادمج القيود مع سياسة تراجع مناسبة لبناء مهام خلفية قوية وصديقة للبطارية. أعد دائماً true عند النجاح وfalse عند الفشل القابل لإعادة المحاولة. ضع وسوماً لمهامك حتى تتمكن من إلغاء المجموعات ذات الصلة بشكل نظيف. فهم هذه الانتقالات في دورة الحياة سيوفر عليك ساعات من تصحيح سلوك المهام غير المتوقع في الإنتاج.