האפליקציה לא קורסת מיד — היא גדלה בזיכרון בהדרגה. אחרי 20 דקות, מופיע פיגור קל. אחרי 40, Memory Pressure של המערכת הורג תהליכי רקע. אחרי שעה, SIGKILL מ-iOS או OOM-killer באנדרואיד מסיים את האפליקציה. המשתמש חושב שהאפליקציה גרועה. Firebase Crashlytics לא מראה כלום — זה לא קריסה, זה הרג מערכת. גם אם Memory Warning מופעל, ה-UI עלול להיות איטי בגלל ניקוי מטמון, מה שמוביל לביקורות שליליות. לפי סטטיסטיקות, ל-80% מהאפליקציות הניידות יש דליפות זיכרון, מה שגורם לאובדן של 15% משתמשים בחודש. כמפתחי מובייל עם 5 שנות ניסיון, אנחנו נתקלים במקרים כאלה מדי יום. המהנדסים שלנו מוצאים דליפות במקום שבו כלים סטנדרטיים נשארים שקטים. החיסכון בעלויות שרת יכול להגיע ל-30% אחרי תיקון דליפות. צרו קשר לייעוץ על פרופיילינג של האפליקציה שלכם.
איך לפרופייל זיכרון ב-5 שלבים
- הגדרת כלים: חברו Instruments ל-iOS, LeakCanary לאנדרואיד, ו-DevTools Memory ל-Flutter.
- תרחיש בדיקה: הגדירו מסכים ופעולות מפתח (ניווט, טעינת נתונים, טיפול בתמונות).
- איסוף נתונים: הריצו פרופיילר, בצעו את התרחיש, ותפסו תמונות מצב של ה-heap לפני ואחרי כל פעולה.
- ניתוח: חפשו אובייקטים שלא שוחררו (retain cycles, מאזינים, context). השתמשו ב-LeakCanary לזיהוי אוטומטי.
- אופטימיזציה: תקנו דליפות שזוהו (הפניות חלשות, ביטול הרשמות, סגירת cursors). הריצו את הבדיקה שוב.
הגדרה מפורטת של LeakCanary
הוסיפו דרך debugImplementation: `debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")`. אחרי ההפעלה, אם מתרחשת דליפה, מופיעה התראה עם מחסנית הקריאות המלאה.איך למצוא דליפת זיכרון ב-iOS?
שתי תבניות לניתוח זיכרון ב-Instruments:
- Allocations — כל הקצאות הזיכרון, אובייקטים חיים ומתים. Generations (כפתור Generation) מאפשרים להשוות אובייקטים לפני ואחרי פעולה. אם אובייקטים ממסך נשארים חיים אחרי סגירתו, יש דליפה.
- Leaks — גלאי אוטומטי של retain cycles. אייקון אדום = נמצא cycle. מציג גרף תלות עם האשמים.
דוגמה קלאסית ל-retain cycle ב-Swift:
// Утечка: ViewController держит closure, closure захватывает ViewController
class PhotoViewController: UIViewController {
var onPhotoLoaded: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
onPhotoLoaded = {
self.imageView.image = UIImage(named: "photo") // strong capture
}
}
}
// Правильно: onPhotoLoaded = { [weak self] in
// self?.imageView.image = UIImage(named: "photo")// }// Утечка: ViewController держит closure, closure захватывает ViewController class PhotoViewController: UIViewController { var onPhotoLoaded: (() -> Void)? override func viewDidLoad() { super.viewDidLoad() onPhotoLoaded = { self.imageView.image = UIImage(named: "photo") // strong capture } } } // Правильно: onPhotoLoaded = { [weak self] in self?.imageView.image = UIImage(named: "photo") } הוא סטנדרטי לכל closures שתופסים self באובייקטים ארוכי חיים. הכלי Leaks ימצא את זה, אבל לעתים קרובות מראה את הסימפטום, לא את הסיבה. עקבו אחרי הגרף למחסנית, וחפשו את ההפניה החזקה השורשית.
לפי Apple Instruments Documentation, שימוש ב-generations יכול לזהות עד 90% מהדליפות.
למה תמונות אוכלות זיכרון?
[weak self] שומר תמונות במטמון המערכת. טוב לאייקונים בשימוש תדיר, רע לתמונות גדולות שנטענות פעם אחת. השתמשו ב-UIImage(named:) – ללא מטמון.
פענוח תמונה מתרחש בהצגה הראשונה, לא ביצירת UIImage. פענחו מראש על thread ברקע:
func decodedImage(_ image: UIImage) -> UIImage {
UIGraphicsBeginImageContextWithOptions(image.size, true, 0)
defer { UIGraphicsEndImageContext() }
image.draw(in: CGRect(origin: .zero, size: image.size))
return UIGraphicsGetImageFromCurrentImageContext() ?? image
}אחרי קריאה זו, התמונה מפוענחת ונשמרת כ-bitmap בזיכרון. העבירו אותה ל-UI ללא עיכוב פענוח.
אנדרואיד: Memory Profiler ו-LeakCanary
Android Studio Memory Profiler מציג Heap בזמן אמת: Java Heap, Native Heap, Stack, Code, Graphics. כפתור Dump Heap שומר תמונת מצב – נתחו ב-hprof viewer או המירו ל-Eclipse Memory Analyzer.
אבל הכלי השימושי ביותר בקרב הוא LeakCanary. הוסיפו אותו ב-debugImplementation, והוא עובד אוטומטית:
// build.gradle.kts debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14") כשמתגלה דליפה, LeakCanary מציג התראה עם המחסנית המלאה: מה מחזיק מה, דרך איזו שרשרת. אין צורך לנתח heap dumps ידנית.
גורמים נפוצים לדליפות באנדרואיד:
- Context בשדות סטטיים או בסינגלטונים.
- Cursor שלא נסגר מ-ContentProvider או SQLiteDatabase.
- מאזין שלא הוסר ב-onDestroy.
השוואת כלי פרופיילינג:
| פלטפורמה | כלי | שיטת זיהוי | מורכבות |
|---|---|---|---|
| iOS | Instruments Allocations | תמונות מצב של heap עם generations | בינונית |
| iOS | Instruments Leaks | חיפוש אוטומטי של retain cycles | נמוכה |
| אנדרואיד | Memory Profiler | Dump heap + MAT | גבוהה |
| אנדרואיד | LeakCanary | ניטור אוטומטי | נמוכה |
| Flutter | DevTools Memory | תמונות מצב של קבוצות אובייקטים | בינונית |
LeakCanary מוצא דליפות פי 10 מהר יותר מניתוח ידני של heap dump. בפועל, 80% מהדליפות נובעות מ-retain cycles או ניהול context לא נכון.
דליפות טיפוסיות והגורמים שלהן:
| סוג דליפה | פלטפורמה | גורם | פתרון |
|---|---|---|---|
| Retain cycle ב-closure | iOS | לכידת self חזקה | השתמשו ב-weak self |
| Context בשדה סטטי | אנדרואיד | Activity שהועבר לסינגלטון | השתמשו ב-Application context |
| StreamSubscription שלא נסגר | Flutter | חוסר ב-cancel ב-dispose | קראו ל-cancel ב-dispose |
מה לעשות עם Cursor ו-Subscriptions?
override fun onStart() {
super.onStart()
locationManager.requestLocationUpdates(provider, 0, 0f, this)
}
override fun onStop() {
super.onStop()
locationManager.removeUpdates(this) // иначе Activity не умрёт
}תמיד סגרו Cursor בבלוק finally. הסירו הרשמות למיקום או חיישנים ב-onStop()/onPause().
Flutter: Observatory ו-DevTools Memory
Flutter DevTools → לשונית Memory — פרופיילר תמונות מצב. מציג קבוצות אובייקטים לפי סוג. Dart:core, package:myapp — חפשו מחלקות עם מספר מופעים גבוה בצורה חריגה.
דליפת Flutter טיפוסית — StreamSubscription ללא cancel():
class MyWidget extends StatefulWidget {
...
}
class _MyWidgetState extends State<MyWidget> {
late StreamSubscription _sub;
@override
void initState() {
super.initState();
_sub = someStream.listen((event) {
...
});
}
@override
void dispose() {
_sub.cancel();
// обязательно
super.dispose();
}
}בלי _sub.cancel() ב-dispose(), ההרשמה שורדת את ה-widget, ומחזיקה closure עם הפניה ל-State.
הגישה שלנו
אנחנו מבצעים פרופיילינג מלא באמצעות Apple Instruments Documentation והאתר הרשמי של LeakCanary. למהנדסים שלנו יש 5+ שנות ניסיון. אחרי הביקורת, תקבלו דוח עם דליפות ספציפיות ותיקוני קוד מוכנים. העלות מחושבת באופן אישי, אבל החיסכון מביטול דליפות לעתים קרובות מכסה אותה תוך חודש.
מה כלול:
- פרופיילינג זיכרון דרך Instruments Allocations / Memory Profiler / DevTools
- הגדרת LeakCanary לפרויקטים של אנדרואיד
- ניתוח Heap-dump וזיהוי retain cycles
- ביקורת טיפול בתמונות (מטמון, פענוח)
- בדיקת דפוסים למאזינים, הרשמות ו-closures
- דוח עם דליפות ספציפיות ותיקונים
ציר זמן: 2 עד 3 ימים תלוי בגודל האפליקציה. בקשו ייעוץ — נמצא דליפות ונציע תיקונים. צרו קשר כדי לקבל הערכה לפרויקט שלכם.







