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

Как связать жизненный цикл Activity и Presenter

1.8 Middle🔥 141 комментариев
#Архитектура и паттерны#Жизненный цикл и навигация

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

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

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

Связь жизненного цикла Activity и Presenter в архитектуре MVP

Связь жизненного цикла Activity и Presenter — критически важный аспект при разработке Android-приложений с использованием архитектурного паттерна MVP (Model-View-Presenter). Основная задача — обеспечить корректное управление ресурсами, предотвратить утечки памяти и сохранить состояние при поворотах экрана.

Основные принципы связи

В классическом подходе Presenter должен переживать смену конфигурации (например, поворот экрана), но уничтожаться при окончательном завершении Activity. Для этого используются несколько ключевых механизмов:

  1. Ссылка на View — Presenter хранит слабую ссылку (WeakReference) на View, чтобы избежать утечек памяти.
  2. Методы жизненного цикла — Presenter реализует собственные методы жизненного цикла, которые вызываются из Activity.
  3. Сохранение состояния — использование onSaveInstanceState() для сохранения временных данных Presenter.

Базовая реализация связи

Рассмотрим практическую реализацию с использованием интерфейсов и слабых ссылок:

1. Определение контрактов (интерфейсов)

// Базовый интерфейс для View
interface BaseView {
    fun showError(message: String)
}

// Интерфейс для конкретной View
interface MainView : BaseView {
    fun showData(data: List<String>)
    fun showLoading()
    fun hideLoading()
}

// Базовый интерфейс для Presenter
interface BasePresenter<V : BaseView> {
    fun attachView(view: V)
    fun detachView()
    fun onDestroy()
}

2. Реализация Presenter со слабой ссылкой

class MainPresenter : BasePresenter<MainView> {
    
    private var viewRef: WeakReference<MainView>? = null
    private val repository = DataRepository()
    private var subscription: Disposable? = null
    
    override fun attachView(view: MainView) {
        viewRef = WeakReference(view)
        loadData()
    }
    
    override fun detachView() {
        viewRef?.clear()
        viewRef = null
    }
    
    override fun onDestroy() {
        subscription?.dispose()
        // Очистка других ресурсов
    }
    
    private fun loadData() {
        getView()?.showLoading()
        
        subscription = repository.getData()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                { data -> 
                    getView()?.hideLoading()
                    getView()?.showData(data)
                },
                { error ->
                    getView()?.hideLoading()
                    getView()?.showError(error.message ?: "Unknown error")
                }
            )
    }
    
    private fun getView(): MainView? {
        return viewRef?.get()
    }
}

3. Интеграция с Activity

class MainActivity : AppCompatActivity(), MainView {
    
    private lateinit var presenter: MainPresenter
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // Восстановление Presenter после поворота
        presenter = if (lastCustomNonConfigurationInstance != null) {
            lastCustomNonConfigurationInstance as MainPresenter
        } else {
            MainPresenter()
        }
        
        presenter.attachView(this)
    }
    
    override fun onRetainCustomNonConfigurationInstance(): Any? {
        presenter.detachView()
        return presenter
    }
    
    override fun onDestroy() {
        super.onDestroy()
        if (isFinishing) {
            presenter.onDestroy()
        }
        presenter.detachView()
    }
    
    // Реализация методов MainView
    override fun showData(data: List<String>) {
        // Обновление UI
    }
    
    override fun showLoading() {
        // Показать индикатор загрузки
    }
    
    override fun hideLoading() {
        // Скрыть индикатор загрузки
    }
    
    override fun showError(message: String) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
    }
}

Современные подходы с использованием архитектурных компонентов

С появлением Android Architecture Components подход упрощается:

Использование ViewModel + LiveData

class MainViewModel : ViewModel() {
    
    private val repository = DataRepository()
    private val _data = MutableLiveData<List<String>>()
    private val _loading = MutableLiveData<Boolean>()
    private val _error = MutableLiveData<String>()
    
    val data: LiveData<List<String>> = _data
    val loading: LiveData<Boolean> = _loading
    val error: LiveData<String> = _error
    
    fun loadData() {
        _loading.value = true
        
        viewModelScope.launch {
            try {
                val result = withContext(Dispatchers.IO) {
                    repository.getData()
                }
                _data.value = result
                _error.value = null
            } catch (e: Exception) {
                _error.value = e.message
            } finally {
                _loading.value = false
            }
        }
    }
    
    override fun onCleared() {
        // Очистка ресурсов
        super.onCleared()
    }
}

Activity с ViewModel

class MainActivity : AppCompatActivity() {
    
    private lateinit var viewModel: MainViewModel
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
        
        // Наблюдение за LiveData
        viewModel.data.observe(this, Observer { data ->
            // Обновить UI с данными
        })
        
        viewModel.loading.observe(this, Observer { isLoading ->
            // Показать/скрыть индикатор загрузки
        })
        
        viewModel.error.observe(this, Observer { error ->
            // Показать ошибку
        })
        
        if (savedInstanceState == null) {
            viewModel.loadData()
        }
    }
}

Ключевые рекомендации

  1. Всегда используйте слабые ссылки в классическом MVP для предотвращения утечек памяти
  2. Разделяйте ответственность: Presenter не должен знать об Android-контексте
  3. Обрабатывайте сценарии поворота экрана через onRetainCustomNonConfigurationInstance() или ViewModel
  4. Отменяйте асинхронные операции при уничтожении Presenter
  5. Тестируйте сценарии утечек памяти с помощью LeakCanary или Android Profiler
  6. Используйте современные подходы с Architecture Components для упрощения управления жизненным циклом

Распространенные ошибки

  • Сильная ссылка на Activity в Presenter → утечка памяти
  • Неотмененные подписки при повороте экрана → утечки и краши
  • Сохранение состояния в Presenter вместо использования механизмов Android
  • Вызов методов View после ее уничтожения → NullPointerException

Правильная связь жизненных циклов Activity и Presenter обеспечивает стабильность приложения, предотвращает утечки памяти и корректно обрабатывает сценарии изменения конфигурации. Современные инструменты вроде ViewModel значительно упрощают эту задачу, но понимание классических подходов остается важным для глубокого понимания архитектуры Android-приложений.

Как связать жизненный цикл Activity и Presenter | PrepBro