جلب البيانات في الخلفية وإضافة Workmanager
جلب البيانات في الخلفية وإضافة Workmanager
تفرض أنظمة تشغيل الهواتف المحمولة قيوداً صارمة على التنفيذ في الخلفية للحفاظ على البطارية والموارد. ومع ذلك، تحتاج كثير من التطبيقات الواقعية—قارئات الأخبار، وتطبيقات المراسلة، وتتبع اللياقة البدنية—إلى جلب البيانات ومزامنة الحالة أو تشغيل إشعارات محلية حتى عندما لا يستخدم المستخدم التطبيق بنشاط. تسد إضافة workmanager هذه الفجوة من خلال توفير واجهة برمجة Dart موحدة تتعامل مع WorkManager على Android وBGTaskScheduler على iOS، مما يتيح لك جدولة كود Dart للتشغيل في الخلفية بموثوقية عالية.
لماذا يصعب التنفيذ في الخلفية
على Android، يمكن إنهاء العمليات غير الموجودة في المقدمة في أي وقت. على iOS، يُخصص وقت محدود للغاية للتنفيذ في الخلفية. تحل إضافة workmanager هذه المشكلة بتسجيل المهام مع المجدول الأصلي للمنصة، الذي ينفذ المهمة بعد تعليق التطبيق أو حتى بعد إعادة تشغيل الجهاز (على Android فقط). لا يُضمن تنفيذ المهام في وقت محدد، لكنها مضمونة التنفيذ عند توفر الشروط المناسبة.
setState() مباشرة من رد نداء الخلفية. استخدم الإشعارات المحلية أو المخزن المشترك (مثل shared_preferences) لإعادة نتائج المهام إلى واجهة المستخدم.إضافة الاعتمادية
أضف workmanager إلى pubspec.yaml:
pubspec.yaml
dependencies:
workmanager: ^0.5.2
flutter_local_notifications: ^17.0.0 # اختياري، للإشعارات عند اكتمال المهمة
على Android، لا تتطلب الإضافة أي إعداد إضافي سوى الاعتمادية—التهيئة التلقائية للإضافة تتولى تهيئة WorkManager. أما على iOS، فيجب إضافة إمكانية جلب الخلفية في Xcode (Signing & Capabilities → Background Modes → Background fetch) وتسجيل معرف المهمة في Info.plist.
قيد الدالة العامة (Top-Level Callback)
أهم قاعدة معمارية في workmanager هي أن دالة رد النداء التي تنفذ منطق الخلفية يجب أن تكون دالة Dart عامة على مستوى الملف (أو طريقة ثابتة). لا يمكن أن تكون إغلاقاً (closure) أو دالة مجهولة أو طريقة نسخة (instance method)، لأن مسار الخلفية المعزول يُنشأ من الصفر ولا يمكنه الوصول إلى أي حالة في الذاكرة من الجلسة السابقة للتطبيق.
التصريح الصحيح لدالة رد النداء
// دالة عامة — ليست داخل صف، وليست إغلاقاً
@pragma('vm:entry-point')
void callbackDispatcher() {
Workmanager().executeTask((taskName, inputData) async {
switch (taskName) {
case 'fetchLatestPosts':
await _syncPosts(inputData);
break;
case 'cleanupCache':
await _purgeOldCache();
break;
}
return Future.value(true); // true = نجاح، false = فشل مع إعادة المحاولة
});
}
Future<void> _syncPosts(Map<String, dynamic>? input) async {
final userId = input?['userId'] as String? ?? 'anonymous';
print('مزامنة المنشورات للمستخدم: $userId');
// ... منطق الشبكة والتخزين
}
Future<void> _purgeOldCache() async {
print('حذف إدخالات ذاكرة التخزين المؤقت القديمة...');
// ... تنظيف نظام الملفات أو قاعدة البيانات
}
@pragma('vm:entry-point') ضروري في بنيات الإصدار (عند تفعيل tree shaking) لمنع مترجم Dart من حذف الدالة العامة. حذفه سيتسبب في فشل صامت في بنيات الإنتاج.تهيئة Workmanager
استدعِ Workmanager().initialize() مرة واحدة مبكراً في main()، قبل runApp(). مرر دالة رد النداء العامة كأول وسيط. الخيار الاختياري isInDebugMode يُفعّل إشعار نظام على Android في كل مرة تعمل فيها مهمة خلفية، وهو مفيد للغاية أثناء التطوير.
main.dart — التهيئة
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Workmanager().initialize(
callbackDispatcher, // الدالة العامة المُعرَّفة أعلاه
isInDebugMode: kDebugMode,
);
runApp(const MyApp());
}
// جدولة المهام بعد التهيئة — مثلاً بعد تسجيل الدخول:
Future<void> scheduleBackgroundWork() async {
// مهمة أحادية: تعمل مرة واحدة حين تتوفر الشروط
await Workmanager().registerOneOffTask(
'oneoff-sync-001', // اسم المهمة الفريد
'fetchLatestPosts', // اسم المهمة المُمرَّر إلى callbackDispatcher
inputData: {'userId': 'user_42'},
constraints: Constraints(
networkType: NetworkType.connected,
),
);
// مهمة دورية: تتكرر بحد أدنى 15 دقيقة
await Workmanager().registerPeriodicTask(
'periodic-cache-cleanup',
'cleanupCache',
frequency: const Duration(hours: 1),
constraints: Constraints(
networkType: NetworkType.not_required,
requiresBatteryNotLow: true,
),
);
}
قيم إرجاع المهمة ومنطق إعادة المحاولة
يجب أن يُرجع رد النداء قيمة من نوع Future<bool>:
true— نجحت المهمة؛ تُعلّمها workmanager مكتملة.false— فشلت المهمة؛ قد تُعيد workmanager المحاولة وفق سياسة التراجع.- رمي استثناء غير معالج يُعامَل كفشل.
يمكنك تخصيص سياسة التراجع عند تسجيل المهمة باستخدام المعامِلَين backoffPolicy وbackoffPolicyDelay للاختيار بين BackoffPolicy.linear وBackoffPolicy.exponential.
قيود المنصة
يُعبِّر صف Constraints عن الشروط المسبقة التي يجب أن يُحققها المجدول قبل إطلاق المهمة:
networkType:NetworkType.connectedأوmeteredأوunmeteredأوnot_requiredrequiresBatteryNotLow: تأجيل المهمة عندما تكون البطارية دون العتبةrequiresCharging: التشغيل فقط عند الشحنrequiresDeviceIdle: Android فقط — وضع Doze (API 23+)requiresStorageNotLow: تخطي المهمة عند انخفاض التخزين الداخلي بشكل حرج
إلغاء المهام وإدارتها
استخدم واجهة الإلغاء لإيقاف العمل المجدول حين لم يعد ضرورياً (مثلاً عند تسجيل الخروج):
إلغاء المهام
// إلغاء مهمة محددة باسمها الفريد
await Workmanager().cancelByUniqueName('periodic-cache-cleanup');
// إلغاء جميع المهام المسجلة من قِبَل هذا التطبيق
await Workmanager().cancelAll();
قيود iOS
على iOS، التنفيذ في الخلفية أكثر تقييداً بكثير مقارنة بـ Android:
- تُعيَّن المهام الدورية إلى
BGAppRefreshTaskبحد أدنى 15 دقيقة، لكن التردد الفعلي تحدده إجراءات iOS بناءً على أنماط استخدام المستخدم. - تُعيَّن المهام الأحادية إلى
BGProcessingTask، وتعمل عادةً فقط عند الشحن وعلى شبكة Wi-Fi. - يجب الإعلان عن كل معرف مهمة في
Info.plistضمنBGTaskSchedulerPermittedIdentifiers.
UNNotificationServiceExtension، بدلاً من الاعتماد فقط على الجلب الدوري لـ workmanager.ملخص
تتيح إضافة workmanager جدولة مهام Dart الخلفية الأحادية والدورية معاً من خلال واجهة برمجية متعددة المنصات أنيقة. القيود الجوهرية التي يجب استحضارها: يجب أن يكون رد النداء دالة عامة على مستوى الملف مُزيَّنة بـ @pragma('vm:entry-point')، تعمل المهام في مسار Dart معزول خاص بها، أعِد true للنجاح وfalse لإعادة المحاولة، وتفرض iOS سياسات جدولة أكثر صرامة من Android. اجمع workmanager مع الإشعارات المحلية أو التخزين المشترك لإيصال نتائج المهام الخلفية إلى مستخدميك.