מחזור החיים של הפעילות

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

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

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

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

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

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

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

מושגים שקשורים למחזור החיים של פעילות

כדי לנווט בין המעברים בין השלבים של מחזור החיים של הפעילות, המחלקה Activity מספקת קבוצת ליבה של שש פונקציות קריאה חוזרת (callback): onCreate,‏ onStart,‏ onResume,‏ onPause,‏ onStop ו-onDestroy. המערכת מפעילה כל אחד מהקריאות החוזרות האלה כשהפעילות נכנסת למצב חדש.

איור 1 מציג ייצוג חזותי של הפרדיגמה הזו.

איור 1. איור פשוט של מחזור החיים של פעילות.

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

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

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

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

‫Compose ומחזור החיים

ב-Compose, אל תמקמו לוגיקה עסקית או הגדרה ידנית של צופה ישירות בתוך פונקציות קריאה חוזרת (callback) של פעילות כמו onStart או onResume. במקום זאת, השתמשו באפקטים שמודעים למחזור החיים ובמשקיפים שמודעים למצב, שמתאימים באופן אוטומטי לנוכחות של ממשק המשתמש במסך.

  • אוסף שמותאם למחזור החיים: משתמשים ב-collectAsStateWithLifecycle כדי לצרוך תהליכים מה-ViewModel. ה-API הזה מתחיל לאסוף נתונים באופן אוטומטי כשהממשק נכנס למצב Started, ומפסיק כשהוא עובר לרקע, כדי למנוע צריכת משאבים מיותרת. אחרי שאוספים את ה-flow כערך דינמי, אפשר להשתמש ב-LifecycleEffects כדי להריץ קוד כשמתרחש אירוע במחזור חיים.
  • זרימת הלוגיקה: כשמשתמשים בממשקי ה-API האלה, ממשק המשתמש מגיב למצב מחזור החיים באופן טבעי דרך עץ הקומפוזיציה, וכך מבטיח שהלוגיקה העסקית תפעל רק כשהמשתמש יוצר אינטראקציה פעילה עם הרכיב.

מידע נוסף על Compose ומחזור החיים זמין במאמר מחזור החיים ב-Jetpack Compose.

קריאות חוזרות במחזור חיים

בקטע הזה מוסברים המושגים וההטמעה של שיטות הקריאה החוזרת שמשמשות במהלך מחזור החיים של הפעילות.

חלק מהפעולות שייכות ל-methods של מחזור החיים של הפעילות. עם זאת, צריך להציב את הקוד שמטמיע את הפעולות של רכיב תלוי ברכיב עצמו, ולא ב-method של מחזור החיים של הפעילות. כדי לעשות את זה, צריך להגדיר את הרכיב התלוי כרכיב שמודע למחזור החיים. במאמר מחזור חיים ב-Jetpack Compose מוסבר איך להפוך את הרכיבים התלויים למודעים למחזור החיים.

onCreate

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

לדוגמה, ההטמעה של onCreate יכולה לקשר נתונים לרשימות, לשייך את הפעילות ל-ViewModel וליצור מופעים של משתנים מסוימים ברמת המחלקה. ה-method הזה מקבל את הפרמטר savedInstanceState, שהוא אובייקט Bundle שמכיל את המצב של הפעילות שנשמר קודם. אם הפעילות לא הייתה קיימת לפני כן, הערך של האובייקט Bundle הוא null.

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

בדוגמה הבאה אפשר לראות איך לשלב פונקציה קומפוזבילית Text ב-Activity מינימלי:

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!")
}

הפעילות לא נשארת במצב Created (נוצרה). אחרי שהשיטה onCreate מסיימת לפעול, הפעילות נכנסת למצב Started והמערכת קוראת לשיטות onStart ו-onResume ברצף מהיר.

onStart

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

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

ה-method‏ onStart מסתיימת במהירות, וכמו במצב Created, הפעילות לא נשארת במצב Started. אחרי שהקריאה החוזרת מסתיימת, הפעילות נכנסת למצב Resumed והמערכת מפעילה את השיטה onResume.

onResume

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

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

כשמתרחש אירוע שמפריע, הפעילות נכנסת למצב Paused והמערכת מפעילה את הקריאה החוזרת (callback) של onPause.

אם הפעילות חוזרת למצב Resumed מהמצב Paused, המערכת קוראת שוב ל-method‏ 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.

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

onPause

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

  • אירוע שמפריע להפעלת האפליקציה, כמו שמתואר בקטע על פונקציית ה-callback‏ onResume, משהה את הפעילות הנוכחית. זה המקרה הכי נפוץ.
  • במצב ריבוי חלונות, רק אפליקציה אחת נמצאת במוקד בכל זמן נתון, והמערכת משהה את כל האפליקציות האחרות.
  • כשפותחים פעילות חדשה, חצי שקופה, כמו תיבת דו-שיח, הפעילות שהיא מכסה מושהית. כל עוד הפעילות גלויה באופן חלקי אבל לא ממוקדת, היא נשארת מושהית.

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

משתמשים ב-method‏ 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 לא אומרת שהפעילות יוצאת מהמצב "מושהית". במקום זאת, הפעילות נשארת במצב הזה עד שהיא מתחדשת או עד שהיא הופכת לבלתי נראית לחלוטין למשתמש. אם הפעילות מתחדשת, המערכת מפעילה שוב את הקריאה החוזרת (callback) של onResume.

אם הפעילות חוזרת מהמצב Paused למצב Resumed, המערכת שומרת את המופע של Activity בזיכרון, ומאחזרת אותו כשהיא מפעילה את onResume. בתרחיש הזה, אין צורך להפעיל מחדש רכיבים שנוצרו במהלך כל אחת משיטות הקריאה החוזרת שמובילות למצב Resumed. אם הפעילות הופכת לבלתי נראית לחלוטין, המערכת מפעילה את onStop.

onStop

כשהפעילות כבר לא גלויה למשתמש, היא נכנסת למצב Stopped, והמערכת מפעילה את פונקציית ה-callback‏ onStop. זה יכול לקרות כשפעילות חדשה שהופעלה מכסה את כל המסך. המערכת גם קוראת ל-onStop כשהפעילות מסתיימת ועומדת להיסגר.

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

ב-method‏ 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 נקראת לפני שהפעילות נהרסת. המערכת מפעילה את פונקציית ה-callback הזו מאחת משתי סיבות:

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

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

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

אם ה-Activity לא נוצר מחדש, מתבצעת קריאה ל-method‏ onCleared ב-ViewModel, שבה אפשר לנקות את כל הנתונים שצריך לפני ההשמדה. אפשר להבחין בין שני התרחישים האלה באמצעות ה-method‏ isFinishing.

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

הקריאה החוזרת (callback) onDestroy משחררת את כל המשאבים שלא שוחררו על ידי קריאות חוזרות קודמות, כמו onStop.

מצב הפעילות והוצאה מהזיכרון

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

הסבירות להיהרג

מצב התהליך

מצב הפעילות הסופי

הנמוך ביותר

חזית (עם מיקוד או עומד לקבל מיקוד)

הופעלה מחדש

נמוכה

גלוי (לא במיקוד)

התחלה/השהיה

גבוה יותר

רקע (לא גלוי)

הופסק

הגבוהה ביותר

ריק

הושמד

טבלה 1. הקשר בין מחזור החיים של התהליך לבין מצב הפעילות.

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

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

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

שמירה ושחזור של מצב ממשק משתמש זמני

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

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

כשאילוצי המערכת משמידים את הפעילות, צריך לשמור על מצב ממשק המשתמש הזמני של המשתמש באמצעות שילוב של ViewModel (ללוגיקה עסקית מורכבת ולמצב המסך), Jetpack Compose rememberSaveable API (למצב ממשק משתמש קל) ו/או אחסון מקומי. במאמר שמירת מצבי ממשק המשתמש מוסבר בהרחבה על הציפיות של המשתמשים בהשוואה להתנהגות המערכת, ואיך לשמור בצורה הטובה ביותר נתונים מורכבים של מצב ממשק המשתמש בפעילות שהמערכת יזמה ובהשבתת תהליך.

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

מצב המכונה

יש כמה תרחישים שבהם הפעילות נהרסת בגלל התנהגות רגילה של האפליקציה, למשל כשהמשתמש לוחץ על הכפתור "הקודם" או כשהפעילות מאותתת על ההרס שלה באמצעות קריאה ל-method‏ finish.

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

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

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

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

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

כדי לשמור יותר מכמות קטנה מאוד של נתונים, צריך להשתמש בגישה משולבת שכוללת אחסון מקומי קבוע, הכיתה ViewModel והעלאת הרמה של מצב (state hoisting) ב-Compose, כמו שמתואר במאמר שמירת מצבי ממשק משתמש.

שמירת מצב פשוט וקל של ממשק המשתמש באמצעות rememberSaveable

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

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

var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) {
    mutableStateOf(
        TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length))
    )
}

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

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

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

var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) {
    mutableStateOf(
        TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length))
    )
}

פעילויות וניווט

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

כדי ללמוד איך להטמיע ניווט מודרני שמבוסס על Jetpack Compose, אפשר לעיין במדריך לספריית Navigation 3.

התחלת פעילות אחת מתוך פעילות אחרת

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

בהתאם לשאלה אם הפעילות שלכם רוצה לקבל תוצאה מהפעילות החדשה שהיא עומדת להתחיל, אתם מתחילים את הפעילות החדשה באמצעות השיטה startActivity או השיטה startActivityForResult. בשני המקרים, מעבירים אובייקט Intent.

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

startActivity

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

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

val context = LocalContext.current

Button(onClick = {
    val intent = Intent(context, SignInActivity::class.java)
    context.startActivity(intent)
}) {
    Text("Sign In")
}

התחלת פעילויות חיצוניות

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

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

לדוגמה, אם רוצים לאפשר למשתמש לשלוח הודעת אימייל, אפשר ליצור את ה-Intent הבא:

val intent = Intent(Intent.ACTION_SEND).apply {
    putExtra(Intent.EXTRA_EMAIL, recipientArray)
}
startActivity(intent)

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

פעילויות תיאום

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

הסדר של הקריאות החוזרות במחזור החיים מוגדר היטב, במיוחד כששני ה-activities נמצאים באותו תהליך (כלומר, באותה אפליקציה) ואחד מהם מפעיל את השני. זה סדר הפעולות שמתרחשות כש-Activity א' מתחילה את Activity ב':

  1. ה-method onPause של פעילות א' מופעלת.
  2. השיטות onCreate,‏ onStart ו-onResume של פעילות ב' מופעלות ברצף. עכשיו הפעילות B נמצאת במיקוד של המשתמש.
  3. אם פעילות א' כבר לא מוצגת במסך, השיטה onStop שלה מופעלת.

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

מקורות מידע נוספים

מידע נוסף על מחזור החיים של פעילות זמין במקורות המידע הבאים:

צפייה בתוכן