Android מגרסה 11 (רמת API 30) ואילך תומך בהקפאה של אפליקציות שנשמרו במטמון. התכונה הזו מפסיקה את הביצוע של תהליכים שנשמרו במטמון ומפחיתה את השימוש במשאבים על ידי אפליקציות שמתנהגות בצורה לא תקינה, שעשויות לנסות לפעול בזמן שהן שמורות במטמון.
הקפאת אפליקציות בזיכרון המטמון שומרת את האפליקציות בזיכרון ה-RAM, אבל לא מאפשרת להן להשתמש במעבד. אם מערכת Android קובעת שאפליקציה לא צריכה לבצע פעולות אבל יכול להיות שיהיה בה צורך בעתיד, היא מקפיאה את תהליך האפליקציה במקום לסיים אותו. כך נמנעת הפעלה במצב התחלתי (cold start) כשצריך להשתמש באפליקציה שוב.
מערכת Android מקפיאה אפליקציות שנשמרו במטמון על ידי העברת התהליכים שלהן לקבוצת בקרה (cgroup) קפואה. הפעולה הזו מפחיתה את צריכת ה-CPU בזמן פעילות ובזמן השבתה, כשיש אפליקציות פעילות במטמון. אפשר להפעיל את ההקפאה של האפליקציות באמצעות דגל הגדרת מערכת או אפשרות למפתחים.
ב-Android 14 (רמת API 34) ומעלה, ההקפאה של אפליקציות שנשמרו במטמון כוללת את ההתנהגויות החזקות הבאות:
- תהליכי האפליקציה במצב שמור במטמון מוקפאים 10 שניות אחרי הכניסה למצב שמור במטמון.
- המערכת מבטלת את ההקפאה של תהליך של אפליקציה שהוקפא באופן מיידי במהלך אירוע במחזור החיים. האירועים האלה כוללים קבלת intent, הפעלה של שירות job או חידוש של פעילות על ידי המשתמש.
ActivityManagerService מנהל את כל תהליכי האפליקציה ומקבל החלטות לגבי מחזור החיים של האפליקציה. CachedAppOptimizer אחראי להקפאת תהליך האפליקציה.
כשקורה מצב של הקפאת תהליך של אפליקציה, כל השרשורים שלה מושעים ולא יכולים לבצע עבודה של יחידת העיבוד המרכזית (CPU) עד שההקפאה מבוטלת. כתוצאה מכך, האפליקציה לא יכולה לבצע איסוף אשפה (GC) ולא יכולה להגיב לאירועים של ניהול זיכרון. פרטים נוספים זמינים במאמר ComponentCallbacks2.onTrimMemory(int). כדי להתאים את המערכת לשינוי הזה, החל מ-Android 14:
- אפליקציות עם מופע גלוי של
Activityמקבלות הודעה עלTRIM_MEMORY_UI_HIDDENברגע שהן עוברות לרקע. אפליקציות שנשארות במחזור חיים בלי ממשק משתמש, כמו אפליקציות עם שירות שפועל בחזית, עשויות לקבלTRIM_MEMORY_BACKGROUND. אירועים אחרים של גיזום לא מועברים, כי כשהאפליקציות עומדות בדרישות לאירועים האלה, מצפים שהן יוקפאו. - זמן קצר אחרי הכניסה למצב המאוחסן במטמון, יכול להיות שהמערכת תבקש מסביבת זמן הריצה של האפליקציה לבצע איסוף אשפה (GC) כהכנה למצב קפוא פוטנציאלי.
- כשמעבדים אפליקציה קפואה, יכול להיות שיתבצעו שלבים נוספים של דחיסת זיכרון, כמו כתיבת דפים מלוכלכים לאחסון גיבוי והחלפת דפים אנונימיים ל-ZRAM.
- אם כל התהליכים של אפליקציה מסוימת מוקפאים, המערכת מסיימת את כל שקעי ה-TCP הפעילים שהאפליקציה מנהלת. כך נמנעת שליחה של פינגים של TCP keepalive מצד השרת של השקע, שיכולים להפעיל את המודם של המכשיר.
תהליכי אפליקציה שמאוחסנים בקובץ שמור מופשרים כשמצב התהליך שלהם משתנה ממצב שמור למצב חשיבות גבוה יותר. כדי לצמצם את מספר האירועים של ביטול ההקפאה ב-Android 14 ובגרסאות מתקדמות יותר, המערכת מכניסה לתור שידורים רשומים בהקשר בזמן שהאפליקציה במצב שמור במטמון. שידורים שרשומים בהקשר הם מקלטים שאפליקציה רושמת באופן דינמי על ידי קריאה ל-Context.registerReceiver. המערכת תעביר את השידורים האלה בתור רק אחרי שהאפליקציה תופשר. לעומת זאת, המערכת לא מוסיפה ל-queue שידורים שמוצהרים במניפסט.
שידורים שהוצהרו במניפסט הם מקלטים שהוצהרו באופן סטטי ב-AndroidManifest.xml באמצעות הרכיב <receiver>. המערכת מבטלת את ההקפאה של האפליקציה ששמורה במטמון באופן מיידי כדי להעביר שידורים שהוגדרו במניפסט.
השפעה על תקינות המערכת
מערכת Android מפסיקה את התהליך של האפליקציה שפחות נעשה בה שימוש מבין האפליקציות שמאוחסנות במטמון, אם יש יותר מ-MAX_CACHED_PROCESSES תהליכים של אפליקציות שמאוחסנות במטמון. במכשירים נתמכים עם Android מגרסה 14 ואילך, הערך של MAX_CACHED_PROCESSES גדל באופן משמעותי, כך שהמכשירים יכולים לשמור ב-RAM הרבה יותר תהליכי אפליקציות במטמון.
שמירה של יותר אפליקציות במטמון בזיכרון ה-RAM מובילה לצמצום של עד 30% בהפעלות במצב התחלתי, והצמצום הזה גדל בהתאם לזיכרון ה-RAM הכולל של המכשיר. במקביל, צריכת המעבד (CPU) על ידי אפליקציות שמורות במטמון מצטמצמת, וכך מתאפשר חיסכון משמעותי בסוללה.
פטורים ממס על מקפיאים
בתנאים מסוימים, תהליך של אפליקציה עשוי להיכנס למצב שמור במטמון אבל להישאר לא קפוא. הפטורים האלה הם פרטי הטמעה ועשויים להשתנות בגרסאות עתידיות של Android:
- נעילות קבצים: אם תהליך שנשמר במטמון מחזיק בנעילת קובץ שחוסמת תהליכים אחרים שלא נשמרו במטמון, התהליך שמחזיק בנעילה לא מוקפא.
BIND_WAIVE_PRIORITYכריכות: תהליכי אפליקציה עם כריכות נכנסות שנוצרו באמצעותContext.BIND_WAIVE_PRIORITYיכולים להיכנס למצב שמור במטמון, אבל הם לא יוקפאו עד שגם כל תהליכי הלקוח המחוברים יישמרו במטמון. הפטור הזה תומך באפליקציות מרובות תהליכים, כמו דפדפני אינטרנט שמשתמשים בכרטיסיות בהתאמה אישית.
הטמעה של הקפאת אפליקציות
הכלי להקפאת אפליקציות שמורות במטמון מסתמך על הכלי להקפאה cgroup v2 של ליבת המערכת. אפשר להפעיל את התכונה במכשירים שנשלחים עם ליבת תאימות. מפעילים את האפשרות למפתחים השהיית הביצוע של אפליקציות שנשמרו במטמון או מגדירים את דגל ההגדרה של המכשיר activity_manager_native_boot use_freezer לערך true. לדוגמה:
adb shell device_config put activity_manager_native_boot use_freezer true && adb rebootההקפאה מושבתת כשמגדירים את הדגל use_freezer לערך false או כשמשביתים את האפשרות למפתחים. לדוגמה:
adb shell device_config put activity_manager_native_boot use_freezer false && adb rebootאפשר לשנות את ההגדרה הזו על ידי שינוי הגדרת המכשיר בגרסת תוכנה או בעדכון.
כדי לבטל את ההגדרה של MAX_CACHED_PROCESSES, למשל כדי להגדיר את הערך 1, 024 לצורך בדיקה:
adb shell device_config put activity_manager max_cached_processes 1024
adb shell device_config set_sync_disabled_for_tests persistentכדי לבטל את ההגדרה של MAX_CACHED_PROCESSES:
adb shell device_config delete activity_manager max_cached_processes
adb shell device_config set_sync_disabled_for_tests noneההקפאה של האפליקציות לא חושפת ממשקי API רשמיים ואין לה לקוח ייחוס להטמעה, אבל היא משתמשת בממשקי ה-API הנסתרים של המערכת setProcessFrozen
כדי להקפיא תהליך ספציפי וenableFreezer כדי להפעיל או להשבית את ההקפאה באופן גלובלי.
טיפול בתכונות מותאמות אישית
תהליכי אפליקציה לא אמורים לבצע פעולות כשהם במטמון, אבל יכול להיות שלחלק מהאפליקציות יש תכונות מותאמות אישית שנתמכות על ידי תהליכים שאמורים לפעול כשהם במטמון. כשהקפאת האפליקציות מופעלת במכשיר שמופעלות בו אפליקציות כאלה, התהליכים שנשמרו במטמון מוקפאים, ויכול להיות שהדבר ימנע את הפעלת התכונות המותאמות אישית.
כפתרון עקיף, אפשר לשנות את סטטוס התהליך ללא שמירה במטמון לפני שהתהליך צריך לבצע עבודה כלשהי. השינוי הזה מאפשר לאפליקציות להישאר פעילות. דוגמאות לסטטוסים פעילים כוללות שירות בחזית שמוגבל או סטטוס בחזית.
מצבי כשל נפוצים
כשמשהים תהליכים של אפליקציות, תקשורת לא תקינה בין תהליכים (IPC) או תזמון משימות עלולים לגרום לסגירת האפליקציות או להתנהגות לא צפויה.
טרנזקציות סינכרוניות של Binder לתהליכים קפואים
כשבתהליך של אפליקציית לקוח נשלחת עסקה סינכרונית של Binder לתהליך של אפליקציית שרת שמוקפא, המערכת מסיימת מיד את התהליך של אפליקציית השרת. כך נמנעת חסימה של השרשור של הלקוח לזמן בלתי מוגבל בזמן ההמתנה לתגובה מהשרת הקפוא. השרשור של הלקוח מקבל את ההודעה RemoteException, וכל מאזין רשום מופעל. פרטים נוספים זמינים במאמר IBinder.linkToDeath.
הסיבה הבסיסית: הכשל הזה נגרם בדרך כלל בגלל באג באפליקציית הלקוח. כשלקוח מבצע קישור לשירות, תהליך השרת מקושר ללקוח ולא יכול להיכנס למצב המאוחסן במטמון לפני שהלקוח עושה זאת. פרטים נוספים זמינים במאמר Context.bindService. עם זאת, אחרי שהלקוח שולח קריאה ל-Context.unbindService, תהליך השרת יכול להיכנס למטמון ולהיות קפוא. אם הלקוח ימשיך להשתמש בהפניה IBinder שנשמרה במטמון אחרי ביטול הקישור, הוא עלול לתקשר עם תהליך קפוא.
כדי למנוע את הבעיה הזו, חשוב לוודא שאפליקציות הלקוח מבטלות את ההפניות IBinder מיד אחרי הקריאה ל-Context.unbindService.
גלישת חוצץ בטרנזקציה אסינכרונית של Binder
כשתהליך של אפליקציית שרת מקבל עסקאות Binder אסינכרוניות (oneway) בזמן שהוא קפוא, העסקאות נשמרות במאגר זמני לכל תהליך. אם השרת מקבל יותר מדי טרנזקציות אסינכרוניות בזמן שהוא קפוא, מאגר הנתונים גולש והמערכת מסיימת את התהליך של אפליקציית השרת.
כדי למנוע את גלישת המאגר הזו, מומלץ להימנע משליחה של יותר מדי טרנזקציות של קבצים אסינכרוניים לתהליכים שעשויים להיות במטמון או קפואים.
ביצוע חוזר של משימות מתוזמנות אחרי ביטול ההקפאה
אם אפליקציה מבצעת משימות חוזרות, היא מושעית בזמן שהתהליך קפוא. פרטים נוספים זמינים במאמר בנושא ScheduledThreadPoolExecutor.scheduleAtFixedRate או במאמר בנושא Timer.scheduleAtFixedRate. כשמבטלים את ההקפאה של התהליך, יכול להיות שההפעלות שהצטברו ירוצו במהירות זו אחר זו, כמעט ללא עיכוב.
כדי למנוע גל של ביצועים כשהאפליקציה מפסיקה להיות קפואה, משתמשים ב-scheduleWithFixedDelay במקום ב-scheduleAtFixedRate למשימות ברקע. אפשר גם להשתמש ב-WorkManager.
בדיקה ופתרון בעיות בהקפאת אפליקציות
כדי לוודא שהכלי להקפאת אפליקציות פועל כמו שצריך או כדי לפתור בעיות שקשורות להקפאה, משתמשים בפקודות ובכלי האבחון הבאים:
פקודות של מנהל הפעילויות
אפשר להשתמש בפקודות adb shell am כדי לשלוט ידנית בהקפאה ובדחיסה של תהליך מסוים:
כדי להקפיא תהליך:
adb shell am freeze <process>כדי לבטל את ההקפאה של תהליך:
adb shell am unfreeze <process>כדי לכפות דחיסה מלאה של הזיכרון בתהליך מסוים:
adb shell am compact full <process>
בדיקה של Logcat
אפשר לצפות ב-logcat כדי לראות רשומות קפואות ולא קפואות בכל פעם שתהליך עובר אל ה-freezer או יוצא ממנו:
adb logcat | grep -i "\(freezing\|froze\)"פלט של יומני סיבות לביטול ההקפאה של ערכים ממוספרים מתוך UnfreezeReason פרוטוקול enum של buffer.
בדיקת Dumpsys
כדי לבדוק את רשימת התהליכים שהוקפאו, משתמשים בפקודה dumpsys activity:
adb shell dumpsys activity | grep -A 20 "Apps frozen:"בודקים אם הקובץ /sys/fs/cgroup/uid_0/cgroup.freeze קיים.
ApplicationExitInfo
כדי לשאול מה הסיבה לסיום תהליך קודם, אפשר לעיין במאמר בנושא ActivityManager.getHistoricalProcessExitReasons.
אם תהליך של אפליקציה הסתיים בגלל בעיה שקשורה להקפאה, כמו קבלת טרנזקציית binder סינכרונית בזמן שהאפליקציה הייתה מוקפאת, סיבת היציאה מוגדרת כ-ApplicationExitInfo.REASON_FREEZER.
מעקב באמצעות Perfetto
אירועים שקשורים ל-Freezer מועברים למסלול שנקרא Freezer בתהליך system_server ב-Perfetto traces:
- הפרוסות
Freezeו-Unfreezeמציינות מתי המצב של תהליך משתנה. - אירועים מסוג
updateAppFreezeStateLSPמוצגים כשהשרת של המערכת בוחן מחדש את מאפייני התהליך כדי לקבל החלטות לגבי הקפאה או ביטול הקפאה.
אפשר לבדוק את האירועים האלה ישירות בממשק המשתמש של Perfetto או לנתח אותם באמצעות PerfettoSQL:
INCLUDE PERFETTO MODULE slices.with_context;
SELECT *
FROM process_slice
WHERE process_name = "system_server"
AND track_name = "Freezer"
AND (name LIKE "Freeze %" OR name LIKE "Unfreeze %");
בספרייה הרגילה של PerfettoSQL, אירועי ההקפאה מסוכמים גם בטבלה android_freezer_events.