Как контролировать контекст и избегать утечек памяти при использовании Dagger компонентов
Что проверяют
Понимаете ли вы, что scope в Dagger не управляет памятью сам, а лишь привязывает время жизни объекта к экземпляру компонента. Следом обычно спрашивают, что будет, если Activity попадёт в @Singleton, и как вы такую утечку найдёте.
Как отвечать
Главная мысль: объект живёт столько, сколько живёт компонент, который его держит. Аннотация @ActivityScope означает только «один экземпляр на экземпляр компонента». Если сам компонент сохранён в Application или в статическом поле, всё, что в нём лежит, включая Activity, переживёт поворот экрана.
- иерархия компонентов:
AppComponentживёт всё приложение,ActivityComponentкак сабкомпонент создаётся вonCreateи хранится полем самойActivity, поэтому умирает вместе с ней; - разные квалификаторы:
@ApplicationContextдля долгоживущих зависимостей,@ActivityContextдля темы и окон. Второй привязан только в сабкомпоненте, и синглтон его запросить не сможет: Dagger упадёт на компиляции; @BindsInstance:Activityпередаётся в фабрику сабкомпонента, а не в конструктор модуля корневого графа;- Hilt: готовые
SingletonComponent,ActivityRetainedComponent,ViewModelComponent,ActivityComponent,FragmentComponentи квалификаторы из коробки. В@HiltViewModelдоступен только@ApplicationContext:ViewModelпереживает пересозданиеActivity; - проверка: LeakCanary в debug-сборке показывает цепочку ссылок до утёкшей
Activity.
Страховка на компиляции частичная. Если положить Activity прямо в корневой граф, Dagger промолчит.
Где спотыкаются
Советуют WeakReference как основное лекарство. Это заплатка поверх неверного графа, а объект может исчезнуть в неожиданный момент.
Считают, что Provider<T> или Lazy<T> решают утечку. Они откладывают создание, но ссылку по-прежнему держит тот же компонент.
Забывают про фрагменты: фрагмент живёт дольше своей view, и внедрённые в него ссылки на view или binding держат уже уничтоженную разметку.