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

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

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

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

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

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

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

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

מושגים בנושא מחזור החיים של פעילות

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

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

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

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

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

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

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

קריאה חוזרת (callback) במחזור החיים

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

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

onCreate()

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

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

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

הדוגמה הבאה ל-method onCreate() מציגה את ההגדרה הבסיסית של הפעילות, כמו הצהרה על ממשק המשתמש (מוגדר בקובץ פריסת XML), הגדרת משתני חבר והגדרת חלק מממשק המשתמש. בדוגמה הזו, קובץ פריסת ה-XML מעביר את מזהה המשאב של הקובץ R.layout.main_activity אל setContentView().

Kotlin

lateinit var textView: TextView

// Some transient state for the activity instance.
var gameState: String? = null

override fun onCreate(savedInstanceState: Bundle?) {
    // Call the superclass onCreate to complete the creation of
    // the activity, like the view hierarchy.
    super.onCreate(savedInstanceState)

    // Recover the instance state.
    gameState = savedInstanceState?.getString(GAME_STATE_KEY)

    // Set the user interface layout for this activity.
    // The layout is defined in the project res/layout/main_activity.xml file.
    setContentView(R.layout.main_activity)

    // Initialize member TextView so it is available later.
    textView = findViewById(R.id.text_view)
}

// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    textView.text = savedInstanceState?.getString(TEXT_VIEW_KEY)
}

// Invoked when the activity might be temporarily destroyed; save the instance state here.
override fun onSaveInstanceState(outState: Bundle?) {
    outState?.run {
        putString(GAME_STATE_KEY, gameState)
        putString(TEXT_VIEW_KEY, textView.text.toString())
    }
    // Call superclass to save any view hierarchy.
    super.onSaveInstanceState(outState)
}

Java

TextView textView;

// Some transient state for the activity instance.
String gameState;

@Override
public void onCreate(Bundle savedInstanceState) {
    // Call the superclass onCreate to complete the creation of
    // the activity, like the view hierarchy.
    super.onCreate(savedInstanceState);

    // Recover the instance state.
    if (savedInstanceState != null) {
        gameState = savedInstanceState.getString(GAME_STATE_KEY);
    }

    // Set the user interface layout for this activity.
    // The layout is defined in the project res/layout/main_activity.xml file.
    setContentView(R.layout.main_activity);

    // Initialize member TextView so it is available later.
    textView = (TextView) findViewById(R.id.text_view);
}

// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    textView.setText(savedInstanceState.getString(TEXT_VIEW_KEY));
}

// Invoked when the activity might be temporarily destroyed; save the instance state here.
@Override
public void onSaveInstanceState(Bundle outState) {
    outState.putString(GAME_STATE_KEY, gameState);
    outState.putString(TEXT_VIEW_KEY, textView.getText());

    // Call superclass to save any view hierarchy.
    super.onSaveInstanceState(outState);
}

במקום להגדיר את קובץ ה-XML ולהעביר אותו אל setContentView(), אפשר יכול ליצור אובייקטים חדשים מסוג View בקוד הפעילות שלך ולבנות להציג את ההיררכיה על ידי הוספת אובייקטי View חדשים לתוך ViewGroup לאחר מכן משתמשים בפריסה הזו על ידי העברת רמת השורש ViewGroup אל setContentView(). למידע נוסף על יצירת ממשק משתמש, אפשר לעיין במאמר משאבי עזרה לממשק המשתמש.

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

onStart()

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

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

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

onResume()

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

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

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

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

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

Kotlin

class CameraComponent : LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun initializeCamera() {
        if (camera == null) {
            getCamera()
        }
    }
    ...
}

Java

public class CameraComponent implements LifecycleObserver {

    ...

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    public void initializeCamera() {
        if (camera == null) {
            getCamera();
        }
    }
    ...
}

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

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

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

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

לא משנה איזה אירוע build תבחרו לבצע פעולת אתחול ב, יש להקפיד להשתמש במחזור החיים התואם כדי לשחרר את המשאב. אם מאתחלים משהו אחרי האירוע ON_START, יש לשחרר או לסיים אותו לאחר אירוע ON_STOP. אם יופעל אחרי האירוע ON_RESUME, יש לשחרר אחרי אירוע ON_PAUSE.

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

onPause()

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

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

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

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

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

עם זאת, כפי שצוין בקטע על onResume(), מודל מושהה ייתכן שעדיין ניתן יהיה לראות את הפעילות במלואה אם האפליקציה במצב ריבוי חלונות. כדאי להשתמש ב-onStop() במקום ב-onPause() כדי לשחרר או לבצע התאמה באופן מלא פעולות ומשאבים שקשורים לממשק המשתמש לתמיכה טובה יותר במצב ריבוי חלונות.

הדוגמה הבאה של LifecycleObserver התגובה לאירוע ON_PAUSE מקבילה לאירוע הקודם דוגמה לאירוע ON_RESUME: שחרור המצלמה שתאתחל אחרי האירוע ON_RESUME מתקבל:

Kotlin

class CameraComponent : LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun releaseCamera() {
        camera?.release()
        camera = null
    }
    ...
}

Java

public class JavaCameraComponent implements LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    public void releaseCamera() {
        if (camera != null) {
            camera.release();
            camera = null;
        }
    }
    ...
}

בדוגמה הזו מוצג קוד השחרור של המצלמה אחרי האירוע ON_PAUSE התקבל על ידי LifecycleObserver.

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

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

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

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

onStop()

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

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

ב-method onStop(), משחררים או מבצעים התאמות משאבים שאינם נחוצים בזמן שהאפליקציה לא גלויה למשתמש. לדוגמה, ייתכן שהאפליקציה להשהות אנימציות או לעבור מעדכוני מיקום מפורטים יותר לעדכוני מיקום רחבים. באמצעות onStop() במקום onPause() פירושה שהעבודה הקשורה לממשק המשתמש נמשכת, גם כשהמשתמש צופה בפעילות בחלונות מרובים במצב תצוגה.

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

Kotlin

override fun onStop() {
    // Call the superclass method first.
    super.onStop()

    // Save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    val values = ContentValues().apply {
        put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText())
        put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle())
    }

    // Do this update in background on an AsyncQueryHandler or equivalent.
    asyncQueryHandler.startUpdate(
            token,     // int token to correlate calls
            null,      // cookie, not used here
            uri,       // The URI for the note to update.
            values,    // The map of column names and new values to apply to them.
            null,      // No SELECT criteria are used.
            null       // No WHERE columns are used.
    )
}

Java

@Override
protected void onStop() {
    // Call the superclass method first.
    super.onStop();

    // Save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    ContentValues values = new ContentValues();
    values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
    values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());

    // Do this update in background on an AsyncQueryHandler or equivalent.
    asyncQueryHandler.startUpdate (
            mToken,  // int token to correlate calls
            null,    // cookie, not used here
            uri,    // The URI for the note to update.
            values,  // The map of column names and new values to apply to them.
            null,    // No SELECT criteria are used.
            null     // No WHERE columns are used.
    );
}

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

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

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

הערה : אחרי שהפעילות הופסקה, המערכת עלול להרוס את התהליך שמכיל את הפעילות, אם המערכת צריך לשחזר את הזיכרון. גם אם המערכת תשמיד את התהליך בזמן שהפעילות הופסקה, המערכת עדיין שומרת על המצב של View כמו טקסט בווידג'ט EditText, Bundle – blob של צמדי מפתח-ערך – ושחזור שלהם אם המשתמש חוזר לפעילות. עבור לקבלת מידע נוסף על שחזור פעילות שאליה משתמש חוזר, אפשר לעיין בקטע שמירה ושחזור של מצב.

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

onDestroy()

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

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

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

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

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

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

הקריאה החוזרת (callback) של onDestroy() מסירה את כל המשאבים שלא שוחררו לפני כן קריאות חוזרות (callback), כמו onStop().

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

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

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

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

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

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

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

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

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

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

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

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

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

מצב המכונה

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

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

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

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

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

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

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

שמירת מצב פשוט וקומפקטי של ממשק משתמש באמצעות onSaveInstanceState()

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

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

Kotlin

override fun onSaveInstanceState(outState: Bundle?) {
    // Save the user's current game state.
    outState?.run {
        putInt(STATE_SCORE, currentScore)
        putInt(STATE_LEVEL, currentLevel)
    }

    // Always call the superclass so it can save the view hierarchy state.
    super.onSaveInstanceState(outState)
}

companion object {
    val STATE_SCORE = "playerScore"
    val STATE_LEVEL = "playerLevel"
}

Java

static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
// ...


@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state.
    savedInstanceState.putInt(STATE_SCORE, currentScore);
    savedInstanceState.putInt(STATE_LEVEL, currentLevel);

    // Always call the superclass so it can save the view hierarchy state.
    super.onSaveInstanceState(savedInstanceState);
}

הערה: onSaveInstanceState() אינו מופעלת כשהמשתמש סוגר את הפעילות באופן מפורש או במקרים אחרים, מתבצעת שיחה אל finish().

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

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

כשהפעילות שלכם נוצרת מחדש לאחר שהיא נמחקה בעבר, יכול לשחזר את מצב המכונה השמור מה-Bundle מועברות לפעילות שלכם. גם onCreate() ו- onRestoreInstanceState() שיטות הקריאה החוזרת (callback) מקבלות את אותו Bundle שמכיל על מצב המכונה.

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

קטע הקוד הבא מראה איך אפשר לשחזר נתוני המצב ב-onCreate():

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState) // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance.
    if (savedInstanceState != null) {
        with(savedInstanceState) {
            // Restore value of members from saved state.
            currentScore = getInt(STATE_SCORE)
            currentLevel = getInt(STATE_LEVEL)
        }
    } else {
        // Probably initialize members with default values for a new instance.
    }
    // ...
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance.
    if (savedInstanceState != null) {
        // Restore value of members from saved state.
        currentScore = savedInstanceState.getInt(STATE_SCORE);
        currentLevel = savedInstanceState.getInt(STATE_LEVEL);
    } else {
        // Probably initialize members with default values for a new instance.
    }
    // ...
}

במקום לשחזר את המצב במהלך onCreate(), אפשר לבחור להטמיע ל-onRestoreInstanceState(), שהמערכת קוראת לו אחרי onStart(). המערכת קוראת onRestoreInstanceState() רק אם יש מצב שמור לשחזור, אז אין צורך לבדוק אם הערך Bundle הוא null.

Kotlin

override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    // Always call the superclass so it can restore the view hierarchy.
    super.onRestoreInstanceState(savedInstanceState)

    // Restore state members from saved instance.
    savedInstanceState?.run {
        currentScore = getInt(STATE_SCORE)
        currentLevel = getInt(STATE_LEVEL)
    }
}

Java

public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy.
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from saved instance.
    currentScore = savedInstanceState.getInt(STATE_SCORE);
    currentLevel = savedInstanceState.getInt(STATE_LEVEL);
}

זהירות: קרא תמיד ליישום של מחלקה-על של onRestoreInstanceState() כך שהטמעת ברירת המחדל יכולה לשחזר את המצב של היררכיית התצוגות.

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

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

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

התחלת פעילות אחת בפעילות אחרת

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

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

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

startActivity()

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

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

Kotlin

val intent = Intent(this, SignInActivity::class.java)
startActivity(intent)

Java

Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);

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

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

Kotlin

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

Java

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);

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

startActivityForתוצאה()

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

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

כשפעילות של ילדים מסתיימת, היא יכולה לבצע קריאה לsetResult(int) כדי להחזיר נתונים להורה. בפעילות הצאצא צריך לספק קוד תוצאה, שיכול להיות התוצאות הרגילות RESULT_CANCELED, RESULT_OK או כל ערך מותאם אישית אחר החל מ-RESULT_FIRST_USER.

In addition, הפעילות של הילד או הילדה יכולה באופן אופציונלי להחזיר Intent שמכיל את הנתונים הנוספים שהוא רוצה. בפעילות של ההורה משתמשים onActivityResult(int, int, Intent) יחד עם המזהה השלם של פעילות ההורה במקור, סופק, כדי לקבל את המידע.

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

Kotlin

class MyActivity : Activity() {
    // ...

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
            // When the user center presses, let them pick a contact.
            startActivityForResult(
                    Intent(Intent.ACTION_PICK,Uri.parse("content://contacts")),
                    PICK_CONTACT_REQUEST)
            return true
        }
        return false
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
        when (requestCode) {
            PICK_CONTACT_REQUEST ->
                if (resultCode == RESULT_OK) {
                    // A contact was picked. Display it to the user.
                    startActivity(Intent(Intent.ACTION_VIEW, intent?.data))
                }
        }
    }

    companion object {
        internal val PICK_CONTACT_REQUEST = 0
    }
}

Java

public class MyActivity extends Activity {
     // ...

     static final int PICK_CONTACT_REQUEST = 0;

     public boolean onKeyDown(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
             // When the user center presses, let them pick a contact.
             startActivityForResult(
                 new Intent(Intent.ACTION_PICK,
                 new Uri("content://contacts")),
                 PICK_CONTACT_REQUEST);
            return true;
         }
         return false;
     }

     protected void onActivityResult(int requestCode, int resultCode,
             Intent data) {
         if (requestCode == PICK_CONTACT_REQUEST) {
             if (resultCode == RESULT_OK) {
                 // A contact was picked. Display it to the user.
                 startActivity(new Intent(Intent.ACTION_VIEW, data));
             }
         }
     }
 }

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

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

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

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

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