Приложение падает с OutOfMemoryError в проде. Открываете LeakCanary — а там знакомый стек: MainActivity утекла через Context в каком-то Singleton. Что делать дальше, трейс не подсказывает: он показывает, кто держит ссылку, но не почему это плохо и как переписать. Вот тут LLM и становится полезной — если скормить ей трейс правильно.
Ниже — промты, которые я гоняю по реальным утечкам памяти Android. Каждый с примером трейса LeakCanary и тем, что должно вернуться в ответ.
Провал №1: скормили стек — получили пересказ стека
Типичная ошибка: кидаете в чат весь вывод LeakCanary как есть и пишете «найди утечку». LLM честно пересказывает: «Activity утекла через статическое поле». Спасибо, это и так видно.
Причина в том, что модель не знает, что именно вы хотите: диагноз, рефакторинг или тест. Разделяйте задачи.
Промт для диагноза:
Ты — Android-инженер, который чинит утечки памяти в проде. Ниже — трейс LeakCanary. Не пересказывай стек. Ответь строго по пунктам:
1) Какой объект держит ссылку на утекший и почему GC его не собирает.
2) Какая lifecycle-связь нарушена (Activity/Fragment/View/Cursor/Listener).
3) Три наиболее вероятные причины в порядке убывания вероятности.
4) Для каждой причины — как проверить её в Android Studio Memory Profiler за 2 шага.
Если данных в трейсе не хватает — перечисли, какие поля LeakCanary надо дослать.
Трейс:
────────────
└─ com.example.app.data.SessionManager.instance
└─ mContext (Context)
└─ com.example.app.ui.MainActivity
└─ mViews (View[])
────────────
Ожидаемый ответ: SessionManager — синглтон со статическим полем, держит Context Activity, поэтому вся иерархия View остаётся в памяти. Проверка: во вкладке Memory Profiler сделать heap dump, отфильтровать по MainActivity, посмотреть retained size и цепочку referrers — там будет тот же путь.
Провал №2: починили симптом — утечка вернулась
SessionManager хранит Context — модель предлагает передать applicationContext. Работает. Но через месяц та же Activity снова в дампе, потому что слушатель в синглтоне остался.
Чтобы не латать по одному месту, просите рефакторинг с обоснованием:
Ниже — класс с утечкой. Перепиши так, чтобы Context Activity не удерживался. Требования:
- Замени хранимый Context на applicationContext ИЛИ убери Context из полей вовсе.
- Если нужен слушатель — предложи WeakReference или явный unregister в onDestroy.
- Покажи diff в формате unified, не переписывай весь файл.
- В конце — 3 строки: что изменилось, что осталось потенциально опасным, как это закрыть.
Код:
────────────
object SessionManager {
var context: Context? = null
private val listeners = mutableListOf<SessionListener>()
fun init(ctx: Context) { context = ctx }
fun addListener(l: SessionListener) { listeners.add(l) }
}
────────────
Модель вернёт diff: context → applicationContext, listeners → WeakReference или явный removeListener. Плюс честно скажет, что applicationContext не годится, если нужен доступ к UI-ресурсам темы — тогда решение другое.
Провал №3: «на глазок» вместо воспроизводимого теста
Легко поверить, что утечка ушла, потому что «вроде не падает». Память — плохой индикатор: OOM приходит через часы нагрузки. Генерируйте reproducer-тест.
Напиши JVM-тест (Robolectric или чистый JUnit с фейками), который воспроизводит утечку:
создаём объект Activity-подобного класса, кладём его в статический держатель, обнуляем локальную ссылку,
вызываем System.gc(), проверяем через WeakReference, что объект НЕ собран.
Затем — второй тест, который проверяет, что после рефакторинга объект СОБИРАЕТСЯ.
Используй только стандартный API и JUnit. Не тяни сторонние библиотеки.
Ожидаемый результат: два теста, один красный до правки, второй зелёный после. Это и есть доказательство, что причина найдена, а не замаскирована.
Провал №4: доверились модели без проверки гипотезы
LLM может уверенно назвать причину, которой нет. Хорошая практика — заставить её сформулировать проверяемую гипотезу до правки:
Сформулируй гипотезу утечки в виде: «Если <действие>, то в Memory Profiler <наблюдение>, иначе гипотеза неверна».
Дай две такие гипотезы: одну про static context, одну про незакрытый Cursor.
Для каждой — точные шаги в Android Studio: какая вкладка, какой фильтр, на что смотреть.
Ответ должен быть в форме «поверни экран 5 раз → сделай heap dump → отфильтруй по Cursor → retained size растёт». Если шаг нельзя выполнить в интерфейсе — гипотеза плохая, переписывайте.
Что реально закрывает OOM
Не «правильный промт», а порядок: трейс → диагноз с проверкой → diff → тест → гипотеза в Profiler. LLM ускоряет каждый шаг, но пропуск любого возвращает ту же утечку через месяц.
Если хочется глубже разобраться, откуда в Android вообще берутся такие ссылки на уровне стека и корутин, посмотрите разбор Промты для Kotlin-корутин и Flow — там та же логика, но про асинхронные держатели. Подробнее — в статье «Kotlin и Android-разработка».
Следующий раз, когда LeakCanary покажет стек, не закрывайте вкладку — скормите трейс с явной задачей. Начните с промта №1 и не переходите к правке, пока не получите три гипотезы и способ проверить каждую.