ניהול העבודה

אחרי שמגדירים את 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);

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

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 שלכם מופסק.

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

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

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

המאפיין isStopped()‎

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

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

התבוננות במצב של סיבת העצירה

כדי לנפות באגים ולגלות למה Worker הופסק, אפשר לתעד את סיבת ההפסקה על ידי קריאה ל-WorkInfo.getStopReason():

Kotlin

workManager.getWorkInfoByIdFlow(syncWorker.id)
  .collect { workInfo ->
      if (workInfo != null) {
        val stopReason = workInfo.stopReason
        logStopReason(syncWorker.id, stopReason)
      }
  }

Java

  workManager.getWorkInfoByIdLiveData(syncWorker.id)
    .observe(getViewLifecycleOwner(), workInfo -> {
        if (workInfo != null) {
          int stopReason = workInfo.getStopReason();
          logStopReason(syncWorker.id, workInfo.getStopReason());
        }
  });