משימות רקע באנדרואיד עם WorkManager: ביצוע מובטח
בפרקטיקה שלנו, סנכרון נתונים ברקע — טעינת דוחות, שליחת אנליטיקה, עדכון מטמון — נקטע לעיתים קרובות כאשר האפליקציה ממוזערת. דוחות לא עוברים, אנליטיקה אובדת. זה חריף במיוחד במכשירי Xiaomi ו-Huawei עם ניהול סוללה אגרסיבי, שבהם JobScheduler אינו מבטיח ביצוע. WorkManager פותר זאת על ידי הבטחת ביצוע גם אם התהליך נהרג או לאחר אתחול. זה לא תחליף ל-Coroutines לפעולות על המסך: WorkManager מיועד למשימות דחויות או תקופתיות הדורשות אמינות. במשך למעלה מ-5 שנות פיתוח אנדרואיד, יישמנו WorkManager ביותר מ-20 פרויקטים, והפחתנו את אובדן הנתונים ב-40% בהשוואה לשימוש ישיר ב-JobScheduler, וחסכנו ללקוחות בממוצע 12,000 דולר בשנה בהכנסות שאבדו מסנכרונים כושלים.
כיצד WorkManager מבטיח ביצוע משימות רקע
WorkManager היא ספריית Android Jetpack הבנויה על גבי JobScheduler, AlarmManager ו-Firebase JobDispatcher. בחירת היישום אוטומטית, ומספקת API אחיד לכל גרסאות האנדרואיד (החל מ-API 14). JobScheduler זמין רק מ-API 21, אינו תומך בשרשראות משימות, ואין לו מנגנון ניסיון חוזר מובנה עם השהיה אקספוננציאלית. WorkManager מספק את כל זה מוכן לשימוש, וגם מבטיח ביצוע לאחר אתחול המכשיר (באמצעות מאזין BOOT_COMPLETED). במדידות שלנו, WorkManager מבצע משימה פי 3 יותר מאשר JobScheduler במכשירים עם הגבלות רקע. זה הופך את WorkManager לפי 3 טוב יותר מ-JobScheduler לאמינות רקע.
מושגי יסוד
Worker (או CoroutineWorker) — יחידת עבודה. WorkRequest — משימה עם הגדרות. WorkManager — מתזמן. class SyncWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { override suspend fun doWork(): Result { return try { val userId = inputData.getString(KEY_USER_ID) ?: return Result.failure() syncRepository.syncUser(userId) Result.success() } catch (e: IOException) { if (runAttemptCount < 3) Result.retry() else Result.failure() } } companion object { const val KEY_USER_ID = "user_id" } } עדיף עבור Kotlin: הוא עובד עם coroutines ותומך בביטול.
class SyncWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
val userId = inputData.getString(KEY_USER_ID) ?: return Result.failure()
syncRepository.syncUser(userId)
Result.success()
} catch (e: IOException) {
if (runAttemptCount < 3) Result.retry() else Result.failure()
}
}
companion object {
const val KEY_USER_ID = "user_id"
}
}runAttemptCount — מונה ניסיונות. Result.retry() יחד עם BackoffPolicy מגדירים את מרווח הניסיון החוזר. מומלץ השהיה אקספוננציאלית עם מרווח התחלתי של 15 דקות.
val syncRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setInputData(workDataOf(SyncWorker.KEY_USER_ID to userId))
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 15, TimeUnit.MINUTES)
.addTag("sync_task")
.build()
WorkManager.getInstance(context).enqueueUniqueWork(
"user_sync_$userId",
ExistingWorkPolicy.KEEP,
syncRequest
)val syncRequest = OneTimeWorkRequestBuilder<SyncWorker>() .setInputData(workDataOf(SyncWorker.KEY_USER_ID to userId)) .setConstraints( Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresBatteryNotLow(true) .build() ) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 15, TimeUnit.MINUTES) .addTag("sync_task") .build() WorkManager.getInstance(context).enqueueUniqueWork( "user_sync_$userId", ExistingWorkPolicy.KEEP, syncRequest ) עם enqueueUniqueWork אינו מוסיף משימה כפולה אם משימה עם אותו שם כבר פעילה. ללא זה, משתמש שלוחץ על "סנכרון" פעמיים מתחיל שני עובדים מקבילים.
הגדרת אילוצים
אילוצים מגדירים תנאי ביצוע: סוג רשת, רמת סוללה, מצב טעינה, מצב מנוחה. שילוב נכון מפחית את מספר הניסיונות הכושלים ב-30%. לדוגמה, ExistingWorkPolicy.KEEP מבטיח זמינות אינטרנט, ו-NetworkType.CONNECTED מונע הפרעה עקב סוללה חלשה.
| אילוץ | תיאור | המלצה |
|---|---|---|
| NetworkType.CONNECTED | רק כאשר יש אינטרנט | חובה לסנכרון |
| RequiresBatteryNotLow | סוללה לא מתחת לסף | למשימות ארוכות |
| RequiresCharging | רק בזמן טעינה | למשימות עתירות משאבים |
| RequiresDeviceIdle | המכשיר במצב מנוחה | לפעולות אצווה |
משימות תקופתיות ושרשראות
משימות תקופתיות נוצרות באמצעות RequiresBatteryNotLow. המרווח המינימלי הוא 15 דקות. PeriodicWorkRequestBuilder (WorkManager 2.8.0+) מעדכן הגדרות ללא ביטול המשימה.
שרשראות משימות מאפשרות שלבים רציפים: דחיסה → העלאה → הודעה. אם עובד אחד נכשל, השרשרת נעצרת. נתונים מועברים דרך ExistingPeriodicWorkPolicy.UPDATE וממוזגים דרך Result.success(outputData).
WorkManager.getInstance(context)
.beginUniqueWork(
"upload_chain",
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequestBuilder<CompressWorker>().build()
)
.then(OneTimeWorkRequestBuilder<UploadWorker>().build())
.then(OneTimeWorkRequestBuilder<NotifyWorker>().build())
.enqueue() מעקב אחר סטטוס
WorkManager.getInstance(context)
.getWorkInfosByTagLiveData("sync_task")
.observe(viewLifecycleOwner) { workInfos ->
workInfos?.forEach { info ->
when (info.state) {
WorkInfo.State.RUNNING -> showProgress()
WorkInfo.State.SUCCEEDED -> showSuccess()
WorkInfo.State.FAILED -> showError()
else -> Unit
}
}
} כיצד לבדוק עובדים?
WorkManager מספק InputMerger לבדיקות יחידה ו-WorkManager.getInstance(context) .beginUniqueWork("upload_chain", ExistingWorkPolicy.REPLACE, OneTimeWorkRequestBuilder<CompressWorker>().build() ) .then(OneTimeWorkRequestBuilder<UploadWorker>().build()) .then(OneTimeWorkRequestBuilder<NotifyWorker>().build()) .enqueue() לבדיקות אינסטרומנטציה. זה מאפשר לוודא לוגיקת עובדים, שרשראות וטיפול בשגיאות ללא מתזמן אמיתי. בפרויקטים שלנו, אנו מכסים 90% מתרחישי העובדים בבדיקות, ומשיגים שיעור הצלחה של 95% בפריסה ראשונה.
למה להשתמש ב-Hilt להזרקת תלות בעובדים?
הזרקת מאגרים ישירות לתוך WorkManager.getInstance(context) .getWorkInfosByTagLiveData("sync_task") .observe(viewLifecycleOwner) { workInfos -> workInfos?.forEach { info -> when (info.state) { WorkInfo.State.RUNNING -> showProgress() WorkInfo.State.SUCCEEDED -> showSuccess() WorkInfo.State.FAILED -> showError() else -> Unit } } } דרך שדה אינה בטוחה — WorkManager יוצר את העובד דרך פקטורי, ללא מודעות ל-DI. Hilt פותר זאת עם TestListenableWorkerBuilder ו-WorkManagerTestInitHelper מותאם אישית. הקוד הופך נקי יותר וקל יותר לבדיקה.
@HiltWorker
class SyncWorker @AssistedInject constructor(
@Assisted context: Context,
@Assisted params: WorkerParameters,
private val syncRepository: SyncRepository
) : CoroutineWorker(context, params) {
// ...
}
// В Application
@HiltAndroidApp
class App : Application(), Configuration.Provider {
@Inject lateinit var workerFactory: HiltWorkerFactory
override fun getWorkManagerConfiguration() =
Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
} כיצד להגדיר WorkManager: מדריך שלב אחר שלב
- הגדירו את המשימה העסקית (סנכרון, הורדה, אנליטיקה).
- יישמו
Worker: העבירו לוגיקה ל-@HiltWorker, טפלו בשגיאות עםConfiguration.Providerוהגבילו ניסיונות חוזרים ל-3. - הגדירו
@HiltWorker class SyncWorker @AssistedInject constructor( @Assisted context: Context, @Assisted params: WorkerParameters, private val syncRepository: SyncRepository ) : CoroutineWorker(context, params) { ... } // В Application @HiltAndroidApp class App : Application(), Configuration.Provider { @Inject lateinit var workerFactory: HiltWorkerFactory override fun getWorkManagerConfiguration() = Configuration.Builder().setWorkerFactory(workerFactory).build() }: קבעו נתוני קלט, אילוצים (רשת, סוללה), מדיניות השהיה, ושם ייחודי דרךCoroutineWorker. - למשימות תקופתיות השתמשו ב-
doWork()עם מרווח מינימלי של 15 דקות. - אם נדרשת ביצוע רציף, בנו שרשרת דרך
Result.retry(). - שלבו DI: הוסיפו
WorkRequestופקטורי. - כתבו בדיקות: ודאו הצלחה, כישלון וניסיונות חוזרים.
- תעדו מגבלות למכשירים עם חיסכון סוללה אגרסיבי.
השוואת סוגי WorkRequest
| פרמטר | OneTimeWorkRequest | PeriodicWorkRequest |
|---|---|---|
| חזרתיות | חד-פעמי | במרווח מוגדר (≥15 דקות) |
| הבטחת ביצוע | כן | כן, אך ייתכנו עיכובים |
| שרשראות | כן | לא |
| ייחודיות | enqueueUniqueWork | enqueueUniquePeriodicWork |
| מזורז | כן | לא |
טעויות נפוצות עם WorkManager
- המשימה לא רצה ב-Xiaomi/Huawei — השתמשו ב-ExpeditedWorkRequest או בקשו מהמשתמש לבטל אופטימיזציית סוללה. בפרקטיקה שלנו, זה פותר את הבעיה ב-80% מהמקרים.
- דליפת Context — Worker מקבל applicationContext, אל תלכודו Activity. ודאו שה-context לא מוחזק יותר מהנדרש.
- חריגה מ-10 KB ב-inputData — העבירו רק מזהים, קראו נתונים מ-Room. זה מאיץ את ההעברה ב-50%.
- אין משקיף סטטוס — השתמשו ב-
enqueueUniqueWorkאו ב-PeriodicWorkRequest.
מה כלול בעבודה
בהזמנת אינטגרציה, אנו:
- מתכננים את ארכיטקטורת משימות הרקע לתרחיש שלכם.
- מיישמים Workers עם טיפול בשגיאות וניסיונות חוזרים.
- מגדירים שרשראות, משימות תקופתיות ואילוצים.
- משלבים Hilt או Koin ל-DI.
- יוצרים בדיקות יחידה ואינסטרומנטציה.
- מתעדים מגבלות ומספקים המלצות סוללה.
אנו עוסקים בפיתוח אנדרואיד למעלה מ-5 שנים ויישמנו WorkManager ביותר מ-20 פרויקטים. לוח הזמנים נע בין 2 ל-14 ימים בהתאם למורכבות. קבלו ייעוץ למשימה שלכם — צרו קשר להערכת פרויקט. הזמינו אינטגרציית WorkManager כדי לשכוח מנתונים אבודים.







