במדריך לתחילת העבודה מוסבר איך ליצור 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);
למשימות מורכבות יותר, אתם יכולים להשתמש בכלי לבניית אתרים:
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>()
<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 ואילך, השירותים שפועלים בחזית עדיין זמינים באמצעות ה-method המתאימה של setForeground.
קובץ שירות
העובדים לא יודעים אם העבודה שהם מבצעים היא דחופה או לא. אבל בגרסאות מסוימות של Android, ה-workers יכולים להציג התראה כש-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.

איור 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 יפסיק את ה-worker. המערכת תנסה לבצע שוב את העבודה כשיתקיימו כל האילוצים.
עבודה מאוחרת
אם אין למשימה אילוצים, או שכל האילוצים מתקיימים כשהמשימה נכנסת לתור, המערכת עשויה לבחור להריץ את המשימה באופן מיידי. אם אתם לא רוצים שהמשימה תרוץ מיד, אתם יכולים לציין שהיא תתחיל אחרי השהיה ראשונית מינימלית.
הדוגמה הבאה ממחישה איך להגדיר את העבודה כך שתפעל לפחות 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. לאחר מכן, המשימה מתוזמנת מחדש בהתאם להשהיית השהייה ומדיניות השהייה.
השהיה לפני ניסיון חוזר מציינת את משך הזמן המינימלי שצריך להמתין לפני ניסיון חוזר לביצוע המשימה אחרי הניסיון הראשון. הערך הזה לא יכול להיות פחות מ-10 שניות (או MIN_BACKOFF_MILLIS).
מדיניות ההשהיה מגדירה איך זמן ההשהיה צריך לגדול עם הזמן בניסיונות חוזרים עוקבים. WorkManager תומך בשתי כללי מדיניות של השהיה לפני ניסיון חוזר,
LINEARו-EXPONENTIAL.
לכל בקשת עבודה יש מדיניות השהיה וזמן השהיה. מדיניות ברירת המחדל היא EXPONENTIAL עם השהיה של 30 שניות, אבל אפשר לשנות אותה בהגדרות של בקשת העבודה.
דוגמה להתאמה אישית של מדיניות ההשהיה ומשך ההשהיה:
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();
בדוגמה הזו, ההשהיה המינימלית של השהיית הניסיון החוזר מוגדרת לערך המינימלי המותר, 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 כדי להפיק ערך מוחזר.
השלבים הבאים
בדף מצבים ותצפיות מוסבר על מצבי העבודה ואיך לעקוב אחרי התקדמות העבודה.