Android Kotlin Fundamentals 09.2: WorkManager

ה-codelab הזה הוא חלק מהקורס Android Kotlin Fundamentals. כדי להפיק את המרב מהקורס הזה, מומלץ לעבוד על ה-codelabs לפי הסדר. כל ה-codelab של הקורס מפורטים בדף הנחיתה של ה-codelab בנושא יסודות Kotlin ל-Android.

מבוא

ברוב האפליקציות בעולם האמיתי יש צורך לבצע משימות ארוכות ברקע. לדוגמה, אפליקציה יכולה להעלות קבצים לשרת, לסנכרן נתונים משרת ולשמור אותם במסד נתונים Room, לשלוח יומנים לשרת או לבצע פעולות יקרות על נתונים. צריך לבצע פעולות כאלה ברקע, מחוץ ל-thread של ממשק המשתמש (ה-thread הראשי). משימות ברקע צורכות משאבים מוגבלים של המכשיר, כמו זיכרון RAM וסוללה. אם לא מטפלים בבעיה בצורה נכונה, היא עלולה לפגוע בחוויית המשתמש.

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

מה שכדאי לדעת

  • איך משתמשים ברכיבי הארכיטקטורה של Android‏ ViewModel,‏ LiveData ו-Room.
  • איך מבצעים טרנספורמציות במחלקה LiveData.
  • איך בונים ומפעילים קורוטינה.
  • איך משתמשים במתאמי קישור בקישור נתונים.
  • איך טוענים נתונים שנשמרו במטמון באמצעות תבנית מאגר.

מה תלמדו

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

הפעולות שתבצעו:

  • יוצרים Worker כדי להריץ משימה ברקע לאחזור מראש של פלייליסט הסרטונים DevBytes מהרשת.
  • מתזמנים את העובד להפעלה תקופתית.
  • הוספת מגבלות ל-WorkRequest.
  • מתזמנים WorkRequest תקופתי שמופעל פעם ביום.

ב-codelab הזה תעבדו על אפליקציית DevBytes שפיתחתם ב-codelab קודם. (אם האפליקציה לא מותקנת במכשיר, אפשר להוריד קוד התחלתי לשיעור הזה).

באפליקציית DevBytes מוצגת רשימה של סרטוני DevByte, שהם מדריכים קצרים שנוצרו על ידי צוות יחסי המפתחים של Google Android. בסרטונים מוצגות תכונות למפתחים ושיטות מומלצות לפיתוח ל-Android.

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

במשימה הזו תורידו את קוד ההתחלה ותבדקו אותו.

שלב 1: הורדה והפעלה של אפליקציית המתחילים

אתם יכולים להמשיך לעבוד על אפליקציית DevBytes שיצרתם ב-codelab הקודם (אם יש לכם אותה). אפשרות נוספת היא להוריד את אפליקציית המתחילים.

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

  1. אם עדיין לא התקנתם את אפליקציית DevBytes, אתם יכולים להוריד את קוד ההתחלה של DevBytes בשביל ה-codelab הזה מפרויקט DevBytesRepository ב-GitHub.
  2. מחלצים את הקוד ופותחים את הפרויקט ב-Android Studio.
  3. אם מכשיר הבדיקה או האמולטור לא מחוברים לאינטרנט, צריך לחבר אותם. מבצעים Build לאפליקציה ומריצים אותה. האפליקציה מאחזרת את רשימת סרטוני DevByte מהרשת ומציגה אותם.
  4. באפליקציה, מקישים על סרטון כלשהו כדי לפתוח אותו באפליקציית YouTube.

שלב 2: בודקים את הקוד

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

  1. ב-Android Studio, מרחיבים את כל החבילות.
  2. מעיינים בחבילה database. החבילה מכילה את ישויות מסד הנתונים ואת מסד הנתונים המקומי, שמיושם באמצעות Room.
  3. מעיינים בחבילה repository. החבילה מכילה את המחלקה VideosRepository שמבצעת הפשטה של שכבת הנתונים משאר האפליקציה.
  4. אתם יכולים לעיין בשאר קוד המתחילים בעצמכם, בעזרת ה-Codelab הקודם.

WorkManager הוא אחד מרכיבי הארכיטקטורה של Android וחלק מ-Android Jetpack. ‫WorkManager מיועד לעבודות רקע שאפשר לדחות אותן ונדרשת הרצה מובטחת שלהן:

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

בזמן ש-WorkManager פועל ברקע, הוא מטפל בבעיות תאימות ובשיטות מומלצות לשמירה על תקינות הסוללה והמערכת. ‫WorkManager תואם לגרסאות עד רמת API‏ 14. ‫WorkManager בוחר דרך מתאימה לתזמון משימה ברקע, בהתאם לרמת ה-API של המכשיר. יכול להיות שהיא תשתמש ב-JobScheduler (ב-API 23 ומעלה) או בשילוב של AlarmManager ו-BroadcastReceiver.

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

ב-codelab הזה, מתזמנים משימה לאחזור מראש של פלייליסט הסרטונים DevBytes מהרשת פעם ביום. כדי לתזמן את המשימה הזו, משתמשים בספרייה WorkManager.

  1. פותחים את הקובץ build.gradle (Module:app) ומוסיפים את התלות WorkManager לפרויקט.

    אם משתמשים בגרסה האחרונה של הספרייה, האפליקציה של הפתרון אמורה לעבור קומפילציה כמצופה. אם לא, צריך לפתור את הבעיה או לחזור לגרסת הספרייה שמוצגת בהמשך.
// WorkManager dependency
def work_version = "1.0.1"
implementation "android.arch.work:work-runtime-ktx:$work_version"
  1. מסנכרנים את הפרויקט ומוודאים שאין שגיאות קומפילציה.

לפני שמוסיפים קוד לפרויקט, כדאי להכיר את המחלקות הבאות בספריית WorkManager:

  • Worker
    במחלקת ה-class הזו מגדירים את העבודה בפועל (המשימה) שתופעל ברקע. אתם מרחיבים את המחלקה הזו ומבטלים את השיטה doWork(). בשיטה doWork() מוסיפים קוד שיופעל ברקע, כמו סנכרון נתונים עם השרת או עיבוד תמונות. במשימה הזו מטמיעים את Worker.
  • WorkRequest
    המחלקות האלה מייצגות בקשה להפעלת העובד ברקע. משתמשים ב-WorkRequest כדי להגדיר איך ומתי להריץ את משימת ה-Worker, בעזרת Constraints כמו חיבור המכשיר לחשמל או חיבור ל-Wi-Fi. תטמיעו את WorkRequest במשימה מאוחרת יותר.
  • WorkManager
    הכיתה הזו מתזמנת ומפעילה את WorkRequest. ‫WorkManager מתזמן בקשות עבודה באופן שמפזר את העומס על משאבי המערכת, תוך התחשבות במגבלות שציינתם. תטמיעו את WorkManager במשימה מאוחרת יותר.

שלב 1: יצירת עובד

במשימה הזו, מוסיפים Worker כדי לבצע אחזור מראש של פלייליסט הסרטונים DevBytes ברקע.

  1. בתוך חבילת devbyteviewer, יוצרים חבילה חדשה בשם work.
  2. בתוך חבילת work, יוצרים מחלקה חדשה של Kotlin בשם RefreshDataWorker.
  3. הרחבת השיעור RefreshDataWorker מהשיעור CoroutineWorker. מעבירים את context ואת WorkerParameters כפרמטרים של בנאי.
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
       CoroutineWorker(appContext, params) {
}
  1. כדי לפתור את השגיאה שקשורה למחלקה מופשטת, צריך לבטל את השיטה doWork() בתוך המחלקה RefreshDataWorker.
override suspend fun doWork(): Result {
  return Result.success()
}

פונקציית השהיה היא פונקציה שאפשר להשהות ולהמשיך אותה מאוחר יותר. פונקציה להשהיה יכולה להריץ פעולה ארוכה ולהמתין להשלמתה בלי לחסום את ה-thread הראשי.

שלב 2: הטמעה של doWork()‎

ה-method‏ doWork() בתוך המחלקה Worker מופעל בשרשור ברקע. השיטה מבצעת עבודה באופן סינכרוני, והיא אמורה להחזיר אובייקט ListenableWorker.Result. מערכת Android נותנת ל-Worker עד 10 דקות לסיים את ההרצה ולהחזיר אובייקט ListenableWorker.Result. אחרי שפרק הזמן הזה מסתיים, המערכת מפסיקה את Worker באופן אוטומטי.

כדי ליצור אובייקט ListenableWorker.Result, קוראים לאחת מהשיטות הסטטיות הבאות כדי לציין את סטטוס השלמת העבודה ברקע:

  • Result.success() – העבודה הושלמה בהצלחה.
  • Result.failure() – העבודה הושלמה עם כשל קבוע.
  • Result.retry() – העבודה נתקלה בשגיאה זמנית וצריך לנסות שוב.

במשימה הזו, מטמיעים את השיטה doWork() כדי לאחזר את פלייליסט הסרטונים DevBytes מהרשת. אפשר להשתמש מחדש בשיטות הקיימות במחלקה VideosRepository כדי לאחזר את הנתונים מהרשת.

  1. בכיתה RefreshDataWorker, בתוך doWork(), יוצרים מופע של אובייקט VideosDatabase ומופע של אובייקט VideosRepository.
override suspend fun doWork(): Result {
   val database = getDatabase(applicationContext)
   val repository = VideosRepository(database)

   return Result.success()
}
  1. בכיתה RefreshDataWorker, בתוך doWork(), מעל ההצהרה return, קוראים למתודה refreshVideos() בתוך בלוק try. מוסיפים יומן כדי לעקוב אחרי מועד ההפעלה של ה-Worker.
try {
   repository.refreshVideos( )
   Timber.d("Work request for sync is run")
   } catch (e: HttpException) {
   return Result.retry()
}

כדי לפתור את השגיאה 'הפניה לא פתורה', מייבאים את retrofit2.HttpException.

  1. לעיון, הנה הכיתה המלאה RefreshDataWorker:
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
       CoroutineWorker(appContext, params) {

   override suspend fun doWork(): Result {
       val database = getDatabase(applicationContext)
       val repository = VideosRepository(database)
       try {
           repository.refreshVideos()
       } catch (e: HttpException) {
           return Result.retry()
       }
       return Result.success()
   }
}

Worker מגדיר יחידת עבודה, ו-WorkRequest מגדיר איך ומתי העבודה צריכה להתבצע. יש שתי הטמעות קונקרטיות של המחלקה WorkRequest:

  • הכיתה OneTimeWorkRequest מיועדת למטלות חד-פעמיות. (משימה חד-פעמית מתבצעת רק פעם אחת).
  • הכיתה PeriodicWorkRequest מיועדת לעבודות תקופתיות, עבודות שחוזרות על עצמן במרווחי זמן קבועים.

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

במשימה הזו מגדירים ומתזמנים WorkRequest להפעלת העובד שיצרתם במשימה הקודמת.

שלב 1: הגדרת עבודה חוזרת

בתוך אפליקציית Android, המחלקה Application היא מחלקת הבסיס שמכילה את כל הרכיבים האחרים, כמו פעילויות ושירותים. כשנוצר התהליך של האפליקציה או החבילה, מתבצעת יצירה של מופע של המחלקה Application (או של כל מחלקת משנה של Application) לפני כל מחלקה אחרת.

באפליקציה לדוגמה הזו, המחלקה DevByteApplication היא מחלקת משנה של המחלקה Application. כדאי לתזמן את WorkManager בכיתה DevByteApplication.

  1. בכיתה DevByteApplication, יוצרים שיטה בשם setupRecurringWork() כדי להגדיר את עבודת הרקע החוזרת.
/**
* Setup WorkManager background job to 'fetch' new network data daily.
*/
private fun setupRecurringWork() {
}
  1. בתוך השיטה setupRecurringWork(), יוצרים ומפעילים בקשה תקופתית לעבודה שתרוץ פעם ביום, באמצעות השיטה PeriodicWorkRequestBuilder(). מעבירים את המחלקה RefreshDataWorker שיצרתם במשימה הקודמת. מעבירים אינטרוול חזרה של 1 עם יחידת זמן של TimeUnit.DAYS.
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
       .build()

כדי לפתור את השגיאה, מייבאים את java.util.concurrent.TimeUnit.

שלב 2: תזמון WorkRequest באמצעות WorkManager

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

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

מידע נוסף על דרכים לתזמון של WorkRequest זמין במאמר WorkManager.

  1. בכיתה RefreshDataWorker, בתחילת הכיתה, מוסיפים אובייקט נלווה. מגדירים שם עובד כדי לזהות באופן ייחודי את העובד הזה.
companion object {
   const val WORK_NAME = "com.example.android.devbyteviewer.work.RefreshDataWorker"
}
  1. ב-class‏ DevByteApplication, בסוף ה-method‏ setupRecurringWork(), מתזמנים את העבודה באמצעות ה-method‏ enqueueUniquePeriodicWork(). מעבירים את הערך enum‏ KEEP עבור ExistingPeriodicWorkPolicy. מעבירים את הערך repeatingRequest כפרמטר PeriodicWorkRequest.
WorkManager.getInstance().enqueueUniquePeriodicWork(
       RefreshDataWorker.WORK_NAME,
       ExistingPeriodicWorkPolicy.KEEP,
       repeatingRequest)

אם יש עבודה בהמתנה (לא הושלמה) עם אותו שם, הפרמטר ExistingPeriodicWorkPolicy.KEEP גורם ל-WorkManager לשמור את העבודה התקופתית הקודמת ולבטל את בקשת העבודה החדשה.

  1. בתחילת המחלקה DevByteApplication, יוצרים אובייקט CoroutineScope. מעבירים את Dispatchers.Default כפרמטר של ה-constructor.
private val applicationScope = CoroutineScope(Dispatchers.Default)
  1. במחלקה DevByteApplication, מוסיפים שיטה חדשה בשם delayedInit() כדי להפעיל קורוטינה.
private fun delayedInit() {
   applicationScope.launch {
   }
}
  1. בתוך הפונקציה delayedInit(), מפעילים את הפונקציה setupRecurringWork().
  2. מעבירים את האתחול של Timber מה-method‏ onCreate() ל-method‏ delayedInit().
private fun delayedInit() {
   applicationScope.launch {
       Timber.plant(Timber.DebugTree())
       setupRecurringWork()
   }
}
  1. במחלקה DevByteApplication, בסוף השיטה onCreate(), מוסיפים קריאה לשיטה delayedInit().
override fun onCreate() {
   super.onCreate()
   delayedInit()
}
  1. פותחים את החלונית Logcat בתחתית החלון של Android Studio. סינון לפי RefreshDataWorker.
  2. מריצים את האפליקציה. WorkManager מתזמן את העבודה החוזרת באופן מיידי.

    בחלונית Logcat, שימו לב להצהרות היומן שמראות שהבקשה לעבודה מתוזמנת, ואז מופעלת בהצלחה.
D/RefreshDataWorker: Work request for sync is run
I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]

היומן WM-WorkerWrapper מוצג מהספרייה WorkManager, ולכן אי אפשר לשנות את הודעת היומן הזו.

שלב 3: (אופציונלי) תזמון של WorkRequest למרווח מינימלי

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

  1. בקטע DevByteApplication class, בתוך השיטה setupRecurringWork(), מוסיפים הערה להגדרה הנוכחית של repeatingRequest. הוספת בקשת עבודה חדשה עם מרווח חזרה תקופתי של 15 דקות.
// val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
//        .build()
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
       .build()
  1. פותחים את החלונית Logcat ב-Android Studio ומסננים לפי RefreshDataWorker. כדי לנקות את היומנים הקודמים, לוחצים על הסמל Clear logcat .
  2. מריצים את האפליקציה, ו-WorkManager מתזמן את העבודה החוזרת באופן מיידי. בחלונית Logcat, אפשר לראות את היומנים – בקשת העבודה מופעלת פעם ב-15 דקות. צריך להמתין 15 דקות כדי לראות עוד קבוצה של יומני בקשות עבודה. אפשר להשאיר את האפליקציה פועלת או לסגור אותה. מנהל העבודה אמור להמשיך לפעול.

    שימו לב שהמרווח בין העלאות לפעמים קצר מ-15 דקות ולפעמים ארוך מ-15 דקות. (התזמון המדויק תלוי באופטימיזציות של הסוללה במערכת ההפעלה).
12:44:40 D/RefreshDataWorker: Work request for sync is run
12:44:40 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
12:59:24 D/RefreshDataWorker: Work request for sync is run
12:59:24 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:15:03 D/RefreshDataWorker: Work request for sync is run
13:15:03 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:29:22 D/RefreshDataWorker: Work request for sync is run
13:29:22 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:44:26 D/RefreshDataWorker: Work request for sync is run
13:44:26 I/WM-WorkerWrapper: Worker result SUCCESS for Work
 

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

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

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

כשמגדירים את WorkRequest, אפשר לציין אילוצים לגבי מתי Worker צריך לפעול. לדוגמה, יכול להיות שתרצו לציין שהעבודה תתבצע רק כשהמכשיר לא פעיל, או רק כשהמכשיר מחובר לחשמל ול-Wi-Fi. אפשר גם לציין מדיניות השהיה (backoff) לניסיון חוזר של עבודה. האילוצים הנתמכים הם שיטות ההגדרה ב-Constraints.Builder. מידע נוסף זמין במאמר בנושא הגדרת בקשות עבודה.

שלב 1: מוסיפים אובייקט Constraints ומגדירים אילוץ אחד

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

  1. בכיתה DevByteApplication, בתחילת setupRecurringWork(), מגדירים val מהסוג Constraints. משתמשים בשיטה Constraints.Builder().
val constraints = Constraints.Builder()

כדי לפתור את השגיאה, מייבאים את androidx.work.Constraints.

  1. משתמשים ב-method‏ setRequiredNetworkType() כדי להוסיף אילוץ מסוג רשת לאובייקט constraints. משתמשים ב-enum‏ UNMETERED כדי שבקשת העבודה תפעל רק כשהמכשיר מחובר לרשת ללא הגבלת נפח.
.setRequiredNetworkType(NetworkType.UNMETERED)
  1. משתמשים בשיטה build() כדי ליצור את האילוצים מהכלי ליצירת מבצעים.
val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .build()

עכשיו צריך להגדיר את אובייקט Constraints שנוצר זה עתה לבקשת העבודה.

  1. בכיתה DevByteApplication, בתוך השיטה setupRecurringWork(), מגדירים את האובייקט Constraints כבקשת העבודה התקופתית, repeatingRequest. כדי להגדיר את האילוצים, מוסיפים את ה-method‏ setConstraints() מעל הקריאה ל-method‏ build().
       val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
               .setConstraints(constraints)
               .build()

שלב 2: מריצים את האפליקציה ומסתכלים על היומנים

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

  1. כדי לבטל משימות שנקבעו מראש, צריך להסיר את האפליקציה מהמכשיר או מהאמולטור.
  2. פותחים את החלונית Logcat ב-Android Studio. בחלונית Logcat, מוחקים את היומנים הקודמים על ידי לחיצה על הסמל Clear logcat בצד ימין. סינון לפי work.
  3. משביתים את ה-Wi-Fi במכשיר או באמולטור, כדי לראות איך פועלות ההגבלות. הקוד הנוכחי מגדיר רק אילוץ אחד, שמציין שהבקשה צריכה לפעול רק ברשת ללא הגבלת נפח. מכיוון ש-Wi-Fi מושבת, המכשיר לא מחובר לרשת, בין אם היא מוגבלת או לא. לכן, המגבלה הזו לא תתקיים.
  4. מריצים את האפליקציה ומסתכלים על החלונית Logcat. המערכת WorkManager מתזמנת את המשימה ברקע באופן מיידי. המשימה לא מופעלת כי היא לא עומדת בדרישות של הגבלת הרשת.
11:31:44 D/DevByteApplication: Periodic Work request for sync is scheduled
  1. מפעילים את ה-Wi-Fi במכשיר או באמולטור וצופים בחלונית Logcat. עכשיו המשימה המתוזמנת ברקע מופעלת בערך כל 15 דקות, כל עוד מתקיימת מגבלת הרשת.
11:31:44 D/DevByteApplication: Periodic Work request for sync is scheduled
11:31:47 D/RefreshDataWorker: Work request for sync is run
11:31:47 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]
11:46:45 D/RefreshDataWorker: Work request for sync is run
11:46:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:03:05 D/RefreshDataWorker: Work request for sync is run
12:03:05 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:16:45 D/RefreshDataWorker: Work request for sync is run
12:16:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:31:45 D/RefreshDataWorker: Work request for sync is run
12:31:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:47:05 D/RefreshDataWorker: Work request for sync is run
12:47:05 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
13:01:45 D/RefreshDataWorker: Work request for sync is run
13:01:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]

שלב 3: הוספת עוד אילוצים

בשלב הזה מוסיפים את האילוצים הבאים ל-PeriodicWorkRequest:

  • הסוללה לא חלשה.
  • טעינת המכשיר.
  • מצב המתנה של המכשיר; זמין רק ברמת API‏ 23 (Android M) ומעלה.

מטמיעים את הפעולות הבאות במחלקה DevByteApplication.

  1. במחלקה DevByteApplication, בתוך השיטה setupRecurringWork(), מציינים שבקשת העבודה תפעל רק אם הסוללה לא חלשה. מוסיפים את האילוץ לפני הקריאה לשיטה build() ומשתמשים בשיטה setRequiresBatteryNotLow().
.setRequiresBatteryNotLow(true)
  1. מעדכנים את בקשת העבודה כך שהיא תפעל רק כשהמכשיר בטעינה. מוסיפים את האילוץ לפני הקריאה לשיטה build() ומשתמשים בשיטה setRequiresCharging().
.setRequiresCharging(true)
  1. מעדכנים את בקשת העבודה כך שהיא תפעל רק כשהמכשיר לא פעיל. מוסיפים את האילוץ לפני הקריאה לשיטה build() ומשתמשים בשיטה setRequiresDeviceIdle(). ההגבלה הזו מפעילה את בקשת העבודה רק כשהמשתמש לא משתמש במכשיר באופן פעיל. התכונה הזו זמינה רק ב-Android מגרסה 6.0 ‏ (Marshmallow) ואילך, לכן צריך להוסיף תנאי לגרסת SDK M ומעלה.
.apply {
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
       setRequiresDeviceIdle(true)
   }
}

הנה ההגדרה המלאה של אובייקט constraints.

val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .setRequiresBatteryNotLow(true)
       .setRequiresCharging(true)
       .apply {
           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
               setRequiresDeviceIdle(true)
           }
       }
       .build()
  1. בתוך השיטה setupRecurringWork(), משנים את מרווח הבקשות בחזרה לפעם ביום.
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
       .setConstraints(constraints)
       .build()

הנה ההטמעה המלאה של השיטה setupRecurringWork(), עם יומן שמאפשר לעקוב אחרי התזמון של בקשת העבודה התקופתית.

private fun setupRecurringWork() {

       val constraints = Constraints.Builder()
               .setRequiredNetworkType(NetworkType.UNMETERED)
               .setRequiresBatteryNotLow(true)
               .setRequiresCharging(true)
               .apply {
                   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                       setRequiresDeviceIdle(true)
                   }
               }
               .build()
       val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
               .setConstraints(constraints)
               .build()
       
       Timber.d("Periodic Work request for sync is scheduled")
       WorkManager.getInstance().enqueueUniquePeriodicWork(
               RefreshDataWorker.WORK_NAME,
               ExistingPeriodicWorkPolicy.KEEP,
               repeatingRequest)
   }
  1. כדי להסיר את בקשת העבודה שנקבעה מראש, צריך להסיר את ההתקנה של אפליקציית DevBytes מהמכשיר או מהאמולטור.
  2. מריצים את האפליקציה, והבקשה לעבודה מתוזמנת מיד על ידי WorkManager. בקשת העבודה מופעלת פעם ביום, כשכל האילוצים מתקיימים.
  3. בקשת העבודה הזו תפעל ברקע כל עוד האפליקציה מותקנת, גם אם האפליקציה לא פועלת. לכן, כדאי להסיר את האפליקציה מהטלפון.

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

פרויקט Android Studio: DevBytesWorkManager.

  • בעזרת WorkManager API אפשר לתזמן בקלות משימות אסינכרוניות שניתנות לדחייה ושחייבים להריץ בצורה מהימנה.
  • ברוב האפליקציות בעולם האמיתי יש צורך לבצע משימות ארוכות ברקע. כדי לתזמן משימה ברקע בצורה יעילה ואופטימלית, משתמשים ב-WorkManager.
  • המחלקות העיקריות בספריית WorkManager הן Worker,‏ WorkRequest ו-WorkManager.
  • המחלקות Worker מייצגות יחידת עבודה. כדי להטמיע את משימת הרקע, מרחיבים את המחלקה Worker ומבטלים את ה-method‏ doWork().
  • המחלקות WorkRequest מייצגות בקשה לביצוע יחידת עבודה. ‫WorkRequest הוא מחלקת הבסיס להגדרת פרמטרים לעבודה שמתזמנים ב-WorkManager.
  • יש שתי הטמעות קונקרטיות של המחלקה WorkRequest: ‏ OneTimeWorkRequest למשימות חד-פעמיות ו-PeriodicWorkRequest לבקשות עבודה תקופתיות.
  • כשמגדירים את WorkRequest, אפשר לציין Constraints כדי לקבוע מתי Worker צריך לפעול. האילוצים כוללים דברים כמו האם המכשיר מחובר לחשמל, האם המכשיר לא פעיל או האם הוא מחובר ל-Wi-Fi.
  • כדי להוסיף אילוצים ל-WorkRequest, משתמשים בשיטות ההגדרה שמפורטות במסמכי התיעוד של Constraints.Builder. לדוגמה, כדי לציין שהפונקציה WorkRequest לא תפעל אם הסוללה של המכשיר חלשה, משתמשים בשיטת ההגדרה setRequiresBatteryNotLow().
  • אחרי שמגדירים את WorkRequest, מעבירים את המשימה למערכת Android. כדי לעשות זאת, מתזמנים את המשימה באמצעות אחת מהשיטות של WorkManager enqueue.
  • הזמן המדויק שבו מתבצעת הפעולה Worker תלוי באילוצים שמוגדרים ב-WorkRequest ובאופטימיזציות של המערכת. ‫WorkManager נועד לספק את ההתנהגות הטובה ביותר האפשרית, בהתחשב בהגבלות האלה.

קורס ב-Udacity:

מסמכי תיעוד למפתחי Android:

אחר:

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

  • אם צריך, מקצים שיעורי בית.
  • להסביר לתלמידים איך להגיש מטלות.
  • בודקים את שיעורי הבית.

אנשי ההוראה יכולים להשתמש בהצעות האלה כמה שרוצים, ומומלץ להם להקצות כל שיעורי בית אחרים שהם חושבים שמתאימים.

אם אתם עובדים על ה-codelab הזה לבד, אתם יכולים להשתמש במשימות האלה כדי לבדוק את הידע שלכם.

שאלה 1

מהן ההטמעות הקונקרטיות של המחלקה WorkRequest?

OneTimeWorkPeriodicRequest

‫▢ OneTimeWorkRequest ו-PeriodicWorkRequest

‫▢ OneTimeWorkRequest ו-RecurringWorkRequest

‫▢ OneTimeOffWorkRequest ו-RecurringWorkRequest

שאלה 2

באיזו מהמחלקות הבאות משתמשת WorkManager כדי לתזמן את משימת הרקע ב-API מגרסה 23 ואילך?

‫▢ רק JobScheduler

‫▢ BroadcastReceiver ו-AlarmManager

‫▢ AlarmManager ו-JobScheduler

‫▢ Scheduler ו-BroadcastReceiver

שאלה 3

איזה API משמש אותך כדי להוסיף אילוצים ל-WorkRequest?

setConstraints()

addConstraints()

setConstraint()

addConstraintsToWorkRequest()

עוברים לשיעור הבא: 10.1 סגנונות ועיצובים

קישורים ל-codelabs אחרים בקורס הזה מופיעים בדף הנחיתה של ה-codelabs בנושא יסודות Android Kotlin.