Как связать жизненный цикл Activity и Presenter
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Связь жизненного цикла Activity и Presenter в архитектуре MVP
Связь жизненного цикла Activity и Presenter — критически важный аспект при разработке Android-приложений с использованием архитектурного паттерна MVP (Model-View-Presenter). Основная задача — обеспечить корректное управление ресурсами, предотвратить утечки памяти и сохранить состояние при поворотах экрана.
Основные принципы связи
В классическом подходе Presenter должен переживать смену конфигурации (например, поворот экрана), но уничтожаться при окончательном завершении Activity. Для этого используются несколько ключевых механизмов:
- Ссылка на View — Presenter хранит слабую ссылку (
WeakReference) на View, чтобы избежать утечек памяти. - Методы жизненного цикла — Presenter реализует собственные методы жизненного цикла, которые вызываются из Activity.
- Сохранение состояния — использование
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()
}
}
}
Ключевые рекомендации
- Всегда используйте слабые ссылки в классическом MVP для предотвращения утечек памяти
- Разделяйте ответственность: Presenter не должен знать об Android-контексте
- Обрабатывайте сценарии поворота экрана через
onRetainCustomNonConfigurationInstance()или ViewModel - Отменяйте асинхронные операции при уничтожении Presenter
- Тестируйте сценарии утечек памяти с помощью LeakCanary или Android Profiler
- Используйте современные подходы с Architecture Components для упрощения управления жизненным циклом
Распространенные ошибки
- Сильная ссылка на Activity в Presenter → утечка памяти
- Неотмененные подписки при повороте экрана → утечки и краши
- Сохранение состояния в Presenter вместо использования механизмов Android
- Вызов методов View после ее уничтожения → NullPointerException
Правильная связь жизненных циклов Activity и Presenter обеспечивает стабильность приложения, предотвращает утечки памяти и корректно обрабатывает сценарии изменения конфигурации. Современные инструменты вроде ViewModel значительно упрощают эту задачу, но понимание классических подходов остается важным для глубокого понимания архитектуры Android-приложений.