Какие знаешь примеры утечки памяти в Android?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Утечки памяти в 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для долгоживущих объектов.
Утечки памяти требуют постоянного внимания, так как даже небольшие накопления в долгосрочной перспективе приводят к серьёзным проблемам. Регулярный профилинг и код-ревью помогают держать потребление памяти под контролем.