תהליכי אפליקציות ב-Android לא מתבצעים בבידוד. אפליקציות מסתמכות לעיתים קרובות על שירותים שמסופקים על ידי אפליקציות אחרות או על ידי המערכת עצמה. כשמבצעים חיבור של תהליך אחד לתהליך אחר באמצעות Service Binding, נוצרת תלות שמשפיעה באופן משמעותי על האופן שבו מסגרת Android מנהלת את הזיכרון.
מצבי תהליך וציוני OOM
במסגרת Android נעשה שימוש במצבי תהליך כדי לעקוב אחרי החשיבות של כל תהליך שפועל. לאחר מכן, המערכת משתמשת במצבים האלה כדי להקצות ערך של OomAdjusterOOM Score Adjustment (oom_score_adj), בטווח של -1000 עד 1000.
ערך נמוך יותר של oom_score_adj מציין שהתהליך חשוב יותר ופחות סביר ש-Low Memory Killer (LMK) יפסיק אותו.
מצבי עיבוד נפוצים
בטבלה הבאה מוצגים כמה ממצבי התהליך הנפוצים ביותר והערכים האופייניים
oom_score_adj שלהם. רשימה מלאה ועדכנית זמינה ב-android.app.ActivityManager וב-com.android.server.am.psc.Constants בקוד המקור של Android.
| מצב התהליך (קיצור) | תיאור | oom_score_adj רגיל |
|---|---|---|
| PER (קבוע) | תהליכי מערכת שתמיד צריכים לפעול (לדוגמה, שיחות טלפון). | -800 |
| TOP | התהליך שבו המשתמש מקיים אינטראקציה כרגע. | 0 |
| VIS (גלוי) | התהליך כולל פעילות גלויה (למשל, מאחורי תיבת דו-שיח שקופה). | 100 |
| PERC (ניתן לתפיסה) | תהליך ברקע שהמשתמש מודע לו (למשל, הפעלת מוזיקה). | 200 |
| FGS | תהליך אירוח של שירות שפועל בחזית. | 0 עד 200 (משתנה) |
| BTOP (Bound Top) | תהליך שמוגבל על ידי אפליקציית TOP. | 100 |
| BFGS | שירות שפועל בחזית ומקושר (בדרך כלל מקושר למערכת). | 0 |
| PREV (הקודם) | התהליך האחרון שהמשתמש היה בו לפני התהליך הנוכחי. | 700 |
| CACHED | אפליקציות שפועלות ברקע שאפשר להפסיק את הפעולה שלהן בבטחה. | 900 עד 999 |
ההשפעה של כבילת שירות
כשלקוח (למשל, אפליקציה במצב TOP) נקשר לשירות בתהליך שרת, תהליך השרת לרוב יורש עדיפות גבוהה יותר. כך השירות נשאר זמין כל עוד הלקוח צריך אותו.

שליטה בהורשה באמצעות סימוני BIND
הורשה היא התנהגות ברירת המחדל כשמשתמשים ב-Context.BIND_AUTO_CREATE.
עם זאת, מפתחים יכולים לקבוע איך הקישור ישפיע על חשיבות תהליך היעד באמצעות דגלים שונים ב-bindService().
סימוני BIND חשובים לציון OOM
הדגלים הבאים רלוונטיים במיוחד כשמנהלים את העומס על הזיכרון בכל המערכת:
-
BIND_AUTO_CREATE: הסימון הנפוץ ביותר. היא מוודאת שתהליך השירות יתחיל וימשיך לפעול כל עוד הקישור קיים. כברירת מחדל, הוא גם מעלה את עדיפות התהליך בשרת כך שתהיה זהה לזו של הלקוח. -
BIND_NOT_FOREGROUND: מונע את העלאת התהליך של שירות היעד לרמת העדיפות של תזמון (עדיפות CPU) בחזית. עם זאת, עדיין אפשר להעלות את העדיפות של הזיכרון (oom_score_adj). האפשרות הזו שימושית לעבודות ברקע שלא צריכות להתחרות עם ממשק המשתמש על מחזורי CPU, אבל עדיין צריכות להיות מוגנות מפני סגירה. -
BIND_WAIVE_PRIORITY: דגל חזק מאוד שמורה למערכת לא להשפיע על התזמון או על העדיפות של ניהול הזיכרון של תהליך היעד. תהליך השירות ינוהל כאילו היה תהליך רגיל ברקע ברשימת ה-LRU, כך שהוא יהיה מועמד להפסקת פעולה בגלל חוסר זיכרון (OOM), גם כשהוא מאוגד. -
BIND_ABOVE_CLIENT: מציין שהשירות חשוב יותר מאפליקציית הלקוח עצמה. כשהמערכת צריכה לפנות זיכרון, היא תעדיף להפסיק את פעולת אפליקציית הלקוח לפני שהיא תפסיק את פעולת השירות המצורף. ההצפנה הזו 'חזקה' יותר מ-BIND_AUTO_CREATEכי היא מספקת שכבת הגנה נוספת לשירות על חשבון הלקוח. -
BIND_NOT_PERCEPTIBLE: מוריד את רמת החשיבות של שירות היעד לרמה נמוכה מ-PERCEPTIBLE, וכך המערכת יכולה לפנות זיכרון כדי לפנות מקום לתהליכים קריטיים יותר שהמשתמש יכול לראות.
תרגול מעשי: צפייה באפקטים של קישור
נשתמש באפליקציית MemoryLab כדי להדגים איך קישור מאפליקציית TOP משפיע על המצב של תהליך נפרד.
1. הפעלת MemoryLab
הפקודה הבאה מפעילה את האפליקציה. אחרי שהיא נפתחת, מוודאים שהיא נשארת בחזית (לא לוחצים על הלחצן הראשי ולא עוברים לאפליקציות אחרות).
adb shell am start -n com.android.memorylab/.MainActivity
2. זיהוי תהליכים
לפני שמבצעים את הקישור, צריך לבדוק את מצבי התהליך. ממשק המשתמש הראשי של MemoryLab פועל בתהליך אחד, ויש לו RemoteService שפועל בתהליך :remote.
adb shell dumpsys activity processes com.android.memorylab
דוגמה לקטע קוד של פלט:
Process OOM control (154 total, non-act at 7, non-svc at 7):
Proc #0: fg T/A/TOP LCMNFUATI t: 0 13470:com.android.memorylab/u0a417 (top-activity)
oom: max=1001 curRaw=0 setRaw=0 cur=0 set=0
state: cur=TOP set=TOP lastRss=0.00 lastCachedRss=0.00
התהליך הראשי com.android.memorylab יופיע במצב TOP. תהליך :remote עדיין לא התחיל.
3. קישור של טריגר
שולחים שידור לאפליקציה כדי להפעיל את כבילת השירות:
adb shell am broadcast -a com.android.memorylab.LEAK_BINDER
4. התבוננות במצב מוגבר
בודקים שוב את מצבי התהליך:
adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"
דוגמה לקטע קוד של פלט:
Proc # 1: vis F/ /BTOP ---NFUATI t: 0 13560:com.android.memorylab:remote/u0a417 (service)
com.android.memorylab/.RemoteService<=Proc{13470:com.android.memorylab/u0a417}
oom: max=1001 curRaw=100 setRaw=100 cur=100 set=100
state: cur=BTOP set=BTOP lastRss=0.00 lastCachedRss=0.00
תהליך :remote פועל עכשיו במצב BTOP (Bound TOP) עם ערך oom_score_adj של 100. ההגנה הזו משמעותית יותר מאשר בשירות הפועל ברקע רגיל (שיהיה ברמה 500 ומעלה). הסימון
<=Proc{...} מציין איזה תהליך אחראי להעלאת העדיפות הזו.
5. העברה לרקע
לוחצים על הלחצן HOME במכשיר. בודקים שוב את המדינות:
adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"
דוגמה לקטע קוד של פלט:
Proc # 2: prev b/ /LAST --------I t: 0 13560:com.android.memorylab:remote/u0a417 (service)
com.android.memorylab/.RemoteService<=Proc{13470:com.android.memorylab/u0a417}
oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
state: cur=LAST set=LAST lastRss=0.00 lastCachedRss=0.00
Proc # 1: prev b/ /LAST --------I t: 0 13470:com.android.memorylab/u0a417 (previous)
oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
state: cur=LAST set=LAST lastRss=209MB lastCachedRss=0.00
עכשיו שני התהליכים עברו למצב עדיפות נמוכה יותר (PREV /
oom_score_adj 700), כי תהליך הלקוח כבר לא TOP. (הערה:
LAST ב-state dump מתייחס למצב הפנימי LAST_ACTIVITY, שמוצג כ-PREV בסיכומים ברמה גבוהה).
ניתוח באמצעות procstats
הכלי procstats מספק תצוגה היסטורית של המצבים האלה.
# View stats for MemoryLab over the last hour
adb shell dumpsys procstats --hours 1 com.android.memorylab
דוגמה לקטע קוד של פלט:
* com.android.memorylab / u0a417 / v37:
* Prc com.android.memorylab / u0a417 / v37:
TOTAL: 0.89% (0.00-0.00-0.00/0.00-0.00-0.00/210MB-210MB-210MB over 1)
Top: 0.89% (0.00-0.00-0.00/0.00-0.00-0.00/210MB-210MB-210MB over 1)
* Prc com.android.memorylab:remote / u0a417 / v37:
TOTAL: 0.19%
Bnd Top: 0.19%
כאן, Bnd Top מציין את אחוז הזמן שבו התהליך המרוחק היה קשור לאפליקציה במצב TOP.
איך לוכדים ומנתחים קשירות באמצעות Perfetto
dumpsys מציג תמונת מצב, אבל Perfetto מאפשר לכם לראות בדיוק מתי מתבצעת ההתאמה ואיך משתנה ציון ה-OOM בזמן אמת.
1. תיעוד עקבות
משתמשים בהגדרה שכוללת את linux.process_stats ואת קטגוריית ה-atrace am:
adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/service_bindings.perfetto-trace <<EOF
buffers: { size_kb: 65536 }
data_sources: {
config {
name: "linux.process_stats"
process_stats_config { proc_stats_poll_ms: 100 }
}
}
data_sources: {
config {
name: "linux.ftrace"
ftrace_config { ftrace_events: "am/am_proc_bound" }
}
}
duration_ms: 15000
EOF
2. מעברים של ציוני OOM בשאילתות
באמצעות PerfettoSQL, אפשר לראות איך השתנה ציון ה-OOM של התהליך המרוחק ביחס לתהליך של ממשק המשתמש:
SELECT ts, p.name, value AS oom_score_adj
FROM counter c
JOIN process_counter_track t ON c.track_id = t.id
JOIN process p USING (upid)
WHERE p.name LIKE 'com.android.memorylab%'
AND t.name = 'oom_score_adj'
ORDER BY ts;
3. זיהוי אירועי שיוך
כדי לראות בדיוק מתי נוצר קשר בין תלות, ואיזה תהליך יזם אותו, משתמשים בשאילתה הזו:
SELECT
s.ts,
p.name AS process_name,
t.name AS thread_name,
s.name AS slice_name
FROM slice s
JOIN thread_track tt ON s.track_id = tt.id
JOIN thread t USING (utid)
JOIN process p USING (upid)
WHERE s.name LIKE 'bindService:{com.android.memorylab%';
קישורים בין המערכת לאפליקציה
מערכת Android עצמה מתחברת לעיתים קרובות לשירותים באפליקציות של צד שלישי כדי לספק פונקציונליות בסיסית. לרוב, המטרה של הקישורים האלה היא הפחתת זמן האחזור. כשהתהליך פעיל ונמצא בזיכרון, המערכת נמנעת מהתקורה היקרה של 'הפעלה במצב התחלתי (cold start)' (טעינת ה-APK, הפעלת זמן הריצה ויצירת אובייקט האפליקציה) כשמתרחשת אינטראקציה קריטית של המשתמש. קיימים קשרים אחרים כדי למנוע הפעלות במצב התחלתי (cold start) בתדירות גבוהה מדי באפליקציות שצריכות לטפל בזרמים של אירועים ברקע.
הנה כמה דוגמאות מהחיים האמיתיים שאפשר לראות במכשיר טיפוסי:
VoiceInteractor
המשתמשים מצפים שהעוזר הדיגיטלי יהיה מוטמע במערכת ההפעלה של הטלפון, כך שיוכלו להפעיל אותו באופן מיידי באמצעות מילת הפעלה או תנועת קלט מהירה, ושהאינטראקציה תהיה חלקה ורציפה.
כשמתרחשת הפעלה של העוזר הדיגיטלי (למשל, מילת ההפעלה "Ok Google" בטלפונים של Google Pixel), העוזר הדיגיטלי צריך להגיב באופן מיידי. כדי להבטיח זאת, system_server שומר על קשר קבוע עם שירות האינטראקציה הקולית שנבחר על ידי המשתמש.

אם בודקים את מצבי התהליך (לדוגמה, באמצעות dumpsys activity processes), יכול להיות שיוצג תהליך כמו com.google.android.googlequicksearchbox:interactor במצב BFGS (Bound Foreground Service), שמופעל על ידי קישור מ-system_server (UID 1000).
NotificationListenerService
בחלק מהקישורים בין המערכת לאפליקציה, המטרה היא לא השהיה, אלא מניעת הפעלות במצב התחלתי (cold start) בתדירות גבוהה.
NotificationListenerService, שירות שמקבל שיחות מהמערכת כשמתפרסמות או מוסרות התראות חדשות, הוא דוגמה מצוינת. משתמשים בסמארטפון עשויים לקבל מאות התראות במהלך היום. אם המערכת מבטלת את הקישור של מאזין התראות, סביר להניח שהתהליך של האפליקציה הזו יעבור למצב מטמון, ויכול להיות שהוא יופסק על ידי LMK.
כשמגיעה ההתראה הבאה – יכול להיות שזה יקרה כמה שניות אחר כך – המערכת תיאלץ להפעיל מחדש את התהליך של האפליקציה במצב התחלתי (cold start) כדי להעביר את האירוע. המחזור הקבוע הזה של סגירה והפעלה מחדש יצרוך הרבה יותר CPU וסוללה מאשר פשוט להשאיר את התהליך קשור ופעיל ברקע.
המסך '-1' (פיד חדשות) במפעיל האפליקציות
אפליקציות מודרניות של מרכזי אפליקציות משלבות בדרך כלל את פונקציונליות הניווט הבסיסית (סמלי דף הבית ווידג'טים) עם פיד חדשות שזמין באחד מהמסכים של מרכז האפליקציות ומשולב בצורה חלקה בממשק המשתמש של מרכז האפליקציות. יכול להיות שפיד החדשות מסופק על ידי אפליקציה אחרת. לדוגמה, ב-Google Pixel, מרכז האפליקציות משולב עם פיד שמסופק על ידי אפליקציית Google.
כשמחליקים שמאלה במסך הבית כדי לראות את עדכוני החדשות, המעבר צריך להיות חלק. מרכז האפליקציות עושה זאת על ידי קישור לממשק שירות באפליקציה שמספקת את עדכוני החדשות, ושמירה על הקישור הזה פעיל כל עוד מרכז האפליקציות פעיל. כך התוכן בפיד מוצג ומוכן בזיכרון גם כשלא מסתכלים עליו.
דוגמאות נפוצות אחרות
- מרכז האפליקציות (HOME_APP_ADJ): לאפליקציית מרכז האפליקציות (Home) יש משבצת מיוחדת משלה ברשימת העדיפויות. למרות שלא תמיד מוגבל על ידי שירות, הוא מוקצה ל-
HOME_APP_ADJ(בדרך כלל 600). המערכת מעדיפה להשאיר את מרכז האפליקציות פעיל, כי המשתמש חוזר אליו לעיתים קרובות. למעשה, המערכת תעדיף להפסיק את הפעולה של האפליקציה שהייתה בשימוש קודם (PREV_APP_ADJ = 700) מאשר להפסיק את הפעולה של מרכז האפליקציות, כי הפסקת הפעולה של מרכז האפליקציות תוביל לחוויית משתמש איטית כשיוצאים מכל אפליקציה, כי המשתמש יצטרך לחכות עד שמרכז האפליקציות יופעל מחדש. - עורך שיטות קלט (IME): כשמקלידים, המערכת מתחברת לאפליקציית המקלדת שבחרתם (למשל, Gboard). כך תהליך המקלדת נשאר במצב מורם גם אם המקלדת מוסתרת באופן זמני. כך המקלדת תופיע שוב באופן מיידי כשמקישים על שדה טקסט אחר.
- תשלומים באמצעות NFC: כשמצמידים את הטלפון למסוף כדי לשלם, המערכת מתחברת לשירות התשלומים באמצעות NFC (למשל, Google Wallet). לרוב, העסקאות האלה כוללות דרישות מחמירות לביצוע בזמן אמת ממסוף הסוחר. אם אפליקציית התשלום נאלצה לבצע הפעלה במצב התחלתי (cold start), יכול להיות שהעסקה תסתיים בטיימ-אאוט ולא תושלם.
הפשרות הנדרשות והירידה החדה בביצועים
הקישורים חיוניים לביצועים ולדיוק, אבל הם פוגעים בזיכרון של המערכת.
- גמישות מופחתת: כל תהליך שמוגבל הוא תהליך ש-LMK לא יכול להפסיק בקלות. כך מצטמצם ה'מרווח' של תהליכים במטמון שהמערכת יכולה להשתמש בהם כדי לפנות זיכרון במצב של עומס.
- החמרה של ירידה חדה בביצועים: אם יש יותר מדי תהליכים קשורים, יכול להיות שבמערכת לא יישארו תהליכים ברקע שאפשר להפסיק. כשהעומס על הזיכרון גדל, המערכת תגיע לנקודת השבירה הרבה יותר מהר, כי היא נאלצת להפסיק תהליכים חשובים יותר או להשתמש במטמון הדפים באופן לא יעיל.
← רשות מוניציפאלית | ↑ למעלה | בכל המערכת →