Разработка
Топ-7 утечек памяти Android и как их избежать
Давайте разберем 7 наиболее распространенных виновников и вооружим вас проверенными в бою решениями.
Утечки памяти — тихие убийцы производительности приложений. Даже в 2025 году, когда Jetpack Compose доминирует в разработке пользовательских интерфейсов и появляются новые модные инструменты, эти ошибки все еще преследуют разработчиков. Давайте разберем 7 наиболее распространенных виновников и вооружим вас проверенными в бою решениями.
Статический контекст: классическая ловушка
Статические ссылки на Activity
или Context
подобны неживым призракам — они никогда не умирают, блокируя сборку мусора. Часто встречаются в синглтонах или глобальных утилитах.
Всегда инжектируйте контекст приложения (никогда не активити):
object AuthManager {
private lateinit var appContext: Context
// Initialize in your Application class
fun init(application: Application) {
appContext = application
}
fun getUser(): User? {
// Use appContext here
}
}
// Try to fix using DI like
SingletonComponent::class) (
class AppModule {
fun provideContext( context: Context): Context = context
}
Внутренние классы и лямбды: молчаливые хранители
Нестатические внутренние классы (например, обработчики) или лямбды Compose скрытно хранят родительские ссылки. Ваша активити остается в памяти после поворота экрана? Вот почему:
xxxxxxxxxx
// For legacy code
// Static inner class + WeakReference
class MyActivity : AppCompatActivity() {
private class SafeHandler(
activity: MyActivity
) : Handler(Looper.getMainLooper()) {
private val activityRef = WeakReference(activity)
override fun handleMessage(msg: Message) {
activityRef.get()?.updateUI()
}
}
}
// For compose code
fun LeakProofButton(onClick: () -> Unit) {
// Captures latest onClick reference
val currentOnClick by rememberUpdatedState(onClick)
Button(onClick = { currentOnClick() }) {
Text("I'm Safe!")
}
}
Зомби потоки и корутины
Фоновые задачи сохраняются после уничтожения активити/фрагмента. Часто встречаются в disposables RxJava или GlobalScope
.
Корутины: привяжитесь к жизненному циклу с помощью lifecycleScope
:
xxxxxxxxxx
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
lifecycleScope.launch {
// Auto-cancels when fragment dies
fetchData()
}
}
}
Забытые слушатели: фантомные наблюдатели
Незарегистрированные наблюдатели LiveData, broadcast ресиверы или слушатели кликов остаются, как неприятные запахи:
xxxxxxxxxx
fun SensorObserver() {
val sensorManager = remember { getSystemService(SENSOR_SERVICE) as SensorManager }
DisposableEffect(Unit) {
val listener = SensorEventListener { /* ... */ }
sensorManager.registerListener(listener, ...)
onDispose {
sensorManager.unregisterListener(listener) // Clean exit!
}
}
}
Спагетти из синглтонов
Синглтоны случайно удерживают ссылки на UI. Видели такую ошибку? MySingleton → MyActivity → MySingleton
(циклическая ссылка!).
- Храните только данные на уровне приложения (пользовательские сессии, конфигурации)
- Инжектируйте контекст через DI (Hilt/Dagger/Koin)
xxxxxxxxxx
class AnalyticsService constructor(
private val context: Context
) { ... }
View вампиры: UI-компоненты, которые не хотят умирать
Кастомные представления или ComposeView, сохраняющие ссылки на Activity после отсоединения:
xxxxxxxxxx
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
myView.setOnClickListener(null) // Cut the ties!
}
// Compose
ComposeView(context).apply {
setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnDetachedFromWindow
)
setContent { /* ... */ }
}
Ошибки с растровыми изображениями и монстры состояний
Не отпущенные растровые изображения или массивные состояния Compose, вызывающие OutOfMemoryError
:
xxxxxxxxxx
AsyncImage(
model = "https://example.com/huge_image.jpg",
contentDescription = null,
modifier = Modifier.fillMaxWidth()
)
val heavyList by remember { mutableStateOf(List(10_000) { ... }) } //
// Better: Use lazy lists or derivedStateOf
val visibleItems by remember {
derivedStateOf { heavyList.filter { it.isVisible } }
}
Ваш набор инструментов для борьбы с утечками 2025
- LeakCanary 2.14+ — теперь с поддержкой Compose!
- Android Studio Memory Profiler — отслеживание дампов кучи
- Отладчик Compose Recomposition — поиск злодеев состояний
-
Видео и подкасты для разработчиков4 недели назад
Как устроена мобильная архитектура. Интервью с тех. лидером юнита «Mobile Architecture» из AvitoTech
-
Новости4 недели назад
Видео и подкасты о мобильной разработке 2025.10
-
Новости3 недели назад
Видео и подкасты о мобильной разработке 2025.11
-
Видео и подкасты для разработчиков2 недели назад
Javascript для бэкенда – отличная идея: Node.js, NPM, Typescript