Как решишь OutOfMemoryError
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Общая стратегия решения OutOfMemoryError (OOM)
OutOfMemoryError — критическая ошибка, указывающая на исчерпание доступной heap-памяти в виртуальной машине Dalvik/ART. Решение требует системного подхода: диагностика, идентификация утечек, оптимизация использования памяти и мониторинг.
1. Диагностика и анализ
Первым шагом является определение типа утечки и объектов, удерживающих память.
Использование профилировщиков:
- Android Studio Profiler (Memory) — основной инструмент. Записывайте Heap Dump, анализируйте распределение объектов по классам, ищите "подозрительные" активности, фрагменты, Bitmap'ы, большие массивы.
- LeakCanary — автоматическая детекция утечек. Библиотека показывает цепочки ссылок, не позволяющие объектам быть собранными GC.
Пример анализа в коде (упрощённо):
// Включение строгого режима для детекции утечек активности
if (BuildConfig.DEBUG) {
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectActivityLeaks()
.penaltyLog()
.build()
)
}
2. Основные причины и решения
A. Утечки контекста и жизненного цикла
Наиболее частая причина — неправильные ссылки на Context, Activity, View или Fragment.
Типичные сценарии:
- Синглтоны, хранящие контекст активности вместо контекста приложения.
- Коллбеки, слушатели (Listeners), не отписывающиеся при уничтожении объекта.
- Статические поля, ссылающиеся на view или контекст активности.
Решение:
// НЕПРАВИЛЬНО: статическое поле, удерживающее активность
companion object {
private var sActivity: MainActivity? = null // Утечка!
}
// ПРАВИЛЬНО: используйте WeakReference или контекст приложения
companion object {
private var sActivityRef: WeakReference<MainActivity>? = null
}
// Для синглтона используйте Application Context
class MySingleton private constructor(context: Context) {
init {
val appContext = context.applicationContext
// ...
}
}
B. Неоптимизированные Bitmap'ы
Bitmap — главный потребитель памяти. Ошибки: загрузка полноразмерных изображений без сэмплинга.
Решение:
// Используйте BitmapFactory.Options с inSampleSize
fun decodeSampledBitmapFromResource(res: Resources, resId: Int, reqWidth: Int, reqHeight: Int): Bitmap {
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeResource(res, resId, options)
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
options.inJustDecodeBounds = false
return BitmapFactory.decodeResource(res, resId, options)
}
private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
val (height, width) = options.run { outHeight to outWidth }
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight = height / 2
val halfWidth = width / 2
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}
- Всегда вызывайте bitmap.recycle() для Bitmap, созданных вручную (не через BitmapFactory).
- Используйте библиотеки (Glide, Picasso, Coil), которые автоматически управляют памятью и кэшированием.
C. Большие коллекции и кэши
- Очищайте коллекции (списки, массивы, кэши) при ненадобности.
- Используйте SoftReference или WeakReference для кэшей, если уместно.
- Реализуйте LRU-кэш (LruCache) для контроля размера.
D. Ресурсоёмкие операции в цикле или асинхронных задачах
Создание множества объектов в циклах или незавершённые AsyncTask'и.
Решение:
- Используйте пулы объектов (Object Pooling) для часто создаваемых тяжёлых объектов.
- Отменяйте AsyncTask, Coroutine, RxJava Subscription при уничтожении компонента.
3. Технические приёмы и настройки
Увеличение heap-памяти (временная мера):
В AndroidManifest.xml для конкретной активности:
<application
android:largeHeap="true">
Важно: Это маскирует проблему, а не решает её. Используйте только для memory-intensive операций (например, обработка больших изображений).
Оптимизация сборки мусора (GC):
- Избегайте создания объектов в критичных по производительности участках (onDraw, в циклах).
- Используйте примитивные типы вместо обёрток,
SparseArrayвместоHashMap<Integer, Object>.
4. Профилактика и мониторинг
- Регулярное тестирование на устройствах с малым объемом памяти.
- Мониторинг в продакшене: отправка отчетов об OOM через Firebase Crashlytics или аналоги.
- Code Review с фокусом на жизненный цикл и управление памятью.
Пример чек-листа при возникновении OOM:
- Снял Heap Dump в Android Profiler?
- Проверил LeakCanary на наличие утечек?
- Проанализировал Bitmap'ы — размер, сэмплинг, ресайзинг?
- Отписался от всех listeners/observers в onDestroy/onCleared?
- Очистил кэши и большие коллекции при onLowMemory()?
- Использовал WeakReference/SoftReference для глобальных ссылок?
Итог: Решение OOM — это не разовая операция, а культура разработки. Постоянный мониторинг, использование современных библиотек (Coroutines + Lifecycle, Jetpack), понимание жизненного цикла компонентов и профилирование — ключевые практики для стабильного приложения.