ניהול העבודה

אחרי שמגדירים את Worker ואת WorkRequest, השלב האחרון הוא להוסיף את העבודה לתור. הדרך הכי פשוטה להוסיף עבודה לתור היא לקרוא לשיטה enqueue() של WorkManager ולהעביר את WorkRequest שרוצים להריץ.

Kotlin

val myWork: WorkRequest = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork)

Java

WorkRequest myWork = // ... OneTime or PeriodicWork
WorkManager.getInstance(requireContext()).enqueue(myWork);

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

עבודה ייחודית

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

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

שתי השיטות האלה מקבלות 3 ארגומנטים:

  • uniqueWorkNameString שמשמש לזיהוי ייחודי של בקשת העבודה.
  • existingWorkPolicyenum שמציין ל-WorkManager מה לעשות אם כבר יש שרשרת עבודה לא גמורה עם השם הייחודי הזה. מידע נוסף זמין במדיניות בנושא פתרון סכסוכים.
  • workWorkRequest לתזמון.

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

Kotlin

val sendLogsWorkRequest =
       PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS)
           .setConstraints(Constraints.Builder()
               .setRequiresCharging(true)
               .build()
            )
           .build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
           "sendLogs",
           ExistingPeriodicWorkPolicy.KEEP,
           sendLogsWorkRequest
)

Java

PeriodicWorkRequest sendLogsWorkRequest = new
      PeriodicWorkRequest.Builder(SendLogsWorker.class, 24, TimeUnit.HOURS)
              .setConstraints(new Constraints.Builder()
              .setRequiresCharging(true)
          .build()
      )
     .build();
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
     "sendLogs",
     ExistingPeriodicWorkPolicy.KEEP,
     sendLogsWorkRequest);

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

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

מדיניות יישוב מחלוקות

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

לגבי עבודה חד-פעמית, אתם מספקים ExistingWorkPolicy, שכולל 4 אפשרויות לטיפול בסתירה.

  • REPLACE קיים לעבוד עם העבודה החדשה. האפשרות הזו מבטלת את העבודה הקיימת.
  • KEEP את העבודה הקיימת ויתעלם מהעבודה החדשה.
  • APPEND את העבודה החדשה בסוף העבודה הקיימת. המדיניות הזו תגרום לכך שהעבודה החדשה תקושר לעבודה הקיימת ותפעל אחרי שהעבודה הקיימת תסתיים.

העבודה הקיימת הופכת לדרישה מוקדמת לעבודה החדשה. אם העבודה הקיימת הופכת ל-CANCELLED או ל-FAILED, גם העבודה החדשה תהיה CANCELLED או FAILED. אם רוצים שהעבודה החדשה תפעל בלי קשר לסטטוס של העבודה הקיימת, צריך להשתמש ב-APPEND_OR_REPLACE במקום זאת.

  • הפונקציה APPEND_OR_REPLACE פועלת באופן דומה לפונקציה APPEND, אבל היא לא תלויה בסטטוס העבודה של דרישת הסף. אם העבודה הקיימת היא CANCELLED או FAILED, העבודה החדשה עדיין תפעל.

כשמדובר בעבודה לתקופה מסוימת, צריך לציין את הערך ExistingPeriodicWorkPolicy, שכולל 2 אפשרויות: REPLACE ו-KEEP. האפשרויות האלה פועלות כמו האפשרויות המקבילות להן במדיניות הקיימת.

התבוננות בעבודה

בכל שלב אחרי הוספת העבודה לתור, אפשר לבדוק את הסטטוס שלה על ידי שליחת שאילתה ל-WorkManager לפי name, id או לפי tag שמשויך אליה.

Kotlin

// by id
workManager.getWorkInfoById(syncWorker.id) // ListenableFuture<WorkInfo>

// by name
workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture<List<WorkInfo>>

// by tag
workManager.getWorkInfosByTag("syncTag") // ListenableFuture<List<WorkInfo>>

Java

// by id
workManager.getWorkInfoById(syncWorker.id); // ListenableFuture<WorkInfo>

// by name
workManager.getWorkInfosForUniqueWork("sync"); // ListenableFuture<List<WorkInfo>>

// by tag
workManager.getWorkInfosByTag("syncTag"); // ListenableFuture<List<WorkInfo>>

השאילתה מחזירה ListenableFuture של אובייקט WorkInfo, שכולל את id של היצירה, התגים שלה, State הנוכחי שלה וכל מערך נתונים של פלט באמצעות Result.success(outputData).

הווריאציות LiveData ו-Flow של כל אחת מהשיטות מאפשרות לראות שינויים ב-WorkInfo על ידי רישום של listener. לדוגמה, אם רוצים להציג הודעה למשתמש כשעבודה מסוימת מסתיימת בהצלחה, אפשר להגדיר את זה כך:

Kotlin

workManager.getWorkInfoByIdFlow(syncWorker.id)
          .collect{ workInfo ->
              if(workInfo?.state == WorkInfo.State.SUCCEEDED) {
                  Snackbar.make(requireView(),
                      R.string.work_completed, Snackbar.LENGTH_SHORT)
                      .show()
              }
          }

Java

workManager.getWorkInfoByIdLiveData(syncWorker.id)
        .observe(getViewLifecycleOwner(), workInfo -> {
    if (workInfo.getState() != null &&
            workInfo.getState() == WorkInfo.State.SUCCEEDED) {
        Snackbar.make(requireView(),
                    R.string.work_completed, Snackbar.LENGTH_SHORT)
                .show();
   }
});

שאילתות מורכבות שקשורות לעבודה

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

בדוגמה הבאה אפשר לראות איך מוצאים את כל העבודות עם התג syncTag, שנמצאות במצב FAILED או CANCELLED, ושם העבודה הייחודי שלהן הוא preProcess או sync.

Kotlin

val workQuery = WorkQuery.Builder
       .fromTags(listOf("syncTag"))
       .addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
       .addUniqueWorkNames(listOf("preProcess", "sync")
    )
   .build()

val workInfos: ListenableFuture<List<WorkInfo>> = workManager.getWorkInfos(workQuery)

Java

WorkQuery workQuery = WorkQuery.Builder
       .fromTags(Arrays.asList("syncTag"))
       .addStates(Arrays.asList(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
       .addUniqueWorkNames(Arrays.asList("preProcess", "sync")
     )
    .build();

ListenableFuture<List<WorkInfo>> workInfos = workManager.getWorkInfos(workQuery);

כל רכיב (תג, מצב או שם) ב-WorkQuery מופרד מAND-ים אחרים. כל ערך ברכיב מופרד באמצעות OR. לדוגמה: (name1 OR name2 OR ...) AND (tag1 OR tag2 OR ...) AND (state1 OR state2 OR ...).

WorkQuery פועל גם עם המקבילה של LiveData,‏ getWorkInfosLiveData(), ועם המקבילה של Flow, ‏ getWorkInfosFlow().

ביטול והפסקת העבודה

אם אתם כבר לא צריכים שהעבודה שהוספתם לתור תרוץ, אתם יכולים לבקש לבטל אותה. אפשר לבטל עבודות על ידי name, id או tag שמשויכים אליהן.

Kotlin

// by id
workManager.cancelWorkById(syncWorker.id)

// by name
workManager.cancelUniqueWork("sync")

// by tag
workManager.cancelAllWorkByTag("syncTag")

Java

// by id
workManager.cancelWorkById(syncWorker.id);

// by name
workManager.cancelUniqueWork("sync");

// by tag
workManager.cancelAllWorkByTag("syncTag");

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

RUNNING work receives a call to ListenableWorker.onStopped(). אפשר לבטל את השיטה הזו כדי לטפל בניקוי פוטנציאלי. מידע נוסף זמין במאמר בנושא עצירה של תהליך worker פעיל.

הפסקה של Worker פעיל

יש כמה סיבות לכך ש-WorkManager עשוי להפסיק את הפעולה Worker:

  • ביקשתם במפורש לבטל את המינוי (לדוגמה, התקשרתם אל WorkManager.cancelWorkById(UUID)).
  • במקרה של עבודה ייחודית, הוספתם במפורש לתור WorkRequest חדש עם ExistingWorkPolicy של REPLACE. המינוי הישן WorkRequest מבוטל באופן מיידי.
  • העבודה שלך כבר לא עומדת במגבלות.
  • מסיבה כלשהי, המערכת הורתה לאפליקציה להפסיק את העבודה. זה יכול לקרות אם חורגים מהזמן הקצוב להפעלה של 10 דקות. המערכת תנסה לבצע את הפעולה שוב מאוחר יותר.

במקרים כאלה, ה-Worker שלכם מופסק.

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

הקריאה החוזרת (callback) onStopped()‎

‫WorkManager מפעיל את ListenableWorker.onStopped() ברגע שה-Worker שלכם נעצר. אפשר לבטל את השיטה הזו כדי לסגור משאבים שאתם מחזיקים בהם.

המאפיין isStopped()‎

אפשר לקרוא לשיטה ListenableWorker.isStopped() כדי לבדוק אם העובד כבר הופסק. אם אתם מבצעים פעולות ארוכות או חוזרות ב-Worker, כדאי לבדוק את המאפיין הזה לעיתים קרובות ולהשתמש בו כאות להפסקת העבודה בהקדם האפשרי.

הערה:‏ WorkManager מתעלם מהערך Result שמוגדר על ידי Worker שקיבל את האות onStop, כי ה-Worker כבר נחשב למופסק.