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

במדריך לתחילת העבודה מוסבר איך ליצור 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 מתאים יותר לתזמון עבודות שחוזרות על עצמן במרווחי זמן מסוימים.

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

כדי לבצע פעולות פשוטות, שלא מחייבת הגדרות נוספות, משתמשים בשיטה הסטטית 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 אפשר לבצע עבודה חשובה ובמקביל לתת למערכת שליטה טובה יותר על הגישה למשאבים.

המשימות בעדיפות גבוהה מתאפיינות בתכונות הבאות:

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

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

מכסות

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

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

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

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

החל מ-WorkManager 2.7, האפליקציה יכולה להפעיל את 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.

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

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

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

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

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

כך משתמשים ב-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. במקרה כזה, רק ההפעלה הראשונה של העבודה הקבועה תידחה.

המדיניות בנושא ניסיון חוזר והשהיה לפני ניסיון חוזר (backoff)

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

  • עיכוב לפני ניסיון חוזר מציין את משך הזמן המינימלי שצריך להמתין לפני הניסיון הראשון לבצע את העבודה אחרי הניסיון הראשון. הערך הזה לא יכול להיות קטן מ-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 מוסבר בהרחבה על מצבי העבודה ועל האופן שבו עוקבים אחרי התקדמות העבודה.