← Назад к вопросам

Какие знаешь примеры утечки памяти в Android?

2.0 Middle🔥 181 комментариев
#JVM и память#Производительность и оптимизация

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Утечки памяти в Android: основные причины и примеры

В Android-разработке утечки памяти — одна из наиболее распространённых проблем, приводящих к повышенному потреблению памяти, падениям приложения (OutOfMemoryError) и негативному пользовательскому опыту. Утечка возникает, когда объекты остаются в памяти, хотя больше не нужны, из-за сохранения на них "живых" ссылок. Вот ключевые примеры:

1. Утечки через Context

Использование не того типа Context — частая ошибка. Activity — это Context, но он имеет короткий жизненный цикл. Если передать Activity в долгоживущий объект (например, синглтон), это приведёт к утечке всей активности.

// НЕПРАВИЛЬНО: Activity передаётся в синглтон
class AppSettings private constructor(context: Context) {
    companion object {
        private var instance: AppSettings? = null
        fun getInstance(context: Context): AppSettings {
            if (instance == null) {
                instance = AppSettings(context) // Утечка, если context — Activity
            }
            return instance!!
        }
    }
}

// ПРАВИЛЬНО: использовать Application Context
class AppSettings private constructor(context: Context) {
    init {
        val appContext = context.applicationContext // Безопасный контекст
    }
}

2. Неуправляемые ссылки в статических полях

Статические поля живут всё время работы приложения. Если они ссылаются на View или Activity, эти объекты не соберутся сборщиком мусора.

class MainActivity : AppCompatActivity() {
    companion object {
        private var leakedView: TextView? = null // Статическая ссылка на View
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        leakedView = findViewById(R.id.text_view) // Утечка: View привязана к Activity
    }
}

3. Внутренние классы (inner classes) и анонимные классы

Нестатические внутренние классы неявно хранят ссылку на внешний класс. Если экземпляр внутреннего класса переживает Activity, то и Activity остаётся в памяти.

class MainActivity : AppCompatActivity() {
    private val handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            // Обработка сообщений
        }
    } // Анонимный класс — неявная ссылка на Activity

    // Решение: использовать static nested class + WeakReference
    private class SafeHandler(activity: MainActivity) : Handler(Looper.getMainLooper()) {
        private val activityRef = WeakReference(activity)
        override fun handleMessage(msg: Message) {
            activityRef.get()?.run { /* безопасная работа */ }
        }
    }
}

4. Неотменённые колбэки и слушатели (listeners)

Регистрация слушателей (например, LocationListener, SensorListener) без отмены при уничтожении Activity оставляет ссылки на него.

class MainActivity : AppCompatActivity() {
    private lateinit var sensorManager: SensorManager
    private val sensorListener = object : SensorEventListener {
        override fun onSensorChanged(event: SensorEvent) {}
        override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
    }

    override fun onResume() {
        super.onResume()
        sensorManager.registerListener(sensorListener, ...)
    }

    override fun onPause() {
        super.onPause()
        sensorManager.unregisterListener(sensorListener) // ОБЯЗАТЕЛЬНО отменить!
    }
}

5. Утечки в LiveData и Flow

LiveData, наблюдаемая из Activity, может удерживать её, если не использовать LifecycleOwner правильно. Аналогично, незакрытые CoroutineScope в ViewModel могут утекать.

class MyViewModel : ViewModel() {
    private val _data = MutableLiveData<String>()
    val data: LiveData<String> = _data

    fun loadData() {
        viewModelScope.launch { // scope автоматически отменяется при очистке ViewModel
            _data.value = fetchData()
        }
    }
}

// В Activity: observe с lifecycleOwner
viewModel.data.observe(this) { data -> } // `this` как LifecycleOwner

6. Таймеры (Timer) и потоки (Threads) без очистки

Запущенные Timer или Thread, которые ссылаются на Activity, не дадут ему собраться.

class MainActivity : AppCompatActivity() {
    private var timer: Timer? = null

    override fun onResume() {
        super.onResume()
        timer = Timer()
        timer?.schedule(object : TimerTask() {
            override fun run() {
                // Делаем что-то, ссылаясь на Activity
            }
        }, 0, 1000)
    }

    override fun onDestroy() {
        timer?.cancel() // Критично отменять!
        timer = null
        super.onDestroy()
    }
}

7. Утечки в библиотеках (например, Retrofit, Glide)

Некоторые библиотеки требуют ручного освобождения ресурсов. Например, Glide рекомендует очищать запросы в onDestroy().

override fun onDestroy() {
    Glide.with(this).clear(view) // Для отдельных View
    super.onDestroy()
}

Как обнаруживать и предотвращать утечки?

  • Инструменты: Используйте LeakCanary, Android Profiler (Memory Heap Dump), MAT (Memory Analyzer Tool).
  • Паттерны: Применяйте WeakReference для ссылок на контексты, используйте Lifecycle-aware компоненты (LiveData, ViewModel).
  • Правила: Всегда отменяйте слушатели, таймеры, асинхронные задачи в onPause()/onDestroy(). Используйте Application Context вместо Activity для долгоживущих объектов.

Утечки памяти требуют постоянного внимания, так как даже небольшие накопления в долгосрочной перспективе приводят к серьёзным проблемам. Регулярный профилинг и код-ревью помогают держать потребление памяти под контролем.

Какие знаешь примеры утечки памяти в Android? | PrepBro