ה-codelab הזה הוא חלק מהקורס Android Kotlin Fundamentals. כדי להפיק את המרב מהקורס הזה, מומלץ לעבוד על ה-codelabs לפי הסדר. כל ה-codelab של הקורס מפורטים בדף הנחיתה של ה-codelab בנושא יסודות Kotlin ל-Android.
מבוא
ב-codelab הקודם למדתם על מחזורי החיים של Activity ו-Fragment, ועל השיטות שמופעלות כשמצב מחזור החיים משתנה בפעילויות ובקטעים. ב-codelab הזה נסביר בפירוט על מחזור החיים של פעילות. בנוסף, תלמדו על ספריית מחזור החיים של Android Jetpack, שיכולה לעזור לכם לנהל אירועים במחזור החיים באמצעות קוד מאורגן יותר וקל יותר לתחזוקה.
מה שכדאי לדעת
- מהי פעילות ואיך יוצרים פעילות באפליקציה.
- היסודות של מחזורי החיים של
ActivityושלFragment, ופונקציות ה-callback שמופעלות כשפעילות עוברת בין מצבים. - איך להחליף את שיטות הקריאה החוזרת (callback) של מחזור החיים
onCreate()ו-onStop()כדי לבצע פעולות בזמנים שונים במחזור החיים של הפעילות או של הקטע.
מה תלמדו
- איך מגדירים, מתחילים ומפסיקים חלקים מהאפליקציה ב-callbacks של מחזור החיים.
- איך משתמשים בספריית מחזור החיים של Android כדי ליצור משקיף של מחזור החיים, וכך לנהל בקלות רבה יותר את מחזור החיים של הפעילות והקטע.
- איך כיבוי של תהליכים ב-Android משפיע על הנתונים באפליקציה, ואיך אפשר לשמור ולשחזר את הנתונים האלה באופן אוטומטי כש-Android סוגר את האפליקציה.
- איך סיבוב המכשיר ושינויים אחרים בהגדרות יוצרים שינויים במצבי מחזור החיים ומשפיעים על מצב האפליקציה.
מה עושים
- משנים את האפליקציה DessertClicker כך שתכלול פונקציית טיימר, ומתחילים ומפסיקים את הטיימר בזמנים שונים במחזור החיים של הפעילות.
- משנים את האפליקציה כך שתשתמש בספריית מחזור החיים של Android, וממירים את המחלקה
DessertTimerלמעקב אחר מחזור החיים. - מגדירים את ממשק הגישור של Android (
adb) ומשתמשים בו כדי לדמות את סגירת התהליך של האפליקציה ואת הקריאות החוזרות (callback) למחזור החיים שמתרחשות לאחר מכן. - כדאי להטמיע את השיטה
onSaveInstanceState()כדי לשמור נתונים של אפליקציות שאולי יאבדו אם האפליקציה תיסגר באופן בלתי צפוי. מוסיפים קוד כדי לשחזר את הנתונים האלה כשהאפליקציה מופעלת מחדש.
ב-Codelab הזה, מרחיבים את האפליקציה DessertClicker מה-Codelab הקודם. מוסיפים טיימר ברקע, ואז ממירים את האפליקציה לשימוש בספריית מחזור החיים של Android.

ב-codelab הקודם למדתם איך לעקוב אחר הפעילות ומחזורי החיים של קטעים באמצעות החלפה של קריאות חוזרות שונות למחזור החיים, ורישום ביומן מתי המערכת מפעילה את הקריאות החוזרות האלה. במשימה הזו תכירו דוגמה מורכבת יותר לניהול משימות מחזור חיים באפליקציית DessertClicker. תשתמשו בטיימר שמדפיס הצהרת יומן כל שנייה, עם ספירת מספר השניות שהוא פועל.
שלב 1: הגדרת DessertTimer
- פותחים את אפליקציית DessertClicker מה-codelab האחרון. (אם האפליקציה לא מותקנת במכשיר, אפשר להוריד את DessertClickerLogs כאן).
- בתצוגה Project, מרחיבים את java > com.example.android.dessertclicker ופותחים את
DessertTimer.kt. שימו לב שכרגע כל הקוד מופיע כהערה, ולכן הוא לא מופעל כחלק מהאפליקציה. - בוחרים את כל הקוד בחלון העריכה. בוחרים באפשרות Code > Comment with Line Comment (קוד > הוספת הערה בשורה) או מקישים על
Control+/(Command+/ב-Mac). הפקודה הזו מבטלת את ההערה של כל הקוד בקובץ. (יכול להיות שב-Android Studio יוצגו שגיאות לא פתורות של הפניה עד שתבנו מחדש את האפליקציה). - שימו לב שהכיתה
DessertTimerכוללת אתstartTimer()ואתstopTimer(), שמתחילים ומפסיקים את הטיימר. כשהפונקציהstartTimer()פועלת, הטיימר מדפיס הודעת יומן בכל שנייה, עם הספירה הכוללת של השניות שהטיימר פעל. השיטהstopTimer(), בתורה, מפסיקה את הטיימר ואת הצהרות היומן.
- פתיחת
MainActivity.kt. בחלק העליון של הכיתה, ממש מתחת למשתנהdessertsSold, מוסיפים משתנה לטיימר:
private lateinit var dessertTimer : DessertTimer;- גוללים למטה אל
onCreate()ויוצרים אובייקט חדש שלDessertTimer, מיד אחרי הקריאה אלsetOnClickListener():
dessertTimer = DessertTimer()
עכשיו שיש לך אובייקט של טיימר לקינוח, כדאי לחשוב איפה צריך להתחיל ולהפסיק את הטיימר כדי שהוא יפעל רק כשהפעילות מוצגת על המסך. בשלבים הבאים נסביר על כמה אפשרויות.
שלב 2: הפעלה ועצירה של הטיימר
השיטה onStart() מופעלת ממש לפני שהפעילות הופכת לגלויות. השיטה onStop() נקראת אחרי שהפעילות מפסיקה להיות גלויה. נראה שהקריאות החוזרות האלה מתאימות לשימוש כשרוצים להתחיל ולהפסיק את הטיימר.
- בשיעור
MainActivity, מפעילים את הטיימר בקריאה החוזרתonStart():
override fun onStart() {
super.onStart()
dessertTimer.startTimer()
Timber.i("onStart called")
}- עצירת הטיימר ב-
onStop():
override fun onStop() {
super.onStop()
dessertTimer.stopTimer()
Timber.i("onStop Called")
}- קומפילציה והפעלה של האפליקציה. ב-Android Studio, לוחצים על החלונית Logcat. בתיבת החיפוש של Logcat, מזינים
dessertclickerכדי לסנן לפי המחלקותMainActivityו-DessertTimer. שימו לב: ברגע שהאפליקציה מתחילה לפעול, הטיימר מתחיל לפעול גם כן.
- לוחצים על הלחצן הקודם ורואים שהטיימר נעצר שוב. הטיימר מפסיק כי הפעילות והטיימר ששולט בה נהרסו.
- משתמשים במסך האפליקציות האחרונות כדי לחזור לאפליקציה. שימו לב שב-Logcat הטיימר מופעל מחדש מ-0.
- לוחצים על הלחצן שיתוף. אפשר לראות ב-Logcat שהטיימר עדיין פועל.

- לוחצים על הלחצן דף הבית. שימו לב שהטיימר מפסיק לפעול ב-Logcat.
- משתמשים במסך האפליקציות האחרונות כדי לחזור לאפליקציה. אפשר לראות ב-Logcat שהטיימר מתחיל שוב מהמקום שבו הוא הפסיק.
- ב-
MainActivity, בשיטהonStop(), מוסיפים הערה להפעלתstopTimer(). הוספת הערה ל-stopTimer()מדגימה את המקרה שבו מתחילים פעולה ב-onStart(), אבל שוכחים להפסיק אותה ב-onStop(). - מהדרים ומריצים את האפליקציה, ואז לוחצים על הכפתור הראשי אחרי שהטיימר מתחיל. גם כשהאפליקציה פועלת ברקע, הטיימר פועל ומשתמש ברציפות במשאבי המערכת. אם הטיימר ממשיך לפעול, זה גורם לדליפת זיכרון באפליקציה, וכנראה שזה לא מה שאתם רוצים.
הדפוס הכללי הוא שכאשר מגדירים או מתחילים משהו בפונקציית קריאה חוזרת, מפסיקים או מסירים את אותו הדבר בפונקציית הקריאה החוזרת המתאימה. כך תוכלו להימנע ממצב שבו משהו פועל כשאין בו יותר צורך.
- מבטלים את ההערה בשורה בקוד
onStop()שבה עוצרים את הטיימר. - גוזרים ומדביקים את הקריאה
startTimer()מ-onStart()אלonCreate(). השינוי הזה מדגים את המקרה שבו מאתחלים ומתחילים משאב ב-onCreate(), במקום להשתמש ב-onCreate()כדי לאתחל אותו וב-onStart()כדי להתחיל אותו. - קומפלו את האפליקציה והפעילו אותה. הטיימר יתחיל לפעול, כמו שציפיתם.
- לוחצים על 'הביתה' כדי לעצור את האפליקציה. הטיימר מפסיק לפעול, כמו שציפיתם.
- משתמשים במסך האפליקציות האחרונות כדי לחזור לאפליקציה. שימו לב שהטיימר לא מתחיל שוב במקרה הזה, כי הפונקציה
onCreate()מופעלת רק כשהאפליקציה מתחילה – היא לא מופעלת כשאפליקציה חוזרת לחזית.
נקודות חשובות שכדאי לזכור:
- כשמגדירים משאב בפונקציית callback של מחזור החיים, צריך גם להסיר את המשאב.
- מבצעים הגדרה וביטול הגדרה בשיטות המתאימות.
- אם הגדרתם משהו ב-
onStart(), אתם יכולים להפסיק או לבטל את ההגדרה ב-onStop().
באפליקציית DessertClicker, קל לראות שאם התחלתם את הטיימר ב-onStart(), אתם צריכים לעצור אותו ב-onStop(). יש רק טיימר אחד, כך שקל לזכור איך עוצרים אותו.
באפליקציית Android מורכבת יותר, יכול להיות שתגדירו הרבה דברים ב-onStart() או ב-onCreate(), ואז תבטלו את כולם ב-onStop() או ב-onDestroy(). לדוגמה, יכול להיות שיש לכם אנימציות, מוזיקה, חיישנים או טיימרים שאתם צריכים להגדיר ולבטל, ולהתחיל ולהפסיק. אם שוכחים אחת מהן, זה מוביל לבאגים ולבעיות.
ספריית מחזור החיים, שהיא חלק מ-Android Jetpack, מפשטת את המשימה הזו. הספרייה שימושית במיוחד במקרים שבהם צריך לעקוב אחרי הרבה חלקים משתנים, שחלקם נמצאים בשלבים שונים במחזור החיים. הספרייה משנה את אופן הפעולה של מחזורי החיים: בדרך כלל הפעילות או הקטע מציינים לרכיב (כמו DessertTimer) מה לעשות כשמתרחש קריאה חוזרת (callback) של מחזור חיים. אבל כשמשתמשים בספריית מחזור החיים, הרכיב עצמו עוקב אחרי שינויים במחזור החיים, ואז מבצע את הפעולות הנדרשות כשהשינויים האלה קורים.
ספריית מחזור החיים מורכבת משלושה חלקים עיקריים:
- בעלים של מחזור חיים, שהם הרכיבים שיש להם מחזור חיים (ולכן הם הבעלים שלו).
Activityו-Fragmentהם הבעלים של מחזור החיים. בעלי מחזור החיים מטמיעים את הממשקLifecycleOwner. - המחלקות
Lifecycle, שמכילות את המצב בפועל של בעלים של מחזור חיים ומפעילות אירועים כשמתרחשים שינויים במחזור החיים. - אובייקטים למעקב אחרי מחזור החיים, שעוקבים אחרי המצב של מחזור החיים ומבצעים משימות כשיש שינוי במחזור החיים. האובייקטים של מחזור החיים מטמיעים את הממשק
LifecycleObserver.
במשימה הזו תמירו את האפליקציה DessertClicker לשימוש בספריית מחזור החיים של Android, ותלמדו איך הספרייה מקלה על ניהול מחזורי החיים של הפעילות והקטעים ב-Android.
שלב 1: הופכים את DessertTimer ל-LifecycleObserver
חלק חשוב בספריית מחזור החיים הוא המושג lifecycle observation. התכונה Observation מאפשרת למחלקות (כמו DessertTimer) לדעת על הפעילות או על מחזור החיים של קטע הקוד, ולהתחיל או להפסיק את הפעולה שלהן בתגובה לשינויים במצבי מחזור החיים האלה. באמצעות LifecycleObserver, אפשר להסיר את האחריות להפעלה ולהפסקה של אובייקטים משיטות הפעילות והקטעים.
- פותחים את הכיתה
DesertTimer.kt. - משנים את חתימת המחלקה של המחלקה
DessertTimerכך שתיראה כך:
class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {הגדרת המחלקה החדשה הזו עושה שני דברים:
- הבנאי מקבל אובייקט
Lifecycle, שהוא מחזור החיים שהטיימר עוקב אחריו. - הגדרת המחלקה מטמיעה את הממשק
LifecycleObserver.
- מתחת למשתנה
runnable, מוסיפים בלוקinitלהגדרת המחלקה. בבלוקinit, משתמשים בשיטהaddObserver()כדי לקשר את אובייקט מחזור החיים שהועבר מהבעלים (הפעילות) למחלקה הזו (המשקיף).
init {
lifecycle.addObserver(this)
}- מוסיפים הערה לאירוע
startTimer()עם האירוע@OnLifecycleEvent annotation, ומשתמשים באירועON_STARTבמחזור החיים. כל האירועים במחזור החיים שהאובייקט LifecycleObserver יכול לעקוב אחריהם נמצאים במחלקהLifecycle.Event.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {- מבצעים את אותה פעולה לגבי
stopTimer(), באמצעות האירועON_STOP:
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()שלב 2: שינוי של MainActivity
המחלקות MainActivity שלך כבר מוגדרות כבעלים של מחזור החיים באמצעות ירושה, כי מחלקת העל FragmentActivity מטמיעה את LifecycleOwner. לכן, לא צריך לעשות שום דבר כדי שהפעילות תהיה מודעת למחזור החיים שלה. כל מה שצריך לעשות הוא להעביר את אובייקט מחזור החיים של הפעילות אל ה-constructor של DessertTimer.
- פתיחת
MainActivity. בשיטהonCreate(), משנים את ההגדרה הראשונית שלDessertTimerכך שתכלול אתthis.lifecycle:
dessertTimer = DessertTimer(this.lifecycle)המאפיין lifecycle של הפעילות מכיל את האובייקט Lifecycle שבבעלות הפעילות הזו.
- הסרת השיחה למספר
startTimer()במיקוםonCreate(), והשיחה למספרstopTimer()במיקוםonStop(). לא צריך יותר להגיד ל-DessertTimerמה לעשות בפעילות, כי עכשיוDessertTimerעוקב אחרי מחזור החיים בעצמו ומקבל התראה אוטומטית כשמצב מחזור החיים משתנה. כל מה שצריך לעשות עכשיו בפונקציות הקריאה החוזרת האלה הוא לרשום הודעה ביומן. - מהדרים ומריצים את האפליקציה, ופותחים את Logcat. שימו לב שהטיימר התחיל לפעול, כמו שציפינו.

- לוחצים על הלחצן הראשי כדי להעביר את האפליקציה לרקע. שימו לב שהטיימר הפסיק לפעול, כמו שציפינו.
מה קורה לאפליקציה ולנתונים שלה אם מערכת Android סוגרת את האפליקציה כשהיא פועלת ברקע? חשוב להבין את מקרה הקצה המורכב הזה.
כשהאפליקציה עוברת לרקע, היא לא נמחקת, אלא רק נעצרת ומחכה שהמשתמש יחזור אליה. אבל אחד מהדברים שהכי חשובים למערכת ההפעלה Android הוא שהפעילות שמתבצעת בחזית תפעל בצורה חלקה. לדוגמה, אם משתמש משתמש באפליקציית GPS כדי לעלות על אוטובוס, חשוב להציג את אפליקציית ה-GPS במהירות ולהמשיך להציג את ההוראות. פחות חשוב שהאפליקציה DessertClicker, שהמשתמש לא בדק אותה כמה ימים, תפעל בצורה חלקה ברקע.
מערכת Android מווסתת את האפליקציות שפועלות ברקע כדי שהאפליקציה שפועלת בחזית תוכל לפעול ללא בעיות. לדוגמה, מערכת Android מגבילה את כמות העיבוד שאפליקציות שפועלות ברקע יכולות לבצע.
לפעמים מערכת Android אפילו סוגרת תהליך שלם של אפליקציה, שכולל כל פעילות שקשורה לאפליקציה. מערכת Android מבצעת סגירה כזו כשהמערכת עמוסה מדי ויש סכנה של פיגור ויזואלי, ולכן לא מופעלים בשלב הזה קריאות חוזרות או קוד נוספים. התהליך של האפליקציה פשוט מושבת ברקע, בלי להציג הודעות כלשהן. אבל המשתמש לא רואה שהאפליקציה נסגרה. כשמשתמש חוזר לאפליקציה שמערכת ההפעלה Android סגרה, מערכת Android מפעילה מחדש את האפליקציה.
במשימה הזו תדמו כיבוי של תהליך Android ותבדקו מה קורה לאפליקציה כשמפעילים אותה מחדש.
שלב 1: שימוש ב-adb כדי לדמות סגירה של תהליך
ממשק הגישור של Android (adb) הוא כלי שורת פקודה שמאפשר לשלוח הוראות לאמולטורים ולמכשירים שמחוברים למחשב. בשלב הזה, משתמשים ב-adb כדי לסגור את התהליך של האפליקציה ולראות מה קורה כשמערכת Android סוגרת את האפליקציה.
- מהדרים את האפליקציה ומריצים אותה. לוחצים על הקאפקייק כמה פעמים.
- לוחצים על הכפתור הראשי כדי להעביר את האפליקציה לרקע. האפליקציה שלכם מושבתת עכשיו, והיא עלולה להיסגר אם מערכת Android תזדקק למשאבים שהאפליקציה משתמשת בהם.
- ב-Android Studio, לוחצים על הכרטיסייה Terminal כדי לפתוח את הטרמינל של שורת הפקודה.

- מקלידים
adbומקישים על Return.
אם מוצג פלט רב שמתחיל ב-Android Debug Bridge version X.XX.Xומסתיים ב-tags to be used by logcat (see logcat —help), הכול בסדר. אם במקום זאת רואיםadb: command not found, צריך לוודא שהפקודהadbזמינה בנתיב ההפעלה. הוראות מפורטות מופיעות בקטע 'הוספת adb לנתיב ההפעלה' ב פרק בנושא כלי עזר. - מעתיקים את התגובה הזו ומדביקים אותה בשורת הפקודה, ואז מקישים על Return:
adb shell am kill com.example.android.dessertclickerהפקודה הזו אומרת לכל המכשירים או האמולטורים המחוברים להפסיק את התהליך עם שם החבילה dessertclicker, אבל רק אם האפליקציה פועלת ברקע. מכיוון שהאפליקציה הייתה ברקע, לא מוצג דבר במסך המכשיר או האמולטור שמציין שהתהליך הופסק. ב-Android Studio, לוחצים על הכרטיסייה Run כדי לראות את ההודעה 'Application terminated'. לוחצים על הכרטיסייה Logcat כדי לראות שהקריאה החוזרת onDestroy() מעולם לא הופעלה – הפעילות פשוט הסתיימה.
- כדי לחזור לאפליקציה, משתמשים במסך האפליקציות האחרונות. האפליקציה מופיעה באפליקציות האחרונות גם אם היא הועברה לרקע וגם אם היא הופסקה לגמרי. כשמשתמשים במסך האפליקציות האחרונות כדי לחזור לאפליקציה, הפעילות מתחילה מחדש. הפעילות עוברת את כל מערך הקריאות החוזרות של מחזור החיים של ההפעלה, כולל
onCreate(). - שימו לב שכשהאפליקציה מופעלת מחדש, היא מאפסת את ה'ניקוד' (גם את מספר הקינוחים שנמכרו וגם את הסכום הכולל בדולרים) לערכי ברירת המחדל (0). אם מערכת Android סגרה את האפליקציה, למה היא לא שמרה את המצב שלה?
כשמערכת ההפעלה מפעילה מחדש את האפליקציה, מערכת Android מנסה לאפס את האפליקציה למצב שהיה לה לפני כן. מערכת Android לוקחת את המצב של חלק מהתצוגות שלכם ושומרת אותו בחבילה בכל פעם שאתם עוברים מהפעילות. לדוגמה, הטקסט ב-EditText (כל עוד מוגדר לו מזהה בפריסה) ורשימת הפעילויות הקודמות שלכם נשמרים אוטומטית.
עם זאת, לפעמים מערכת ההפעלה Android לא יודעת על כל הנתונים שלכם. לדוגמה, אם יש לכם משתנה מותאם אישית כמוrevenueבאפליקציית DessertClicker, מערכת ההפעלה Android לא יודעת על הנתונים האלה או על החשיבות שלהם לפעילות שלכם. צריך להוסיף את הנתונים האלה לחבילה באופן ידני.
שלב 2: שימוש ב-onSaveInstanceState() כדי לשמור נתונים של חבילת bundle
השיטה onSaveInstanceState() היא הקריאה החוזרת שבה משתמשים כדי לשמור נתונים שאולי תצטרכו אם מערכת ההפעלה Android תסגור את האפליקציה. בתרשים של הקריאה החוזרת למחזור החיים, הקריאה onSaveInstanceState() מתבצעת אחרי שהפעילות נעצרה. הפונקציה הזו מופעלת בכל פעם שהאפליקציה עוברת לרקע.

אפשר לחשוב על הקריאה onSaveInstanceState() כעל אמצעי בטיחות. היא מאפשרת לכם לשמור כמות קטנה של מידע בחבילה כשהפעילות יוצאת מהחזית. המערכת שומרת את הנתונים האלה עכשיו כי אם היא תחכה עד שהאפליקציה תיסגר, יכול להיות שמערכת ההפעלה תהיה בלחץ משאבים. שמירת הנתונים בכל פעם מבטיחה שנתוני העדכון בחבילה יהיו זמינים לשחזור, אם יהיה צורך בכך.
- ב-
MainActivity, מבטלים את ההגדרה של הקריאה החוזרתonSaveInstanceState()ומוסיפים הצהרה של יומןTimber.
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Timber.i("onSaveInstanceState Called")
}- קומפילציה והרצה של האפליקציה, ולחיצה על הלחצן Home כדי להעביר אותה לרקע. שימו לב שהקריאה החוזרת
onSaveInstanceState()מתרחשת מיד אחריonPause()ו-onStop():
- בחלק העליון של הקובץ, ממש לפני הגדרת המחלקה, מוסיפים את הקבועים האלה:
const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"
const val KEY_TIMER_SECONDS = "timer_seconds_key"תשתמשו במפתחות האלה גם לשמירה וגם לאחזור של נתונים מחבילת מצב המופע.
- גוללים למטה אל
onSaveInstanceState()ושימו לב לפרמטרoutState, שהוא מסוגBundle.
חבילה היא אוסף של צמדי מפתח/ערך, שבהם המפתחות הם תמיד מחרוזות. אפשר להוסיף לחבילה ערכים פרימיטיביים, כמו ערכים שלintושלboolean.
מכיוון שהמערכת שומרת את החבילה הזו ב-RAM, מומלץ לשמור על גודל קטן של הנתונים בחבילה. גם הגודל של החבילה הזו מוגבל, אבל הגודל משתנה ממכשיר למכשיר. בדרך כלל, כדאי לאחסן הרבה פחות מ-100,000, אחרת יש סיכון שהאפליקציה תקרוס עם השגיאהTransactionTooLargeException. - ב-
onSaveInstanceState(), מכניסים את הערךrevenue(מספר שלם) לחבילה באמצעות השיטהputInt():
outState.putInt(KEY_REVENUE, revenue)השיטה putInt() (ושיטות דומות מהמחלקה Bundle כמו putFloat() ו-putString()) מקבלת שני ארגומנטים: מחרוזת למפתח (הקבוע KEY_REVENUE) והערך בפועל שרוצים לשמור.
- חוזרים על אותו התהליך עם מספר הקינוחים שנמכרו והסטטוס של הטיימר:
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)
outState.putInt(KEY_TIMER_SECONDS, dessertTimer.secondsCount)שלב 3: שימוש ב-onCreate() כדי לשחזר נתונים של חבילה
- גוללים למעלה אל
onCreate()ובודקים את חתימת השיטה:
override fun onCreate(savedInstanceState: Bundle) {שימו לב ש-onCreate() מקבל Bundle בכל פעם שמפעילים אותו. אם הפעילות שלכם מופעלת מחדש בגלל סגירה של תהליך, החבילה ששמרתם מועברת אל onCreate(). אם הפעילות שלכם התחילה מחדש, החבילה הזו ב-onCreate() היא null. לכן, אם החבילה לא null, אתם יודעים שאתם 'יוצרים מחדש' את הפעילות מנקודה מוכרת קודמת.
- מוסיפים את הקוד הזה ל-
onCreate(), אחרי ההגדרה שלDessertTimer:
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}הבדיקה של null קובעת אם יש נתונים בחבילה או אם החבילה היא null. כך אפשר לדעת אם האפליקציה הופעלה מחדש או נוצרה מחדש אחרי כיבוי. הבדיקה הזו היא תבנית נפוצה לשחזור נתונים מהחבילה.
שימו לב שהמפתח שבו השתמשתם כאן (KEY_REVENUE) זהה למפתח שבו השתמשתם בשדה putInt(). כדי לוודא שמשתמשים באותו מפתח בכל פעם, מומלץ להגדיר את המפתחות האלה כקבועים. משתמשים ב-getInt() כדי להוציא נתונים מהחבילה, בדיוק כמו שמשתמשים ב-putInt() כדי להכניס נתונים לחבילה. השיטה getInt() מקבלת שני ארגומנטים:
- מחרוזת שמשמשת כמפתח, לדוגמה
"key_revenue"עבור ערך ההכנסה. - ערך ברירת מחדל למקרה שלא קיים ערך למפתח הזה בחבילה.
המספר השלם שמתקבל מהחבילה מוקצה למשתנה revenue, וממשק המשתמש ישתמש בערך הזה.
- מוסיפים את השיטות
getInt()כדי לשחזר את מספר הקינוחים שנמכרו ואת הערך של הטיימר:
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
dessertTimer.secondsCount =
savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
}- קומפילציה והרצה של האפליקציה. לוחצים על הקאפקייק לפחות חמש פעמים עד שהוא הופך לדונאט. לוחצים על 'דף הבית' כדי להעביר את האפליקציה לרקע.
- בכרטיסייה Terminal ב-Android Studio, מריצים את הפקודה
adbכדי להפסיק את התהליך של האפליקציה.
adb shell am kill com.example.android.dessertclicker- משתמשים במסך האפליקציות האחרונות כדי לחזור לאפליקציה. הפעם האפליקציה חוזרת עם ההכנסה הנכונה ועם ערכי הקינוחים שנמכרו מהחבילה. אבל שימו לב גם שהקינוח חזר להיות קאפקייק. יש עוד דבר אחד שצריך לעשות כדי לוודא שהאפליקציה תחזור ממצב כיבוי בדיוק למצב שבו היא הייתה לפני הכיבוי.
- ב-
MainActivity, בודקים את השיטהshowCurrentDessert(). שימו לב שהשיטה הזו קובעת איזו תמונה של קינוח תוצג בפעילות על סמך מספר הקינוחים שנמכרו כרגע ורשימת הקינוחים במשתנהallDesserts.
for (dessert in allDesserts) {
if (dessertsSold >= dessert.startProductionAmount) {
newDessert = dessert
}
else break
}השיטה הזו מתבססת על מספר הקינוחים שנמכרו כדי לבחור את התמונה המתאימה. לכן, לא צריך לעשות שום דבר כדי לאחסן הפניה לתמונה בחבילה ב-onSaveInstanceState(). במארז הזה, אתם כבר מאחסנים את מספר הקינוחים שנמכרו.
- ב-
onCreate(), בבלוק שמשחזר את המצב מהחבילה, קוראים ל-showCurrentDessert():
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
dessertTimer.secondsCount =
savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
showCurrentDessert()
}- קומפילציה והרצה של האפליקציה, והעברה שלה לרקע. אפשר להשתמש ב
adbכדי להפסיק את התהליך. משתמשים במסך האפליקציות האחרונות כדי לחזור לאפליקציה. שימו לב שעכשיו שני הערכים של הקינוחים, סך ההכנסות ותמונת הקינוח משוחזרים בצורה נכונה.
יש עוד מקרה מיוחד אחד בניהול מחזור החיים של הפעילות והקטעים שחשוב להבין: איך שינויים בהגדרות משפיעים על מחזור החיים של הפעילויות והקטעים.
שינוי בהגדרות מתרחש כשמצב המכשיר משתנה באופן קיצוני כל כך, שהדרך הקלה ביותר למערכת לפתור את השינוי היא לכבות את הפעילות ולבנות אותה מחדש. לדוגמה, אם המשתמש משנה את שפת המכשיר, יכול להיות שיהיה צורך לשנות את הפריסה כולה כדי להתאים לכיווני טקסט שונים. אם המשתמש מחבר את המכשיר למעמד או מוסיף מקלדת פיזית, יכול להיות שפריסת האפליקציה תצטרך להתאים את עצמה לגודל או לפריסה שונים של המסך. אם הכיוון של המכשיר משתנה – אם מסובבים את המכשיר לאורך או לרוחב או להיפך – יכול להיות שיהיה צורך לשנות את הפריסה כדי שתתאים לכיוון החדש.
שלב 1: בדיקת סיבוב המכשיר והקריאות החוזרות (callback) של מחזור החיים
- מהדרים ומריצים את האפליקציה, ופותחים את Logcat.
- מסובבים את המכשיר או את האמולטור לפריסה לרוחב. אפשר לסובב את האמולטור ימינה או שמאלה באמצעות לחצני הסיבוב, או באמצעות המקשים
Controlומקשי החיצים (Commandומקשי החיצים ב-Mac).
- בודקים את הפלט ב-Logcat. סינון הפלט לפי
MainActivity.
שימו לב: כשמסובבים את המסך במכשיר או באמולטור, המערכת קוראת לכל הפונקציות של מחזור החיים כדי להפסיק את הפעילות. לאחר מכן, כשהפעילות נוצרת מחדש, המערכת קוראת לכל הפונקציות של מחזור החיים כדי להתחיל את הפעילות. - ב-
MainActivity, מוסיפים הערה לכל השיטהonSaveInstanceState(). - קומפילציה והפעלה של האפליקציה שוב. לוחצים כמה פעמים על הקאפקייק ומסובבים את המכשיר או את האמולטור. בפעם הזו, כשהמכשיר מסתובב והפעילות מושבתת ונוצרת מחדש, הפעילות מתחילה עם ערכי ברירת מחדל.
כשמתרחש שינוי בהגדרות, מערכת Android משתמשת באותו חבילת מצב מופע שלמדתם עליה במשימה הקודמת כדי לשמור ולשחזר את מצב האפליקציה. כמו בהשבתת תהליך, משתמשים ב-onSaveInstanceState()כדי להכניס את נתוני האפליקציה לחבילה. לאחר מכן משחזרים את הנתונים ב-onCreate(), כדי למנוע אובדן של נתוני מצב הפעילות אם המכשיר מסובב. - ב-
MainActivity, מבטלים את ההערה של שיטתonSaveInstanceState(), מריצים את האפליקציה, לוחצים על הקאפקייק ומסובבים את האפליקציה או את המכשיר. שימו לב שהפעם נתוני הקינוח נשמרים במהלך מחזור הפעילות.
פרויקט Android Studio: DessertClickerFinal
טיפים לגבי מחזור החיים
- אם מגדירים או מתחילים משהו בפונקציית קריאה חוזרת של מחזור החיים, צריך להפסיק או להסיר את אותו דבר בפונקציית הקריאה החוזרת המתאימה. כשמפסיקים את הפעולה, מוודאים שהיא לא תמשיך לפעול כשאין בה יותר צורך. לדוגמה, אם הגדרתם טיימר ב-
onStart(), תצטרכו להשהות או לעצור אותו ב-onStop(). - משתמשים ב-
onCreate()רק כדי לאתחל את החלקים באפליקציה שמופעלים פעם אחת, כשהאפליקציה מתחילה לפעול. משתמשים ב-onStart()כדי להפעיל את החלקים באפליקציה שפועלים גם כשהאפליקציה מתחילה לפעול וגם בכל פעם שהאפליקציה חוזרת לחזית.
ספריית מחזור החיים
- אפשר להשתמש בספריית מחזור החיים של Android כדי להעביר את השליטה במחזור החיים מהפעילות או מהקטע לרכיב בפועל שצריך להיות מודע למחזור החיים.
- בעלים של מחזור חיים הם רכיבים שיש להם מחזור חיים (ולכן הם הבעלים שלו), כולל
Activityו-Fragment. בעלי מחזור החיים מטמיעים את הממשקLifecycleOwner. - אובייקטים למעקב של מחזור החיים עוקבים אחרי מצב מחזור החיים הנוכחי ומבצעים משימות כשיש שינוי במחזור החיים. האובייקטים של מחזור החיים מטמיעים את הממשק
LifecycleObserver. - אובייקטים מסוג
Lifecycleמכילים את מצבי מחזור החיים בפועל, והם מפעילים אירועים כשמחזור החיים משתנה.
כדי ליצור מחלקה עם מודעות למחזור החיים:
- מטמיעים את הממשק
LifecycleObserverבמחלקות שצריכות להיות מודעות למחזור החיים. - מאתחלים מחלקה של צופה במחזור החיים עם אובייקט מחזור החיים מהפעילות או מהקטע.
- במחלקת הצופה במחזור החיים, מוסיפים הערות לשיטות שמודעות למחזור החיים עם השינוי במצב מחזור החיים שמעניין אותן.
לדוגמה, ההערה@OnLifecycleEvent(Lifecycle.Event.ON_START)מציינת שהשיטה עוקבת אחרי אירוע מחזור החייםonStart.
השבתת תהליכים ושמירת מצב הפעילות
- מערכת Android מווסתת את האפליקציות שפועלות ברקע כדי שהאפליקציה שפועלת בחזית תוכל לפעול ללא בעיות. התקנה הזו כוללת הגבלה של כמות העיבוד שאפליקציות יכולות לבצע ברקע, ולפעמים אפילו השבתה של כל תהליך האפליקציה.
- המשתמשים לא יכולים לדעת אם המערכת סגרה אפליקציה ברקע. האפליקציה עדיין מופיעה במסך האפליקציות האחרונות, והיא אמורה להפעיל מחדש באותו מצב שבו המשתמש עזב אותה.
- ממשק הגישור של Android (
adb) הוא כלי שורת פקודה שמאפשר לשלוח הוראות לאמולטורים ולמכשירים שמחוברים למחשב. אתם יכולים להשתמש ב-adbכדי לדמות סגירה של תהליך באפליקציה. - כשמערכת Android משביתה את תהליך האפליקציה, לא מתבצעת קריאה לשיטת מחזור החיים
onDestroy(). האפליקציה פשוט מפסיקה לפעול.
שמירה של פעילות ושל מצב פרגמנט
- כשהאפליקציה עוברת לרקע, מיד אחרי הקריאה ל-
onStop(), נתוני האפליקציה נשמרים בחבילה. חלק מנתוני האפליקציות, כמו התוכן שלEditText, נשמרים באופן אוטומטי. - החבילה היא מופע של
Bundle, שהוא אוסף של מפתחות וערכים. המפתחות הם תמיד מחרוזות. - אפשר להשתמש ב-callback
onSaveInstanceState()כדי לשמור בחבילה נתונים אחרים שרוצים לשמור, גם אם האפליקציה נסגרה אוטומטית. כדי להוסיף נתונים לחבילה, משתמשים בשיטות של החבילה שמתחילות ב-put, כמוputInt(). - אפשר להוציא את הנתונים מהחבילה באמצעות השיטה
onRestoreInstanceState(), או בדרך כלל באמצעותonCreate(). לשיטהonCreate()יש פרמטרsavedInstanceStateשמכיל את החבילה. - אם המשתנה
savedInstanceStateמכיל את הערךnull, הפעילות התחילה ללא חבילת מצב ואין נתוני מצב לאחזור. - כדי לאחזר נתונים מהחבילה באמצעות מפתח, משתמשים בשיטות
Bundleשמתחילות ב-get, כמוgetInt().
שינויים בהגדרות
- שינוי בהגדרות מתרחש כשמצב המכשיר משתנה באופן קיצוני כל כך, שהדרך הקלה ביותר למערכת לפתור את השינוי היא לכבות את הפעילות ולבנות אותה מחדש.
- הדוגמה הנפוצה ביותר לשינוי בהגדרות היא כשהמשתמש מסובב את המכשיר ממצב לאורך למצב לרוחב, או ממצב לרוחב למצב לאורך. שינוי בהגדרות יכול לקרות גם כשמשנים את שפת המכשיר או כשמחברים מקלדת פיזית.
- כשמתרחש שינוי בהגדרה, מערכת Android מפעילה את כל קריאות החזרה (callback) של סגירת מחזור החיים של הפעילות. לאחר מכן, מערכת Android מפעילה מחדש את הפעילות מאפס, ומריצה את כל קריאות החזרה (callback) של הפעלה במחזור החיים.
- כשמערכת Android סוגרת אפליקציה בגלל שינוי בהגדרות, היא מפעילה מחדש את הפעילות עם חבילת המצב שזמינה ל-
onCreate(). - בדומה לסגירת התהליך, צריך לשמור את מצב האפליקציה בחבילה ב-
onSaveInstanceState().
קורס ב-Udacity:
מסמכי תיעוד למפתחי Android:
- פעילויות (מדריך API)
-
Activity(הפניית API) - הסבר על מחזור החיים של הפעילות
- טיפול במחזורי חיים באמצעות רכיבים שמודעים למחזור החיים
LifecycleOwnerLifecycleLifecycleObserveronSaveInstanceState()- טיפול בשינויים בהגדרות
- שמירת מצבי ממשק משתמש
אחר:
- Timber (GitHub)
בקטע הזה מפורטות אפשרויות למשימות ביתיות לתלמידים שעובדים על ה-Codelab הזה כחלק מקורס בהנחיית מדריך. המורה צריך:
- אם צריך, מקצים שיעורי בית.
- להסביר לתלמידים איך להגיש מטלות.
- בודקים את שיעורי הבית.
אנשי ההוראה יכולים להשתמש בהצעות האלה כמה שרוצים, ומומלץ להם להקצות כל שיעורי בית אחרים שהם חושבים שמתאימים.
אם אתם עובדים על ה-codelab הזה לבד, אתם יכולים להשתמש במשימות האלה כדי לבדוק את הידע שלכם.
שינוי אפליקציה
פותחים את האפליקציה DiceRoller משיעור 1. (אם האפליקציה לא מותקנת במכשיר, אפשר להוריד אותה מכאן). קומפלו את האפליקציה והריצו אותה. שימו לב שאם מסובבים את המכשיר, הערך הנוכחי של הקובייה אובד. כדי לשמור את הערך הזה בחבילה ולשחזר אותו ב-onCreate(), צריך להטמיע את onSaveInstanceState().
עונים על השאלות הבאות
שאלה 1
האפליקציה שלך מכילה סימולציה של פיזיקה שנדרש חישוב כבד כדי להציג אותה. אחרי כן, המשתמש יקבל שיחת טלפון. איזו מהאפשרויות הבאות נכונה?
- במהלך שיחת הטלפון, צריך להמשיך לחשב את מיקומי האובייקטים בהדמיה הפיזיקלית.
- במהלך שיחת הטלפון, צריך להפסיק את חישוב המיקומים של האובייקטים בסימולציית הפיזיקה.
שאלה 2
איזו שיטה במחזור החיים צריך לבטל כדי להשהות את הסימולציה כשהאפליקציה לא מוצגת במסך?
onDestroy()onStop()onPause()onSaveInstanceState()
שאלה 3
כדי להפוך מחלקה למודעת מחזור חיים באמצעות ספריית מחזור החיים של Android, איזה ממשק צריך להטמיע במחלקה?
LifecycleLifecycleOwnerLifecycle.EventLifecycleObserver
שאלה 4
באילו נסיבות השיטה onCreate() בפעילות מקבלת Bundle עם נתונים (כלומר, Bundle הוא לא null)? יכולות להיות יותר מתשובה אחת.
- הפעילות מופעלת מחדש אחרי שמסובבים את המכשיר.
- הפעילות מתחילה מההתחלה.
- הפעילות מתחדשת אחרי שחוזרים מהרקע.
- המכשיר יופעל מחדש.
עוברים לשיעור הבא:
קישורים ל-codelabs אחרים בקורס הזה מופיעים בדף הנחיתה של ה-codelabs בנושא יסודות Android Kotlin.