Как с MVP и Dagger 2 обеспечить восстановление компонентов Activity при повороте экрана, но пересоздавать граф зависимостей при полном перезапуске Activity
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение проблемы жизненного цикла компонентов с MVP и Dagger 2
В архитектуре MVP с Dagger 2 ключевая проблема заключается в том, что при повороте экрана Android уничтожает и заново создает Activity, но нам нужно сохранить презентер и связанные с ним компоненты, чтобы избежать потери состояния и повторной инициализации. Однако при полном перезапуске Activity (например, после уничтожения системой из-за нехватки памяти) граф зависимостей должен быть пересоздан.
Основные подходы и их реализация
1. Сохранение компонента через RetainInstance или ViewModel
Самый современный подход — использование ViewModel из Android Architecture Components, который автоматически переживает поворот экрана. Но в классическом MVP с Dagger 2 часто используют механизм setRetainInstance(true) для фрагмента-хоста или кастомное решение.
// RetainFragment для хранения компонента
class RetainComponentFragment : Fragment() {
var activityComponent: ActivityComponent? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true // Фрагмент не уничтожается при повороте
}
companion object {
fun getOrCreate(activity: FragmentActivity): RetainComponentFragment {
return activity.supportFragmentManager.findFragmentByTag("RetainComponent") as? RetainComponentFragment
?: RetainComponentFragment().apply {
activity.supportFragmentManager.beginTransaction()
.add(this, "RetainComponent")
.commit()
}
}
}
}
2. Разделение графов зависимостей
Ключевая идея — разделить ApplicationComponent (живет все время работы приложения) и ActivityComponent (должен переживать поворот, но пересоздаваться при полном уничтожении Activity).
// Application-level component
@Singleton
@Component(modules = [AppModule::class])
interface AppComponent {
fun inject(app: MyApplication)
fun activityComponentFactory(): ActivityComponent.Factory
}
// Activity-level component с кастомной областью видимости
@ActivityScope
@Subcomponent(modules = [ActivityModule::class])
interface ActivityComponent {
@Subcomponent.Factory
interface Factory {
fun create(@BindsInstance activity: Activity): ActivityComponent
}
fun inject(activity: MainActivity)
fun getPresenter(): MainPresenter
}
// Кастомная область видимости для Activity
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class ActivityScope
3. Управление жизненным циклом компонента в Activity
class MainActivity : AppCompatActivity() {
@Inject lateinit var presenter: MainPresenter
private lateinit var activityComponent: ActivityComponent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Получаем или создаем компонент через RetainFragment
val retainFragment = RetainComponentFragment.getOrCreate(this)
if (retainFragment.activityComponent == null) {
// Создаем новый компонент при первом запуске или полном перезапуске
activityComponent = (application as MyApplication)
.appComponent
.activityComponentFactory()
.create(this)
retainFragment.activityComponent = activityComponent
} else {
// Используем существующий компонент при повороте
activityComponent = retainFragment.activityComponent!!
}
activityComponent.inject(this)
// Проверяем, было ли полное уничтожение Activity
if (savedInstanceState != null) {
presenter.onRestoreState(savedInstanceState)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
presenter.onSaveState(outState)
}
override fun onDestroy() {
super.onDestroy()
if (isFinishing) {
// При полном завершении Activity очищаем компонент
val retainFragment = supportFragmentManager
.findFragmentByTag("RetainComponent") as? RetainComponentFragment
retainFragment?.activityComponent = null
}
}
}
4. Альтернативный подход: хранение компонента в Application
Более простой, но менее гибкий подход — хранение компонента в кастомном классе Application с проверкой конфигурационных изменений:
class ComponentHolder {
private var activityComponent: ActivityComponent? = null
private var activityHashCode: Int = 0
fun getOrCreateActivityComponent(
currentActivity: Activity,
factory: ActivityComponent.Factory
): ActivityComponent {
return if (activityComponent == null || activityHashCode != currentActivity.hashCode()) {
// Создаем новый компонент при смене Activity или полном перезапуске
activityComponent = factory.create(currentActivity)
activityHashCode = currentActivity.hashCode()
activityComponent!!
} else {
// Используем существующий при повороте
activityComponent!!
}
}
fun clearActivityComponent() {
activityComponent = null
activityHashCode = 0
}
}
Критические моменты реализации
- Области видимости (Scopes): Используйте
@ActivityScopeдля компонентов, которые должны жить дольше одной итерации Activity, но не все время работы приложения - Утечки памяти: Всегда очищайте ссылки на компоненты при полном уничтожении Activity
- Состояние презентера: При восстановлении после поворота презентер уже содержит актуальные данные, но при полном перезапуске может потребоваться восстановление состояния через
Bundle - Тестируемость: Такая архитектура сохраняет возможность тестирования презентера независимо от Android компонентов
Преимущества подхода
- Экономия ресурсов: Избегаем повторной инициализации тяжелых объектов при повороте
- Сохранение состояния: Презентер сохраняет бизнес-логику и состояние UI
- Гибкость: Возможность кастомизировать жизненный цикл для разных сценариев
- Совместимость: Работает с любыми версиями Android и не требует Architecture Components
Это решение обеспечивает баланс между сохранением производительности при поворотах и корректным освобождением ресурсов при полном перезапуске Activity.