Оптимизация потребления оперативной памяти мобильного приложения
iOS убивает приложения молча — без краша, без OutOfMemoryError. Пользователь свернул, открыл, а данные пропали — приложение перезапустилось. Android присылает onLowMemory и onTrimMemory, но если разработчик их игнорирует — процесс тоже умирает. Избыточное потребление памяти — это не только crashes, это degraded UX каждый день.
Где утекает память: Android
Android Memory Profiler в Android Studio — обязательный инструмент. Снимаем heap dump, сортируем по retained size, ищем неожиданно большие объекты или классы, которых должно быть несколько, а их тысячи.
Bitmap утечки. До Glide и Coil разработчики вручную декодировали Bitmap и держали их в статических полях или Map. Сейчас эта проблема встречается реже, но Glide неправильно использованный может кэшировать полноразмерные изображения вместо downsampled. Правило: override(width, height) в Glide-запросах для ImageView, размер которых заранее известен. Bitmap размером 2048x2048 для аватарки 48dp — это 16 МБ впустую.
Fragment/Activity утечки через анонимные классы. Handler, Runnable, лямбды с захватом this — классические паттерны, которые держат ссылку на Activity после её уничтожения. LeakCanary находит такие утечки автоматически в debug-сборке. Обязательно включаем в CI.
ViewModel с неотписанными наблюдателями. LiveData наблюдатели, привязанные к LifecycleOwner, автоматически отписываются. Но прямые подписки на Flow без collectAsStateWithLifecycle или без явного Job.cancel при onDestroy создают утечки.
RecyclerView с большими payload. ListAdapter + DiffUtil — правильный подход, но если Adapter хранит полный список в поле, а не только текущий видимый диапазон — это лишняя память. Paging 3 решает это для длинных списков.
Где утекает память: iOS
Xcode Memory Graph Debugger — главный инструмент. Показывает retain cycles визуально. Instruments → Allocations — для отслеживания роста памяти во времени.
Retain cycles в closure. [weak self] в замыканиях, которые захватывают self — это не перестраховка, это необходимость для любых замыканий, живущих дольше функции. Особенно коварны цепочки: ViewModel → Closure → ViewController → ViewModel.
NSCache без лимитов. NSCache автоматически чистится при memory pressure, но если не задать countLimit и totalCostLimit — он может вырасти до нескольких сотен МБ до первого предупреждения.
Изображения в UIImageView без downsampling. UIImage(named:) кэширует изображение и не освобождает его. UIImage(contentsOfFile:) не кэширует, но требует ручного управления. Для больших изображений — ImageIO с kCGImageSourceShouldCacheImmediately = false и downsampling через CGImageSourceCreateThumbnailAtIndex.
NotificationCenter наблюдатели без removeObserver. В Obj-C это crash, в Swift (до iOS 9) тоже. С iOS 9+ block-based observers самоочищаются, но selector-based нет. В Swift комбо deinit { NotificationCenter.default.removeObserver(self) } — обязателен для UIKit.
Flutter и React Native
В Flutter утечки памяти чаще всего связаны с StreamSubscription без cancel() и AnimationController без dispose(). Dart DevTools → Memory показывает граф объектов. Типичная ловушка: StatefulWidget создаёт StreamSubscription в initState, но в dispose его не отменяет — каждое пересоздание виджета добавляет новую подписку.
В React Native основная проблема — накопление native объектов при навигации без правильного unmount. react-navigation с unmountOnBlur: true для тяжёлых экранов решает часть проблем. Flipper с Memory plugin показывает нативную память отдельно от JS heap.
Процесс оптимизации
| Этап | Инструмент | Цель |
|---|---|---|
| Baseline measurement | Android Profiler / Xcode Instruments | Зафиксировать текущее потребление |
| Heap dump analysis | MAT (Android) / Memory Graph (iOS) | Найти retain cycles и unexpected retentions |
| Stress testing | Monkey / XCUITest | Выявить утечки при длительной работе |
| LeakCanary / Instruments | CI integration | Не допустить регрессию |
Целевые значения зависят от типа приложения: простой CRUD — 50-80 МБ в норме, медиаплеер или карты — 150-200 МБ приемлемо.
Срок работы — одна-три недели: неделя на диагностику и профилирование, одна-две на исправления и верификацию.







