תהליכים ומחזור חיים של אפליקציה

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

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

חשוב שמפתחי האפליקציות יבינו איך רכיבים שונים של האפליקציה (במיוחד Activity,‏ Service ו-BroadcastReceiver) משפיעים על משך החיים של התהליך של האפליקציה. שימוש שגוי ברכיבים האלה עלול לגרום למערכת להרוג את תהליך האפליקציה בזמן שהוא מבצע עבודה חשובה.

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

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

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

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

  3. תהליך גלוי מבצע עבודה שהמשתמש מודע אליה כרגע, ולכן סגירה שלו משפיעה לרעה באופן ניכר על חוויית המשתמש. תהליך נחשב גלוי בתנאים הבאים:
    • הוא מפעיל Activity שגלוי למשתמש במסך אבל לא בחזית (השיטה onPause() שלו הייתה בשימוש). המצב הזה יכול לקרות, למשל, אם Activity בחזית מוצג כתיבת דו-שיח שמאפשרת לראות את Activity הקודם מאחוריה.
    • יש לה Service שפועל כשירות בחזית, דרך Service.startForeground() (שמבקש מהמערכת להתייחס לשירות כמשהו שהמשתמש מודע אליו, או למעשה כאילו הוא גלוי).
    • הוא מארח שירות שהמערכת משתמשת בו לתכונה מסוימת שהמשתמש מודע לה, כמו טפט חי או שירות של שיטת קלט.

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

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

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

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

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

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

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

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

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

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

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

העדיפות של תהליך יכולה גם לעלות על סמך יחסי תלות אחרים שיש לו. לדוגמה, אם תהליך א' משויך ל-Service עם הדגל Context.BIND_AUTO_CREATE או משתמש ב-ContentProvider בתהליך ב', הסיווג של תהליך ב' תמיד חשוב לפחות כמו הסיווג של תהליך א'.