Добавить объявление

Причины возникновения утечек памяти и как их избежать

Flutter скрывает от разработчика ручное управление памятью. Кажется, что сборщик мусора все сделает сам. Но на практике утечки случаются регулярно. Экран закрыт, а какие-то объекты продолжают висеть в памяти. После нескольких переходов туда-сюда приложение начинает тормозить, а на слабых устройствах - вылетать. Это не баг фреймворка, это ошибки в коде, которые можно и нужно контролировать.

Откуда берутся утечки:


Чаще всего проблема в том, что объект создается, но не освобождается. Экран ушел, а контроллеры, подписки, таймеры или слушатели продолжают жить. Они держат ссылки на другие объекты, а те - на виджеты. В итоге память не отдается системе.

Контроллеры и dispose:


TextEditingController, ScrollController, AnimationController, FocusNode - все это нужно уничтожать в dispose(). Если этого не делать, каждый открытый экран будет оставлять после себя мусор. Правило простое: создал в initState - удали в dispose.

Подписки на Stream и слушатели:


StreamSubscription, addListener у ChangeNotifier и ValueNotifier - это классические источники утечек. Если подписался, но не отписался, поток будет слать события в уже мертвый экран. А заодно удерживать его в памяти. Решение: сохранять подписку и отменять ее в dispose.

BLoC и другие сущности с жизненным циклом:


Если BLoC создается прямо внутри State, нужно не забыть вызвать close(). Удобнее использовать BlocProvider, который сам управляет жизненным циклом. Главное правило: кто создал, тот и закрывает.

Таймеры:


Timer.periodic запускает повторяющиеся задачи. Если не отменить таймер при уходе с экрана, он будет продолжать тикать, вызывать setState и держать экран в памяти. Всегда сохраняйте ссылку на таймер и вызывайте cancel() в dispose.

Сохраненный BuildContext:


BuildContext нельзя хранить в сервисах, синглтонах или долгоживущих объектах. Он привязан к дереву виджетов. Хранив его надолго, вы случайно удерживаете в памяти целую часть интерфейса. Лучший способ - передавать контекст только в момент вызова, не сохраняя его.

Кэши без ограничений:


Кэш - это тоже утечка, если он растет бесконтрольно. Изображения, модели, результаты запросов - все это копится в памяти, если вовремя не чистить. Простое решение: ограничить размер кэша или очищать его при выходе пользователя.

Как искать утечки:


Самый прямой способ - Flutter DevTools. Раздел Memory показывает, как меняется потребление памяти. Простой сценарий: открываете экран, закрываете, повторяете несколько раз. Если память не возвращается к исходному уровню - есть проблема.

Можно пойти дальше: сделать снимок памяти до открытия экрана, потом после нескольких открытий/закрытий и сравнить. Если в списке объектов появляются лишние экземпляры TextEditingController, Bloc, State - это явный сигнал.

Leak Tracker:


Для автоматического поиска утечек можно подключить пакет leak_tracker. Он отслеживает объекты с жизненным циклом и выдает предупреждения в консоль, если что-то не было освобождено. Удобно для отладки в процессе разработки.

Важные правила:


  • Если есть initState, проверьте, есть ли dispose.

  • Если есть addListener, найдите соответствующий removeListener.

  • Если есть StreamSubscription, убедитесь, что вызывается cancel.

  • Если есть Timer, проверьте, что он отменяется.

  • Если есть кэш, задайте ему ограничения.

  • После await перед setState проверьте mounted.

Вывод:


Утечки памяти во Flutter - это не магия, а следствие забытых dispose, cancel и removeListener. Они не видны сразу, но накапливаются со временем. Самый надежный способ - профилактика: иметь чек-лист для код-ревью и периодически прогонять подозрительные сценарии через DevTools. Если относиться к dispose как к обязательной части работы с экраном, большинство проблем можно поймать еще до релиза.
29.05.2026 88 515