← Назад к вопросам

Как с MVP и Dagger 2 обеспечить восстановление компонентов Activity при повороте экрана, но пересоздавать граф зависимостей при полном перезапуске Activity

2.8 Senior🔥 81 комментариев
#Dependency Injection#Архитектура и паттерны

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение проблемы жизненного цикла компонентов с 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.