הגדרה של בקשות עבודה

במדריך לתחילת העבודה מוסבר איך ליצור WorkRequest פשוט ולהוסיף אותו לתור.

במדריך הזה תלמדו איך להגדיר ולהתאים אישית אובייקטים מסוג WorkRequest כדי לטפל בתרחישי שימוש נפוצים, כמו:

  • תזמון משימות חד-פעמיות וחוזרות
  • להגדיר אילוצים לעבודה, כמו דרישה ל-Wi-Fi או לטעינה
  • זמן עיבוד מינימלי מובטח
  • הגדרת אסטרטגיות של ניסיונות חוזרים ונסיגה
  • העברת נתוני קלט לעבודה
  • קיבוץ משימות קשורות באמצעות תגים

סקירה כללית

עבודה מוגדרת ב-WorkManager באמצעות WorkRequest. כדי לתזמן משימות באמצעות WorkManager, קודם צריך ליצור אובייקט WorkRequest ואז להוסיף אותו לתור.

Kotlin


val myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest)

Java


WorkRequest myWorkRequest = ...
WorkManager.getInstance(myContext).enqueue(myWorkRequest);

האובייקט WorkRequest מכיל את כל המידע שנחוץ ל-WorkManager כדי לתזמן ולהריץ את העבודה. הוא כולל אילוצים שצריך לעמוד בהם כדי שהעבודה תפעל, פרטי תזמון כמו עיכובים או מרווחי זמן חוזרים, הגדרות של ניסיונות חוזרים, ויכול לכלול נתוני קלט אם העבודה שלכם מסתמכת עליהם.

WorkRequest עצמו הוא סוג בסיס מופשט. יש שתי הטמעות נגזרות של הכיתה הזו שאפשר להשתמש בהן כדי ליצור את הבקשה, OneTimeWorkRequest ו-PeriodicWorkRequest. כפי שרואים מהשמות שלהם, OneTimeWorkRequest שימושי לתזמון משימות שלא חוזרות על עצמן, ואילו PeriodicWorkRequest מתאים יותר לתזמון משימות שחוזרות על עצמן במרווח זמן מסוים.

תזמון עבודה חד-פעמית

לעבודה פשוטה, שלא דורשת הגדרה נוספת, משתמשים ב-method הסטטי from:

Kotlin


val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)

Java


WorkRequest myWorkRequest = OneTimeWorkRequest.from(MyWork.class);

לעבודות מורכבות יותר, אפשר להשתמש ב-builder:

Kotlin

val uploadWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       // Additional configuration
       .build()

Java

WorkRequest uploadWorkRequest =
   new OneTimeWorkRequest.Builder(MyWork.class)
       // Additional configuration
       .build();

תזמון עבודה דחופה

בגרסה 2.7.0 של WorkManager הושק המושג 'משימה דחופה'. כך אפשר לבצע משימות חשובות ב-WorkManager, תוך מתן שליטה טובה יותר למערכת לגבי הגישה למשאבים.

למשימות דחופות יש את המאפיינים הבאים:

  • חשיבות: עבודה מהירה מתאימה למשימות שחשובות למשתמש או שהוא יזם.
  • מהירות: עבודה מהירה מתאימה למשימות קצרות שאפשר להתחיל מיד ולהשלים תוך כמה דקות.
  • מכסות: מכסה ברמת המערכת שמגבילה את זמן הביצוע בחזית קובעת אם ניתן להתחיל משימה מואצת.
  • ניהול צריכת החשמל: סביר להניח שהגבלות של ניהול צריכת החשמל, כמו 'חיסכון בסוללה' ו'מצב שינה', לא ישפיעו על עבודה מואצת.
  • זמן אחזור: המערכת מבצעת מיד משימות מהירות, בתנאי שעומס העבודה הנוכחי במערכת מאפשר לה לעשות זאת. כלומר, הן רגישות לזמן אחזור ואי אפשר לתזמן אותן לביצוע מאוחר יותר.

תרחיש לדוגמה לעבודה מהירה יכול להיות באפליקציית צ'אט, כשהמשתמש רוצה לשלוח הודעה או תמונה מצורפת. באופן דומה, באפליקציה שמטפלת בתהליך תשלום או מינוי כדאי להשתמש גם בעבודה מהירה. הסיבה לכך היא שהמשימות האלה חשובות למשתמש, מתבצעות במהירות ברקע, צריכות להתחיל באופן מיידי וצריך להמשיך לבצע אותן גם אם המשתמש סוגר את האפליקציה.

מכסות

המערכת צריכה להקצות זמן ביצוע למשימה מואצת כדי שתוכל לפעול. זמן הביצוע לא מוגבל. במקום זאת, לכל אפליקציה מוקצית מכסה של זמן ביצוע. כשהאפליקציה משתמשת בזמן הביצוע שלה ומגיעה למכסה שהוקצה לה, אי אפשר יותר לבצע משימות בעדיפות גבוהה עד שהמכסה תתעדכן. כך מערכת Android יכולה לאזן את המשאבים בין האפליקציות בצורה יעילה יותר.

משך זמן הביצוע שזמין לאפליקציה מבוסס על קטגוריית המתנה ועל מידת החשיבות של התהליך.

אתם יכולים לקבוע מה יקרה אם מכסת הביצועים לא תאפשר להריץ מיד משימות מהירות. פרטים נוספים מופיעים בקטעים הבאים.

ביצוע עבודה בעדיפות גבוהה

החל מגרסה 2.7 של WorkManager, האפליקציה יכולה להפעיל את setExpedited() כדי להצהיר ש-WorkRequest צריך לפעול במהירות האפשרית באמצעות משימה מואצת. קטע הקוד הבא הוא דוגמה לשימוש ב-setExpedited():

Kotlin

val request = OneTimeWorkRequestBuilder<SyncWorker>()
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build()

WorkManager.getInstance(context)
    .enqueue(request)

Java

OneTimeWorkRequest request = new OneTimeWorkRequestBuilder<T>()
    .setInputData(inputData)
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build();

בדוגמה הזו, אנחנו מאתחלים מכונה של OneTimeWorkRequest ומפעילים עליה את setExpedited(). לאחר מכן, הבקשה הזו תקבל טיפול מזורז. אם המכסה מאפשרת זאת, היא תתחיל לפעול באופן מיידי ברקע. אם המכסה נוצלה, הפרמטר OutOfQuotaPolicy מציין שצריך להריץ את הבקשה כעבודה רגילה ולא מואצת.

תאימות לאחור ושירותים שפועלים בחזית

כדי לשמור על תאימות לאחור למשימות מואצות, יכול להיות ש-WorkManager יפעל שירות בחזית בגרסאות פלטפורמה ישנות יותר מ-Android 12. שירותים שפועלים בחזית יכולים להציג התראה למשתמש.

השיטות getForegroundInfoAsync() ו-getForegroundInfo() ב-Worker מאפשרות ל-WorkManager להציג התראה כשאתם קוראים ל-setExpedited() לפני Android 12.

כדי לבקש שהמשימה תרוץ כמשימה מהירה, כל ListenableWorker חייב להטמיע את השיטה getForegroundInfo.

כשמטרגטים ל-Android 12 ואילך, שירותים שפועלים בחזית יישארו זמינים דרך השיטה המתאימה של setForeground.

Worker

העובדים לא יודעים אם העבודה שהם מבצעים היא בעדיפות גבוהה או לא. עם זאת, עובדים יכולים להציג התראה בגרסאות מסוימות של Android כשWorkRequest מזורז.

כדי לאפשר זאת, WorkManager מספק את השיטה getForegroundInfoAsync(), שצריך להטמיע כדי ש-WorkManager יוכל להציג התראה להפעלת ForegroundService במקרה הצורך.

CoroutineWorker

אם אתם משתמשים ב-CoroutineWorker, עליכם להטמיע את getForegroundInfo(). לאחר מכן מעבירים אותו אל setForeground() בתוך doWork(). הפעולה הזו תיצור את ההתראה בגרסאות Android שקדמו לגרסה 12.

דוגמה:

  class ExpeditedWorker(appContext: Context, workerParams: WorkerParameters):
   CoroutineWorker(appContext, workerParams) {

   override suspend fun getForegroundInfo(): ForegroundInfo {
       return ForegroundInfo(
           NOTIFICATION_ID, createNotification()
       )
   }

   override suspend fun doWork(): Result {
       TODO()
   }

    private fun createNotification() : Notification {
       TODO()
    }

}

מדיניות מכסות

אתם יכולים לקבוע מה יקרה לעבודה בעדיפות גבוהה כשהאפליקציה תגיע למכסת הביצועים שלה. כדי להמשיך, אפשר להעביר את setExpedited():

  • OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST, וכתוצאה מכך המשימה תרוץ כבקשת עבודה רגילה. קטע הקוד שלמעלה מדגים זאת.
  • OutOfQuotaPolicy.DROP_WORK_REQUEST, שגורם לביטול הבקשה אם אין מספיק מכסה.

אפליקציה לדוגמה

כדי לראות דוגמה מלאה לאופן שבו נעשה שימוש בעבודה מהירה ב-WorkManager 2.7.0, אפשר לעיין ב-WorkManagerSample ב-GitHub.

עבודה דחופה שנדחתה

המערכת מנסה להריץ משימה נתונה בעדיפות גבוהה בהקדם האפשרי אחרי ההפעלה שלה. עם זאת, כמו במקרים של סוגי משימות אחרים, המערכת עשויה לדחות את תחילת העבודה המהירה החדשה, למשל במקרים הבאים:

  • עומס: עומס המערכת גבוה מדי. המצב הזה יכול לקרות כשיש יותר מדי משימות שפועלות או כשאין מספיק זיכרון במערכת.
  • מכסה: חרגתם ממגבלת המכסה של משימות מהירות. בעבודה מהירה נעשה שימוש במערכת מכסות שמבוססת על הקטגוריות של זמן ההמתנה של האפליקציה, ומגבילה את זמן הביצוע המקסימלי בחלון זמן מתגלגל. המכסות לעבודה מהירה מגבילות יותר מאשר המכסות לסוגי משימות אחרים ברקע.

תזמון עבודה תקופתית

לפעמים יכול להיות שתצטרכו להריץ פעולות מסוימות באפליקציה באופן קבוע. לדוגמה, יכול להיות שתרצו לגבות מדי פעם את הנתונים, להוריד תוכן חדש לאפליקציה או להעלות יומנים לשרת.

כך משתמשים ב-PeriodicWorkRequest כדי ליצור אובייקט WorkRequest שמופעל באופן תקופתי:

Kotlin


val saveRequest =
       PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
    // Additional configuration
           .build()

Java


PeriodicWorkRequest saveRequest =
       new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class, 1, TimeUnit.HOURS)
           // Constraints
           .build();

בדוגמה הזו, העבודה מתוזמנת במרווח של שעה אחת.

פרק הזמן של הפסקה מוגדר כזמן המינימלי בין חזרות. המועד המדויק שבו העובד יופעל תלוי באילוצים שבהם אתם משתמשים באובייקט WorkRequest ובאופטימיזציות שהמערכת מבצעת.

מרווחים גמישים להרצה

אם אופי העבודה שלכם רגיש לתזמון ההרצה, תוכלו להגדיר את PeriodicWorkRequest כך שיפעל בתקופת גמישות בתוך כל תקופת מרווח, כפי שמוצג באיור 1.

אפשר להגדיר מרווח זמן גמיש למשימה תקופתית. מגדירים את מרווח החזרה ואת מרווח הגמישות, שמציין פרק זמן מסוים בסוף מרווח החזרה. ‏WorkManager מנסה להריץ את המשימה בשלב כלשהו במהלך מרווח הזמן הגמיש בכל מחזור.

איור 1. בתרשים מוצגים מרווחי זמן חוזרים עם פרק הזמן הגמיש שבו המשימה יכולה לפעול.

כדי להגדיר עבודה תקופתית עם תקופת גמישות, מעבירים את הערך flexInterval יחד עם הערך repeatInterval כשיוצרים את הערך PeriodicWorkRequest. תקופת הגמישות מתחילה ב-repeatInterval - flexInterval ועד לסוף התקופה.

הדוגמה הבאה היא של עבודה תקופתית שיכולה לפעול במהלך 15 הדקות האחרונות של כל תקופה של שעה אחת.

Kotlin


val myUploadWork = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(
       1, TimeUnit.HOURS, // repeatInterval (the period cycle)
       15, TimeUnit.MINUTES) // flexInterval
    .build()

Java


WorkRequest saveRequest =
       new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class,
               1, TimeUnit.HOURS,
               15, TimeUnit.MINUTES)
           .build();

מרווח החזרה חייב להיות גדול מ-PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS או שווה לו, ומרווח הגמישות חייב להיות גדול מ-PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS או שווה לו.

ההשפעה של אילוצים על עבודה תקופתית

אפשר להחיל אילוצים על עבודה תקופתית. לדוגמה, אפשר להוסיף אילוץ לבקשת העבודה כך שהיא תפעל רק כשהמכשיר של המשתמש נטען. במקרה כזה, גם אם מרווח הזמן להפעלה חוזרת מוגדר, הפונקציה PeriodicWorkRequest לא תפעל עד שהתנאי הזה יתקיים. כתוצאה מכך, יכול להיות שהרצה מסוימת של העבודה תידחה או אפילו תועבר אם התנאים לא יתקיימו במהלך מרווח הזמן להרצה.

אילוצים בעבודה

אילוצים מבטיחים שהעבודה תידחה עד שתושלמו התנאים האופטימליים. האילוצים הבאים זמינים ל-WorkManager.

NetworkType מגביל את סוג הרשת שנדרש כדי להפעיל את העבודה. לדוגמה, Wi-Fi‏ (UNMETERED).
BatteryNotLow אם הערך מוגדר כ-true, העבודה לא תפעל אם המכשיר נמצא במצב של סוללה חלשה.
RequiresCharging כשהיא מוגדרת כ-true, העבודה תפעל רק כשהמכשיר נמצא בטעינה.
DeviceIdle כשהערך מוגדר כ-True, העבודה תתבצע רק כשהמכשיר של המשתמש לא פעיל. האפשרות הזו יכולה להיות שימושית להרצת פעולות באצווה, שעלולות להשפיע לרעה על הביצועים של אפליקציות אחרות שפועלות באופן פעיל במכשיר של המשתמש.
StorageNotLow כשהערך מוגדר כ-true, העבודה לא תפעל אם נפח האחסון של המשתמש במכשיר נמוך מדי.

כדי ליצור קבוצה של אילוצים ולשייך אותה לעבודה מסוימת, יוצרים מכונה של Constraints באמצעות Contraints.Builder() ומקצים אותה ל-WorkRequest.Builder().

לדוגמה, הקוד הבא יוצר הזמנת עבודה שפועלת רק כשהמכשיר של המשתמש נטען וגם מחובר ל-Wi-Fi:

Kotlin


val constraints = Constraints.Builder()
   .setRequiredNetworkType(NetworkType.UNMETERED)
   .setRequiresCharging(true)
   .build()

val myWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       .setConstraints(constraints)
       .build()

Java


Constraints constraints = new Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .setRequiresCharging(true)
       .build();

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
               .setConstraints(constraints)
               .build();

אם מציינים כמה אילוצים, העבודה תופעל רק אם כל האילוצים מתקיימים.

אם אילוץ מסוים לא יתקיים בזמן שהעבודה פועלת, WorkManager יפסיק את העובד. לאחר מכן, המערכת תנסה שוב לבצע את העבודה אחרי שכל האילוצים יתקיימו.

עבודה מושהית

אם אין אילוצים לעבודה או שכל האילוצים מתקיימים כשהיא מצורפת לתור, יכול להיות שהמערכת תבחר להריץ את העבודה באופן מיידי. אם אתם לא רוצים שהעבודה תופעל באופן מיידי, תוכלו לציין שהיא תתחיל לאחר עיכוב ראשוני מינימלי.

דוגמה להגדרת העבודה כך שתופעל לפחות 10 דקות אחרי שהיא תתווסף לתור:

Kotlin


val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setInitialDelay(10, TimeUnit.MINUTES)
   .build()

Java


WorkRequest myWorkRequest =
      new OneTimeWorkRequest.Builder(MyWork.class)
               .setInitialDelay(10, TimeUnit.MINUTES)
               .build();

בדוגמה הזו מוסבר איך להגדיר עיכוב ראשוני ל-OneTimeWorkRequest, אבל אפשר גם להגדיר עיכוב ראשוני ל-PeriodicWorkRequest. במקרה כזה, רק ההפעלה הראשונה של העבודה הקבועה תידחה.

מדיניות של ניסיונות חוזרים והשהיה

אם אתם רוצים ש-WorkManager ינסה שוב לבצע את העבודה, תוכלו להחזיר את הערך Result.retry() מהעובד. לאחר מכן, העבודה תתוזמן מחדש בהתאם להשהיה של זמן ההשהיה ולמדיניות ההשהיה.

  • השהיה לפני ניסיון חוזר (backoff) מציינת את משך הזמן המינימלי להמתנה לפני ניסיון חוזר לביצוע העבודה אחרי הניסיון הראשון. הערך הזה לא יכול להיות קטן מ-10 שניות (או MIN_BACKOFF_MILLIS).

  • מדיניות ההשהיה קובעת איך זמן ההשהיה צריך לגדול עם הזמן בניסיונות הניסיון החוזרים. ‏WorkManager תומך בשתי מדיניות השהיה, LINEAR ו-EXPONENTIAL.

לכל בקשת עבודה יש מדיניות השהיה לפני ניסיון חוזר (backoff) והשהיה לפני ניסיון חוזר. מדיניות ברירת המחדל היא EXPONENTIAL עם עיכוב של 30 שניות, אבל אפשר לשנות את זה בהגדרות של בקשת העבודה.

דוגמה להתאמה אישית של המדיניות וההשהיה של הזמן להשהיה.

Kotlin


val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setBackoffCriteria(
       BackoffPolicy.LINEAR,
       OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
       TimeUnit.MILLISECONDS)
   .build()

Java


WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
               .setBackoffCriteria(
                       BackoffPolicy.LINEAR,
                       OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
                       TimeUnit.MILLISECONDS)
               .build();

בדוגמה הזו, עיכוב ה-backoff המינימלי מוגדר לערך המינימלי המותר, 10 שניות. מכיוון שהמדיניות היא LINEAR, מרווח הזמן לניסיון חוזר יגדל ב-10 שניות בערך בכל ניסיון חדש. לדוגמה, ההרצה הראשונה שמסתיימת ב-Result.retry() תנסה שוב אחרי 10 שניות, ואחריה 20, 30, 40 וכן הלאה, אם העבודה תמשיך להחזיר את הערך Result.retry() אחרי ניסיונות נוספים. אם מדיניות ההשהיה מוגדרת לערך EXPONENTIAL, רצף משך הזמן לניסיון חוזר יהיה קרוב יותר ל-20, 40, 80 וכן הלאה.

תיוג עבודות

לכל בקשת עבודה יש מזהה ייחודי, שבעזרתו אפשר לזהות את העבודה הזו מאוחר יותר כדי לבטל אותה או לעקוב אחרי ההתקדמות שלה.

אם יש לכם קבוצה של משימות שקשורות זו לזו באופן לוגי, כדאי גם לתייג את הפריטים האלה. התיוג מאפשר לכם לעבוד יחד עם קבוצה של בקשות עבודה.

לדוגמה, הפונקציה WorkManager.cancelAllWorkByTag(String) מבטלת את כל בקשות העבודה עם תג מסוים, והפונקציה WorkManager.getWorkInfosByTag(String) מחזירה רשימה של אובייקטי WorkInfo שאפשר להשתמש בהם כדי לקבוע את סטטוס העבודה הנוכחי.

הקוד הבא מראה איך מוסיפים תג 'cleanup' לעבודה:

Kotlin


val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .addTag("cleanup")
   .build()

Java


WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
       .addTag("cleanup")
       .build();

לבסוף, אפשר להוסיף כמה תגים לבקשת עבודה אחת. בפנים, התגים האלה נשמרים כקבוצת מחרוזות. כדי לקבל את קבוצת התגים שמשויכת ל-WorkRequest, אפשר להשתמש ב-WorkInfo.getTags()‎.

אפשר לאחזר את קבוצת התגים של הכיתה Worker באמצעות ListenableWorker.getTags()‎.

הקצאת נתוני קלט

יכול להיות שהעבודה שלכם תדרוש נתוני קלט כדי לבצע את הפעולות שלה. לדוגמה, יכול להיות שבעבודה שמטפלת בהעלאת תמונה יהיה צורך להעלות את מזהה ה-URI של התמונה כקלט.

ערכי הקלט נשמרים כצמדי מפתח/ערך באובייקט Data, וניתן להגדיר אותם בבקשת העבודה. WorkManager יעביר את הקלט Data למשימה כשהיא תבוצע. הכיתה Worker יכולה לגשת לפרמטרים של הקלט באמצעות קריאה ל-Worker.getInputData(). בקוד שבהמשך מוסבר איך יוצרים מכונה של Worker שדורשת נתוני קלט, ואיך שולחים אותם בבקשת העבודה.

Kotlin


// Define the Worker requiring input
class UploadWork(appContext: Context, workerParams: WorkerParameters)
   : Worker(appContext, workerParams) {

   override fun doWork(): Result {
       val imageUriInput =
           inputData.getString("IMAGE_URI") ?: return Result.failure()

       uploadFile(imageUriInput)
       return Result.success()
   }
   ...
}

// Create a WorkRequest for your Worker and sending it input
val myUploadWork = OneTimeWorkRequestBuilder<UploadWork>()
   .setInputData(workDataOf(
       "IMAGE_URI" to "http://..."
   ))
   .build()

Java


// Define the Worker requiring input
public class UploadWork extends Worker {

   public UploadWork(Context appContext, WorkerParameters workerParams) {
       super(appContext, workerParams);
   }

   @NonNull
   @Override
   public Result doWork() {
       String imageUriInput = getInputData().getString("IMAGE_URI");
       if(imageUriInput == null) {
           return Result.failure();
       }

       uploadFile(imageUriInput);
       return Result.success();
   }
   ...
}

// Create a WorkRequest for your Worker and sending it input
WorkRequest myUploadWork =
      new OneTimeWorkRequest.Builder(UploadWork.class)
           .setInputData(
               new Data.Builder()
                   .putString("IMAGE_URI", "http://...")
                   .build()
           )
           .build();

באופן דומה, אפשר להשתמש בכיתה Data כדי להפיק ערך מוחזר. נתוני הקלט והפלט מפורטים בהרחבה בקטע פרמטרים של קלט וערכים שמוחזרים.

השלבים הבאים

בדף States and observation מוסבר בהרחבה על מצבי העבודה ועל האופן שבו עוקבים אחרי התקדמות העבודה.