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

במדריך לתחילת העבודה מוסבר איך ליצור 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);

למשימות מורכבות יותר, אפשר להשתמש בכלי לבניית אתרים:

Kotlin

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

Java

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

תזמון של משימות דחופות

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

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

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

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

מכסות

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

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

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

ביצוע משימה דחופה

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

Kotlin

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

WorkManager.getInstance(context)
    .enqueue(request)

Java

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

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

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

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

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

כל ListenableWorker צריך להטמיע את השיטה getForegroundInfo אם רוצים לבקש שהמשימה תפעל כמשימה בעדיפות גבוהה.

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

קובץ שירות

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

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

משימה דחופה שנדחתה

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

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

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

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

כך משתמשים ב-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();

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

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

מגבלות בעבודה

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

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

כדי ליצור קבוצה של אילוצים ולשייך אותה לעבודה מסוימת, יוצרים מופע Constraints באמצעות Constraints.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() מה-Worker. לאחר מכן, העבודה מתוזמנת מחדש בהתאם להשהיה לפני ניסיון חוזר (backoff) ולמדיניות הנסיגה.

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

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

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

דוגמה להתאמה אישית של מדיניות ההשהיה לפני ניסיון חוזר (backoff) וההשהיה עצמה.

Kotlin

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

Java

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

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

תיוג עבודה

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

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

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

הקוד הבא מראה איך אפשר להוסיף תג 'ניקוי' לעבודה:

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 כדי להפיק ערך החזרה.

השלבים הבאים

בדף מצבים ותצפיות מוסבר על מצבי העבודה ואיך לעקוב אחרי התקדמות העבודה.