במהלך הניווט של המשתמש באפליקציה, מחוץ לאפליקציה ובחזרה לאפליקציה, המופעים של Activity
באפליקציה עוברים בין מצבים שונים במחזור החיים שלהם.
המחלקות Activity מספקות מספר קריאות חוזרות (callback) שמאפשרות לפעילות לדעת מתי משתנה מצב או שהמערכת יוצרת, עוצרת או מפעילה מחדש פעילות או משמידה את התהליך שבו הפעילות נמצאת.
בשיטות של קריאה חוזרת במחזור חיים, אפשר להגדיר את אופן הפעולה של הפעילות כשמשתמש יוצא ממנה ונכנס אליה מחדש. לדוגמה, אם אתם מפתחים נגן וידאו לסטרימינג, אתם יכולים להשהות את הסרטון ולנתק את החיבור לרשת כשהמשתמש עובר לאפליקציה אחרת. כשהמשתמש חוזר, אתם יכולים להתחבר מחדש לרשת ולאפשר למשתמש להמשיך לצפות בסרטון מאותה נקודה.
כל קריאה חוזרת מאפשרת לבצע פעולה ספציפית שמתאימה לשינוי מסוים בסטטוס. ביצוע העבודה הנכונה בזמן הנכון וטיפול נכון במעברים משפרים את היציבות והביצועים של האפליקציה. לדוגמה, הטמעה טובה של קריאות חוזרות (callbacks) של מחזור החיים יכולה לעזור לאפליקציה שלכם להימנע מהבעיות הבאות:
- קריסה אם המשתמש מקבל שיחת טלפון או עובר לאפליקציה אחרת בזמן השימוש באפליקציה שלכם.
- צריכת משאבי מערכת יקרים כשהמשתמש לא משתמש בה באופן פעיל.
- התקדמות המשתמשים תאבד אם הם ייצאו מהאפליקציה ויחזרו אליה בשלב מאוחר יותר.
- קריסה או אובדן ההתקדמות של המשתמשים כשהמסך מסתובב בין תצוגה לרוחב לתצוגה לאורך.
במסמך הזה מוסבר בפירוט מחזור החיים של הפעילות. המסמך מתחיל בתיאור של פרדיגמת מחזור החיים. בהמשך מוסבר על כל אחת מהפונקציות לטיפול בקריאות חוזרות: מה קורה באופן פנימי בזמן ההפעלה שלהן ומה צריך להטמיע במהלך ההפעלה.
לאחר מכן מוצג בקצרה הקשר בין מצב הפעילות לבין הפגיעות של תהליך להפסקת הפעולה שלו על ידי המערכת. בסוף, יש דיון בכמה נושאים שקשורים למעברים בין מצבי פעילות.
מידע על ניהול מחזורי חיים, כולל הנחיות לגבי שיטות מומלצות, זמין במאמרים מחזור חיים ב-Jetpack פיתוח נייטיב ושמירת מצבי ממשק המשתמש. כדי ללמוד איך לתכנן אפליקציה חזקה באיכות ייצור באמצעות פעילויות בשילוב עם רכיבי ארכיטקטורה, אפשר לעיין במדריך לארכיטקטורת אפליקציות.
מושגים שקשורים למחזור החיים של פעילות
כדי לנווט בין המעברים בשלבים של מחזור החיים של הפעילות, המחלקה Activity
מספקת קבוצה בסיסית של שש פונקציות קריאה חוזרת: onCreate, onStart, onResume, onPause, onStop ו-onDestroy. המערכת מפעילה כל אחת מהפונקציות האלה כשפעילות עוברת למצב חדש.
איור 1 מציג ייצוג חזותי של הפרדיגמה הזו.
כשהמשתמש מתחיל לצאת מהפעילות, המערכת קוראת לשיטות כדי לפרק את הפעילות. במקרים מסוימים, הפעילות מפורקת רק באופן חלקי והיא עדיין נמצאת בזיכרון, למשל כשהמשתמש עובר לאפליקציה אחרת. במקרים כאלה, הפעילות עדיין יכולה לחזור לחזית.
אם המשתמש חוזר לפעילות, היא ממשיכה מהמקום שבו הוא הפסיק. עם כמה יוצאים מן הכלל, לאפליקציות אסור להתחיל פעילויות כשהן פועלות ברקע.
הסיכוי שהמערכת תסיים תהליך מסוים, יחד עם הפעילויות שבו, תלוי במצב הפעילות באותו זמן. מידע נוסף על הקשר בין מצב לבין פגיעות להוצאה מהזיכרון זמין בקטע בנושא מצב פעילות והוצאה מהזיכרון.
בהתאם למורכבות הפעילות, סביר להניח שלא תצטרכו להטמיע את כל השיטות של מחזור החיים. עם זאת, חשוב להבין כל אחת מהן וליישם את אלה שגורמות לאפליקציה להתנהג כמו שהמשתמשים מצפים.
כתיבת הודעות ומחזור החיים
ב-Compose, מומלץ לא להציב לוגיקה עסקית או הגדרה ידנית של observer ישירות בתוך קריאות חוזרות (callbacks) של פעילות כמו onStart או onResume. במקום זאת, אפשר להשתמש באפקטים שמודעים למחזור החיים ובאובייקטים מסוג Observer שמודעים למצב, שמתיישרים אוטומטית עם הנוכחות של ממשק המשתמש במסך.
- אוסף עם מודעות למחזור החיים: אפשר להשתמש ב-
collectAsStateWithLifecycleכדי לצרוך תהליכים מ-ViewModel. ה-API הזה מתחיל לאסוף נתונים באופן אוטומטי כשממשק המשתמש עובר למצב Started, ומפסיק לאסוף נתונים כשהוא עובר לרקע, וכך נמנעת צריכת משאבים מיותרת. אחרי שמשתמשים ב-Flow כדי לאסוף נתונים כמצב, אפשר להשתמש ב-LifecycleEffectsכדי להריץ קוד כשמתרחש אירוע במחזור חיים. - זרימת הלוגיקה: באמצעות ממשקי ה-API האלה, ממשק המשתמש מגיב למצב מחזור החיים באופן טבעי דרך עץ הקומפוזיציה, וכך מבטיח שהלוגיקה העסקית תופעל רק כשהמשתמש יוצר אינטראקציה פעילה עם הרכיב.
מידע נוסף על Compose ומחזור החיים זמין במאמר מחזור החיים ב-Jetpack Compose.
קריאות חוזרות במחזור חיים
בקטע הזה מוסבר על שיטות הקריאה החוזרת שמשמשות במהלך מחזור החיים של הפעילות.
חלק מהפעולות שייכות לשיטות של מחזור החיים של הפעילות. עם זאת, צריך למקם את הקוד שמטמיע את הפעולות של רכיב תלוי ברכיב, ולא בשיטת מחזור החיים של הפעילות. כדי לעשות את זה, צריך להגדיר את מחזור החיים של הרכיב התלוי. במאמר מחזור החיים ב-Jetpack פיתוח נייטיב מוסבר איך להגדיר את מחזור החיים של רכיבים תלויים.
onCreate
חובה להטמיע את הקריאה החוזרת הזו, שמופעלת כשהמערכת יוצרת את הפעילות בפעם הראשונה. כשיוצרים פעילות, היא עוברת למצב נוצרה. ב-method onCreate, מבצעים לוגיקה בסיסית של הפעלת האפליקציה שמתרחשת רק פעם אחת במהלך מחזור החיים של הפעילות.
לדוגמה, ההטמעה של onCreate עשויה לקשור נתונים לרשימות,
לשייך את הפעילות לViewModel וליצור מופע של כמה משתנים בהיקף המחלקה. ה-method הזה מקבל את הפרמטר savedInstanceState, שהוא אובייקט Bundle שמכיל את המצב שנשמר קודם של הפעילות. אם הפעילות לא התקיימה אף פעם, הערך של האובייקט Bundle הוא null.
אם יש לכם רכיב שמודע למחזור החיים ומחובר למחזור החיים של הפעילות, הוא מקבל את האירוע ON_CREATE. השיטה שמסומנת ב-@OnLifecycleEvent נקראת כדי שהרכיב שמודע למחזור החיים יוכל לבצע את קוד ההגדרה שהוא צריך למצב שנוצר.
בדוגמה הבאה אפשר לראות איך משלבים רכיב Text שאפשר להרכיב בפעילות מינימלית:
class ExampleActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { // In here, we can call composables! MaterialTheme { Greeting(name = "compose") } } } } @Composable fun Greeting(name: String) { Text(text = "Hello $name!") }
הפעילות לא נשארת במצב 'נוצרה'. אחרי שהשיטה onCreate מסיימת את ההרצה, הפעילות עוברת למצב Started והמערכת קוראת לשיטות onStart ו-onResume ברצף מהיר.
onStart
כשהפעילות עוברת למצב Started, המערכת מפעילה את onStart.
הקריאה הזו גורמת לכך שהפעילות תהיה גלויה למשתמש בזמן שהאפליקציה מתכוננת להעביר את הפעילות לחזית ולהפוך אותה לאינטראקטיבית. לדוגמה, בשיטה הזו מאתחלים את הקוד שמנהל את ממשק המשתמש.
כשהפעילות עוברת למצב Started, כל רכיב שמודע למחזור החיים ומקושר למחזור החיים של הפעילות מקבל את האירוע ON_START.
השיטה onStart מסתיימת במהירות, וכמו במצב Created, הפעילות לא נשארת במצב Started. אחרי שהקריאה החוזרת הזו מסתיימת, הפעילות עוברת למצב Resumed והמערכת מפעילה את השיטה onResume.
onResume
כשהפעילות עוברת למצב Resumed, היא עוברת לחזית והמערכת מפעילה את הקריאה החוזרת onResume. זהו המצב שבו האפליקציה יוצרת אינטראקציה עם המשתמש. האפליקציה נשארת במצב הזה עד שקורה משהו שגורם להפסקת המיקוד באפליקציה, כמו קבלת שיחה במכשיר, מעבר של המשתמש לפעילות אחרת או כיבוי המסך של המכשיר.
כשהפעילות עוברת למצב Resumed, כל רכיב שמודע למחזור החיים ומקושר למחזור החיים של הפעילות מקבל את האירוע ON_RESUME. כאן רכיבי מחזור החיים יכולים להפעיל כל פונקציונליות שצריכה לפעול בזמן שהרכיב גלוי ובחזית, כמו הפעלת תצוגה מקדימה של המצלמה.
כשמתרחש אירוע שמשבש את הפעילות, היא עוברת למצב Paused והמערכת מפעילה את הקריאה החוזרת onPause.
אם הפעילות חוזרת למצב Resumed (המשך) מהמצב Paused (השהיה), המערכת קוראת שוב לשיטה onResume. לכן, צריך להטמיע את onResume כדי לאתחל רכיבים שמשחררים במהלך onPause ולבצע את כל האתחולים האחרים שצריכים להתרחש בכל פעם שהפעילות עוברת למצב Resumed.
הנה דוגמה לרכיב שמודע למחזור החיים שלו ומקבל גישה למצלמה כשהרכיב מקבל את האירוע ON_RESUME:
class CameraComponent : LifecycleObserver {
...
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun initializeCamera() {
if (camera == null) {
getCamera()
}
}
...
}
הקוד שלמעלה מאתחל את המצלמה ברגע ש-LifecycleObserver מקבל את האירוע ON_RESUME. עם זאת, במצב מרובה חלונות, יכול להיות שהפעילות שלכם תהיה גלויה לחלוטין גם כשהיא במצב מושהה. לדוגמה, אם האפליקציה במצב ריבוי חלונות והמשתמש מקיש על החלון שלא מכיל את הפעילות שלכם, הפעילות עוברת למצב מושהה.
אם רוצים שהמצלמה תהיה פעילה רק כשהאפליקציה במצב Resumed (גלוי ופעיל בחזית), צריך לאתחל את המצלמה אחרי אירוע ON_RESUME שמוצג למעלה. אם רוצים שהמצלמה תמשיך לפעול בזמן שהפעילות מושהית אבל גלויה, למשל במצב מרובה חלונות, צריך להפעיל את המצלמה אחרי אירוע ON_START.
עם זאת, אם המצלמה פעילה בזמן שהפעילות מושהית, יכול להיות שאפליקציה אחרת שמופעלת מחדש במצב ריבוי חלונות לא תוכל לגשת למצלמה. לפעמים צריך להשאיר את המצלמה פעילה בזמן שהפעילות מושהית, אבל יכול להיות שזה יפגע בחוויית המשתמש הכוללת.
לכן, חשוב לחשוב היטב באיזה שלב במחזור החיים הכי מתאים להשתלט על משאבי מערכת משותפים בהקשר של מצב מרובה חלונות. מידע נוסף על תמיכה במצב ריבוי חלונות זמין במאמר תמיכה במצב ריבוי חלונות.
לא משנה באיזה אירוע הכנה תבחרו לבצע פעולת אתחול, חשוב להשתמש באירוע מחזור החיים המתאים כדי לשחרר את המשאב. אם אתם מאתחלים משהו אחרי אירוע ON_START, אתם צריכים לשחרר או לסיים אותו אחרי אירוע ON_STOP. אם אתם מזמינים את החדר אחרי האירוע ON_RESUME, אתם צריכים לבטל את ההזמנה אחרי האירוע ON_PAUSE.
בקטע הקוד הקודם, קוד ההפעלה של המצלמה ממוקם ברכיב שמודע למחזור החיים. אפשר להוסיף את הקוד הזה ישירות לקריאות החוזרות (callbacks) של מחזור החיים של הפעילות, כמו onStart ו-onStop, אבל אנחנו לא ממליצים על כך. הוספת הלוגיקה הזו לרכיב עצמאי שמודע למחזור החיים מאפשרת לכם לעשות שימוש חוזר ברכיב בכמה פעילויות בלי לשכפל את הקוד. במאמר מחזור החיים ב-Jetpack פיתוח נייטיב מוסבר איך ליצור רכיב שמודע למחזור החיים.
onPause
המערכת קוראת לשיטה הזו כאינדיקציה הראשונה לכך שהמשתמש עוזב את הפעילות, אבל זה לא תמיד אומר שהפעילות נהרסת. היא מציינת שהפעילות כבר לא בחזית, אבל היא עדיין גלויה אם המשתמש נמצא במצב ריבוי חלונות. יש כמה סיבות לכך שפעילות מסוימת עשויה לעבור למצב הזה:
- אירוע שמפריע להרצת האפליקציה, כמו שמתואר בקטע על הקריאה החוזרת (callback)
onResume, גורם להשהיית הפעילות הנוכחית. זה המקרה הנפוץ ביותר. - במצב מרובה חלונות, רק אפליקציה אחת פעילה בכל רגע, והמערכת משהה את כל האפליקציות האחרות.
- כשפעילות חדשה, שקופה למחצה, כמו תיבת דו-שיח, נפתחת, הפעילות שהיא מכסה מושהית. כל עוד הפעילות גלויה באופן חלקי אבל לא במוקד ההתעניינות, היא מושהית.
כשפעילות עוברת למצב Paused, כל רכיב שמודע למחזור החיים ומקושר למחזור החיים של הפעילות מקבל את האירוע ON_PAUSE. כאן רכיבי מחזור החיים יכולים להפסיק כל פונקציונליות שלא צריכה לפעול בזמן שהרכיב לא בחזית, כמו הפסקת תצוגה מקדימה של מצלמה.
משתמשים בשיטה onPause כדי להשהות או לשנות פעולות שלא יכולות להימשך, או שיכולות להימשך באופן מוגבל, בזמן ש-Activity נמצא במצב מושהה, ושצפוי שהן יחזרו לפעולה בקרוב.
אפשר גם להשתמש בשיטה onPause כדי לשחרר משאבי מערכת, ידיות לטיפול בחיישנים (כמו GPS) או משאבים אחרים שמשפיעים על חיי הסוללה בזמן שהפעילות מושהית והמשתמש לא צריך אותם.
עם זאת, כמו שצוין בקטע בנושא onResume, יכול להיות שפעילות מושהית עדיין תהיה גלויה במלואה אם האפליקציה פועלת במצב ריבוי חלונות. כדאי להשתמש ב-onStop במקום ב-onPause כדי לשחרר או להתאים באופן מלא משאבים ופעולות שקשורים לממשק המשתמש, וכך לשפר את התמיכה במצב מרובה חלונות.
בדוגמה הבאה, LifecycleObserver מגיב לאירוע ON_PAUSE
כמו בדוגמה הקודמת של אירוע ON_RESUME, ומשחרר את המצלמה שמופעלת אחרי קבלת האירוע ON_RESUME:
class CameraComponent : LifecycleObserver {
...
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun releaseCamera() {
camera?.release()
camera = null
}
...
}
בדוגמה הזו, קוד השחרור של המצלמה ממוקם אחרי שהאירוע ON_PAUSE מתקבל על ידי LifecycleObserver.
ההפעלה של onPause קצרה מאוד ולא בהכרח מספקת מספיק זמן לביצוע פעולות שמירה. לכן לא מומלץ להשתמש ב-onPause כדי לשמור נתונים של אפליקציות או נתוני משתמש, לבצע קריאות לרשת או להריץ טרנזקציות במסד נתונים.
יכול להיות שהעבודה הזו לא תסתיים לפני שהשיטה תסתיים.
במקום זאת, מומלץ לבצע פעולות כיבוי שדורשות עומס כבד במהלך onStop. בקטע הבא מפורטות פעולות מתאימות שאפשר לבצע במהלך onStop. מידע נוסף על שמירת נתונים זמין בקטע בנושא שמירה ושחזור של מצב.
השלמת השיטה onPause לא אומרת שהפעילות יוצאת ממצב מושהה. במקום זאת, הפעילות נשארת במצב הזה עד שהיא מתחדשת או עד שהיא הופכת לבלתי נראית לחלוטין למשתמש. אם הפעילות ממשיכה, המערכת מפעילה שוב את הקריאה החוזרת onResume.
אם הפעילות חוזרת ממצב מושהה למצב פעיל, המערכת שומרת את מופע Activity בזיכרון, ומזכירה את המופע הזה כשהמערכת מפעילה את onResume. בתרחיש הזה, אין צורך לאתחל מחדש רכיבים שנוצרו במהלך אחת משיטות הקריאה החוזרת שמובילות למצב Resumed. אם הפעילות הופכת לבלתי נראית לחלוטין, המערכת קוראת ל-onStop.
onStop
כשהפעילות כבר לא גלויה למשתמש, היא עוברת למצב Stopped, והמערכת מפעילה את הקריאה החוזרת onStop. זה יכול לקרות כשפעילות שהופעלה לאחרונה מכסה את כל המסך. המערכת קוראת גם ל-onStop
כשהפעילות מסתיימת ועומדת להסתיים.
כשהפעילות עוברת למצב Stopped, כל רכיב שמודע למחזור החיים ומקושר למחזור החיים של הפעילות מקבל את האירוע ON_STOP. כאן רכיבי מחזור החיים יכולים להפסיק כל פונקציונליות שלא צריכה לפעול בזמן שהרכיב לא מוצג על המסך.
בשיטה onStop, משחררים או משנים את המשאבים שלא נדרשים בזמן שהאפליקציה לא מוצגת למשתמש. לדוגמה, האפליקציה יכולה להשהות אנימציות או לעבור מעדכוני מיקום מדויקים לעדכוני מיקום בהערכה גסה. השימוש ב-onStop במקום ב-onPause מאפשר להמשיך בעבודה שקשורה לממשק המשתמש, גם כשהמשתמש צופה בפעילות במצב מרובה חלונות.
בנוסף, מומלץ להשתמש ב-onStop כדי לבצע פעולות כיבוי שדורשות יחסית הרבה משאבי מעבד. לדוגמה, אם לא הצלחתם למצוא זמן טוב יותר לשמירת מידע במסד נתונים, תוכלו לעשות זאת במהלך onStop. בדוגמה הבאה מוצגת הטמעה של onStop ששומרת את התוכן של טיוטת הערה באחסון מתמיד:
override fun onStop() {
super.onStop()
// Delegate the save operation to the ViewModel, which handles the
// background thread operations (e.g., using Kotlin Coroutines and Room).
noteViewModel.saveDraft()
}
כשהפעילות עוברת למצב Stopped, אובייקט Activity נשאר בזיכרון: הוא שומר את כל המידע על המצב והחברים, אבל הוא לא מצורף למנהל החלונות. כשהפעילות מתחדשת, המידע הזה נטען מחדש.
מצב Stopped (הופסקה): הפעילות חוזרת לאינטראקציה עם המשתמש או שהיא מסיימת את הפעולה ונעלמת. אם הפעילות חוזרת, המערכת מפעילה את onRestart. אם הפעולה של Activity מסתיימת, המערכת קוראת ל-onDestroy.
onDestroy
הקריאה ל-onDestroy מתבצעת לפני שהפעילות מושמדת. המערכת מפעילה את הקריאה החוזרת הזו מאחת משתי סיבות:
- הפעילות מסתיימת כי המשתמש סגר את הפעילות לגמרי או כי הפונקציה
finishהופעלה בפעילות. - המערכת משמידה את הפעילות באופן זמני בגלל שינוי בהגדרה, כמו סיבוב המכשיר או מעבר למצב מרובה חלונות.
כשהפעילות עוברת למצב destroyed, כל רכיב שמודע למחזור החיים ומקושר למחזור החיים של הפעילות מקבל את האירוע ON_DESTROY. כאן רכיבי מחזור החיים יכולים לנקות את כל מה שהם צריכים לפני שהרכיב Activity מושמד.
במקום להוסיף לוגיקה ל-Activity כדי לקבוע למה הוא נמחק, אפשר להשתמש באובייקט ViewModel כדי להכיל את נתוני התצוגה הרלוונטיים ל-Activity. אם Activity נוצר מחדש בגלל שינוי בהגדרות, ViewModel לא צריך לעשות כלום, כי הוא נשמר ומועבר למופע הבא של Activity.
אם Activity לא נוצר מחדש, השיטה onCleared נקראת ב-ViewModel, שם אפשר לנקות את כל הנתונים שצריך לפני ההרס. אפשר להבחין בין שני התרחישים האלה באמצעות ה-method isFinishing.
אם הפעילות מסתיימת, onDestroy היא הקריאה החוזרת האחרונה במחזור החיים שהפעילות מקבלת. אם הפונקציה onDestroy מופעלת כתוצאה משינוי בהגדרות, המערכת יוצרת באופן מיידי מופע חדש של פעילות ואז מפעילה את הפונקציה onCreate במופע החדש בהגדרה החדשה.
פונקציית הקריאה החוזרת onDestroy משחררת את כל המשאבים שלא שוחררו על ידי פונקציות קריאה חוזרת קודמות, כמו onStop.
מצב הפעילות והוצאה מהזיכרון
המערכת מפסיקה תהליכים כשהיא צריכה לפנות זיכרון RAM. הסיכוי שהמערכת תסיים תהליך מסוים תלוי במצב התהליך באותו זמן. סטטוס התהליך, בתורו, תלוי בסטטוס הפעילות שפועלת בתהליך. בטבלה 1 מוצגים המתאמים בין מצב התהליך, מצב הפעילות והסיכוי שהמערכת תסיים את התהליך. הטבלה הזו רלוונטית רק אם תהליך לא מפעיל סוגים אחרים של רכיבי אפליקציה.
הסבירות להיהרג |
מצב התהליך |
מצב הפעילות הסופי |
הנמוך ביותר |
חזית (האפליקציה שמוצגת או שעומדת להיות מוצגת) |
הופעלה מחדש |
נמוכה |
גלוי (ללא מיקוד) |
התחלה/השהיה |
גבוה יותר |
רקע (בלתי נראה) |
הופסק |
הגבוהה ביותר |
ריק |
הושמד |
טבלה 1. הקשר בין מחזור החיים של התהליך לבין מצב הפעילות.
המערכת אף פעם לא מפסיקה פעילות באופן ישיר כדי לפנות זיכרון. במקום זאת, היא מפסיקה את התהליך שבו הפעילות רצה, וכך היא משמידה לא רק את הפעילות אלא גם את כל מה שרץ בתהליך. בקטע בנושא שמירה ושחזור של מצב מוסבר איך לשמור ולשחזר את מצב ממשק המשתמש של הפעילות כשמתרחש תהליך סגירה שמתחיל על ידי המערכת.
המשתמש יכול גם להפסיק תהליך באמצעות מנהל האפליקציות, בקטע 'הגדרות', כדי להפסיק את האפליקציה המתאימה.
מידע נוסף על תהליכים זמין במאמר סקירה כללית על תהליכים ועל שרשורים.
שמירה ושחזור של מצב ממשק משתמש זמני
משתמש מצפה שמצב ממשק המשתמש של פעילות מסוימת יישאר זהה לאורך שינוי בהגדרות, כמו סיבוב או מעבר למצב מרובה חלונות. עם זאת, המערכת משמידה את הפעילות כברירת מחדל כשמתרחש שינוי כזה בהגדרה, ומבטלת כל מצב של ממשק משתמש שמאוחסן במופע הפעילות.
באופן דומה, משתמש מצפה שמצב ממשק המשתמש יישאר זהה אם הוא עובר זמנית מהאפליקציה שלכם לאפליקציה אחרת ואז חוזר לאפליקציה שלכם מאוחר יותר. עם זאת, המערכת יכולה להרוס את התהליך של האפליקציה בזמן שהמשתמש לא נמצא ליד המכשיר והפעילות שלכם מופסקת.
כשמגבלות המערכת גורמות להרס הפעילות, צריך לשמור את מצב ממשק המשתמש הזמני של המשתמש באמצעות שילוב של ViewModel (ללוגיקה עסקית מורכבת ולמצב המסך), Jetpack פיתוח נייטיב rememberSaveable API (למצב ממשק משתמש קל משקל) ו/או אחסון מקומי. במאמר שמירת מצבי ממשק המשתמש מוסבר על הציפיות של המשתמשים בהשוואה להתנהגות המערכת, ואיך לשמור בצורה הכי טובה נתונים מורכבים של מצב ממשק המשתמש במהלך פעילות שהמערכת יזמה וסיום תהליך.
rememberSaveable באופן אוטומטי שומר על הנתונים גם כשמבצעים שינויים בהגדרות וגם כשמערכת ההפעלה מפסיקה את התהליך, כי הוא מאגד את הנתונים מאחורי הקלעים. כך אפשר ליהנות מחוויה חלקה בלי להשתמש בקוד boilerplate ברמת הפעילות.
מצב המופע
יש כמה תרחישים שבהם הפעילות נמחקת בגלל התנהגות רגילה של האפליקציה, למשל כשהמשתמש לוחץ על הכפתור "הקודם" או כשהפעילות מסמנת את המחיקה שלה על ידי קריאה לשיטה finish.
כשפעילות מושמדת כי המשתמש לוחץ על'הקודם' או שהפעילות מסתיימת בעצמה, המושג של מופע Activity הזה נעלם לתמיד גם מהמערכת וגם מהתפיסה של המשתמש. בתרחישים האלה, הציפייה של המשתמש תואמת להתנהגות המערכת, ולא צריך לבצע פעולות נוספות.
עם זאת, אם המערכת משמידה את הפעילות בגלל מגבלות מערכת (כמו שינוי בהגדרה או עומס על הזיכרון), אז למרות שמופע Activity בפועל נעלם, המערכת זוכרת שהוא היה קיים. אם המשתמש מנסה לחזור לפעילות, המערכת יוצרת מופע חדש של הפעילות הזו באמצעות קבוצה של נתונים שמורים שמתארים את מצב הפעילות כשהיא נהרסה.
הנתונים השמורים שהמערכת משתמשת בהם כדי לשחזר את המצב הקודם נקראים מצב המופע. מאחורי הקלעים, מדובר באוסף של צמדי מפתח/ערך. כברירת מחדל, המערכת משתמשת במצב המופע כדי לשמור מידע בסיסי על פריסה של ממשק המשתמש, כמו קלט טקסט של משתמש או מיקומי גלילה.
כדי להתחבר להתנהגות הזו של המערכת, משתמשים ב-rememberSaveable. אם מופעלת השמדה של מופע הפעילות ומופעלת יצירה מחדש שלו, כל מצב ממשק המשתמש שעטוף ב-rememberSaveable משוחזר אוטומטית, בלי שנדרש קוד נוסף ברמת הפעילות.
עם זאת, סביר להניח שהפעילות שלכם תכלול מידע מורכב יותר על המצב שתרצו לשחזר, כמו נתוני משתמשים, תגובות רשת או משתנים של חברים שעוקבים אחרי ההתקדמות של המשתמש. מנגנון מצב המופע (ובאופן רחב יותר, rememberSaveable) לא מתאים לשמירה של כמות נתונים גדולה, כי הוא דורש סריאליזציה בשרשור הראשי וצורכת זיכרון של תהליך המערכת.
כדי לשמור יותר מנתונים מועטים מאוד, צריך להשתמש בגישה משולבת שכוללת אחסון מקומי קבוע, המחלקה ViewModel והעברת מצב (state hoisting) ב-Compose, כמו שמתואר במאמר שמירת מצבי ממשק המשתמש.
שמירת מצב פשוט וקל משקל של ממשק המשתמש באמצעות rememberSaveable
כשהפעילות מתחילה להיפסק, המערכת מתכוננת לשמור את פרטי המצב בחבילת מצב של מופע. כדי להתחבר להתנהגות המערכת הזו, משתמשים ב-rememberSaveable ישירות בפונקציות הניתנות להרכבה.
rememberSaveable שומרת באופן אוטומטי את מצב ממשק המשתמש הזמני ומחזירה אותו, למשל
קלט טקסט של משתמש או מיקומי גלילה, במהלך יצירה מחדש של פעילות.
כדי לשמור מידע מותאם אישית וקל משקל על מצב (כמו ההתקדמות של משתמש במשחק), צריך להצהיר על המצב באמצעות rememberSaveable. Compose framework מטפל בסריאליזציה של חבילת מצב המופע מתחת לפני השטח:
var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) { mutableStateOf( TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length)) ) }
כדי לשמור נתונים קבועים, כמו העדפות משתמשים או נתונים למסד נתונים, צריך לנצל הזדמנויות מתאימות כשהפעילות מתבצעת בחזית. אם אין אפשרות כזו, צריך לשמור את הנתונים המתמשכים במהלך השימוש בשיטה onStop.
שחזור מצב ממשק המשתמש של הפעילות באמצעות מצב המכונה השמור
כשפעילות נוצרת מחדש אחרי שהיא נהרסה קודם, שחזור המצב מתבצע באופן אוטומטי. כשמשתמשים ב-rememberSaveable, לא צריך לכתוב לוגיקה מפורשת לשחזור, לבדוק אם יש חבילות null או לבטל את ההגדרות של קריאות חוזרות (callback) של פעילות. הקוד שמאתחל ושומר את המצב גם משחזר אותו בצורה חלקה כשהפעילות חוזרת:
var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) { mutableStateOf( TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length)) ) }
פעילויות וניווט
במהלך השימוש באפליקציה, המשתמש עובר בין מסכים הרבה פעמים, למשל כשהוא מקיש על הכפתור "הקודם" במכשיר או בוחר יעד חדש. אפליקציות מודרניות ל-Android בדרך כלל משתמשות בארכיטקטורה של פעילות יחידה.
במקום להפעיל Activity חדש לכל מסך, האפליקציה מארחת Activity אחד ומשתמשת ברכיב Navigation כדי להחליף בין מסכים שניתנים להרכבה בתוך הפעילות הזו.
במדריך לספריית Navigation 3 של Jetpack פיתוח נייטיב מוסבר איך להטמיע ניווט מודרני שמבוסס על פיתוח נייטיב.
התחלת פעילות אחת מתוך פעילות אחרת
יכול להיות שבשלב מסוים פעילות מסוימת תצטרך להתחיל פעילות אחרת. לדוגמה, כשצריך להעביר אפליקציה מהמסך הנוכחי למסך חדש.
בהתאם לשאלה אם הפעילות רוצה לקבל תוצאה חזרה מהפעילות החדשה שהיא עומדת להתחיל, מפעילים את הפעילות החדשה באמצעות השיטה startActivity או השיטה startActivityForResult. בכל מקרה, מעבירים אובייקט Intent.
אובייקט Intent מציין את הפעילות המדויקת שרוצים להתחיל או מתאר את סוג הפעולה שרוצים לבצע. המערכת בוחרת את הפעילות המתאימה לכם, שיכולה להיות גם מאפליקציה אחרת. אובייקט Intent יכול גם להעביר כמויות קטנות של נתונים לשימוש בפעילות שמופעלת. מידע נוסף על המחלקה Intent זמין במאמר Intents and Intent Filters.
startActivity
אם הפעילות החדשה שהופעלה לא צריכה להחזיר תוצאה, הפעילות הנוכחית יכולה להפעיל אותה באמצעות קריאה לשיטה startActivity.
כשעובדים באפליקציה, לעיתים קרובות צריך פשוט להפעיל פעילות מוכרת. לדוגמה, בקטע הקוד הבא מוצג איך להפעיל פעילות שנקראת SignInActivity.
val context = LocalContext.current
Button(onClick = {
val intent = Intent(context, SignInActivity::class.java)
context.startActivity(intent)
}) {
Text("Sign In")
}
התחלת פעילויות חיצוניות
ניווט פנימי באפליקציה מטופל על ידי Navigation, אבל מדי פעם Activity יצטרך להתחיל פעילויות אחרות. בדרך כלל זה קורה כשרוצים להשתמש באפליקציה של צד שלישי כדי לבצע פעולה ספציפית, כמו פתיחת דפדפן אינטרנט, שליחת אימייל או צילום תמונה.
כדי לעשות את זה, משתמשים באובייקט Intent כדי לתאר את סוג הפעולה שרוצים לבצע, והמערכת מפעילה את הפעילות המתאימה מאפליקציה אחרת.
לדוגמה, אם רוצים לאפשר למשתמש לשלוח הודעת אימייל, אפשר ליצור את הכוונה הבאה:
val intent = Intent(Intent.ACTION_SEND).apply {
putExtra(Intent.EXTRA_EMAIL, recipientArray)
}
startActivity(intent)
אם אתם צריכים להפעיל פעילות חיצונית ולקבל תוצאה (למשל, לבקש מאפליקציית המצלמה לצלם תמונה ולהחזיר את התמונה), כדאי להשתמש בActivity ממשקי ה-API המודרניים של התוצאות ולא ב-callback של startActivityForResult שהוצא משימוש.
תיאום פעילויות
כשפעילות אחת מתחילה פעילות אחרת, שתיהן עוברות שינויים במחזור החיים. הפעילות הראשונה מפסיקה לפעול ועוברת למצב מושהה או למצב עצירה, בזמן שהפעילות השנייה נוצרת. אם הפעילויות האלה משתפות נתונים שנשמרו בדיסק או במקום אחר, חשוב להבין שהפעילות הראשונה לא נעצרת לגמרי לפני שהפעילות השנייה נוצרת. במקום זאת, תהליך ההתחלה של התהליך השני חופף לתהליך העצירה של התהליך הראשון.
הסדר של קריאות חוזרות (callback) של מחזור החיים מוגדר היטב, במיוחד כששתי הפעילויות מתבצעות באותו תהליך – במילים אחרות, באותה אפליקציה – ואחת מתחילה את השנייה. זהו סדר הפעולות שמתרחש כשפעילות א' מתחילה פעילות ב':
- ה-method
onPauseשל פעילות א' מופעל. - השיטות
onCreate,onStartו-onResumeשל פעילות ב' מופעלות ברצף. המיקוד של המשתמש עובר לפעילות ב'. - אם הפעילות A לא גלויה יותר במסך, הפונקציה
onStopשלה מופעלת.
רצף הפונקציות האלה מאפשר לכם לנהל את המעבר של מידע מפעילות אחת לפעילות אחרת.
מקורות מידע נוספים
מידע נוסף על מחזור החיים של פעילות זמין במקורות המידע הבאים: