Optimizing Mobile App RAM Usage
iOS silently kills apps — without crash, without OutOfMemoryError. User closes, opens again, and data is gone — app restarted. Android sends onLowMemory and onTrimMemory, but if developer ignores them — process dies too. Excessive memory consumption is not only crashes, it's degraded UX every day.
Where Memory Leaks: Android
Android Memory Profiler in Android Studio is mandatory tool. Take heap dump, sort by retained size, look for unexpectedly large objects or classes that should be few but are thousands.
Bitmap leaks. Before Glide and Coil, developers manually decoded Bitmap and kept in static fields or Map. This problem is rarer now, but incorrectly used Glide can cache full-size images instead of downsampled. Rule: override(width, height) in Glide requests for ImageView with known size. 2048x2048 Bitmap for 48dp avatar — 16 MB wasted.
Fragment/Activity leaks via anonymous classes. Handler, Runnable, lambdas capturing this — classic patterns holding reference to Activity after destruction. LeakCanary auto-finds these in debug build. Must enable in CI.
ViewModel with unsubscribed observers. LiveData observers bound to LifecycleOwner auto-unsubscribe. But direct Flow subscriptions without collectAsStateWithLifecycle or without explicit Job.cancel on onDestroy create leaks.
RecyclerView with large payloads. ListAdapter + DiffUtil — correct approach, but if Adapter stores full list in field not just visible range — extra memory. Paging 3 solves it for long lists.
Where Memory Leaks: iOS
Xcode Memory Graph Debugger — main tool. Shows retain cycles visually. Instruments → Allocations — for tracking memory growth over time.
Retain cycles in closure. [weak self] in closures capturing self — not overkill, it's necessity for closures living longer than function. Especially treacherous: chains like ViewModel → Closure → ViewController → ViewModel.
NSCache without limits. NSCache auto-cleans on memory pressure, but without countLimit and totalCostLimit — can grow to hundreds of MB before first warning.
Images in UIImageView without downsampling. UIImage(named:) caches image and doesn't release. UIImage(contentsOfFile:) doesn't cache but needs manual management. For big images — ImageIO with kCGImageSourceShouldCacheImmediately = false and downsampling via CGImageSourceCreateThumbnailAtIndex.
NotificationCenter observers without removeObserver. In Obj-C this is crash, in Swift (before iOS 9) too. iOS 9+ block-based observers self-clean, but selector-based don't. In Swift combo deinit { NotificationCenter.default.removeObserver(self) } — mandatory for UIKit.
Flutter and React Native
In Flutter memory leaks mostly relate to StreamSubscription without cancel() and AnimationController without dispose(). Dart DevTools → Memory shows object graph. Typical trap: StatefulWidget creates StreamSubscription in initState but doesn't cancel in dispose — each Widget recreation adds subscription.
In React Native the issue is native object accumulation during navigation without proper unmount. react-navigation with unmountOnBlur: true for heavy screens solves part of it. Flipper with Memory plugin shows native memory separately from JS heap.
Optimization Process
| Stage | Tool | Goal |
|---|---|---|
| Baseline measurement | Android Profiler / Xcode Instruments | Fix current consumption |
| Heap dump analysis | MAT (Android) / Memory Graph (iOS) | Find retain cycles and unexpected retentions |
| Stress testing | Monkey / XCUITest | Reveal leaks during long use |
| LeakCanary / Instruments | CI integration | Prevent regression |
Target values depend on app type: simple CRUD — 50-80 MB normal, media player or maps — 150-200 MB acceptable.
Work timeline: one to three weeks — one week for diagnosis and profiling, one to two for fixes and verification.







