Управление жизненными циклами с помощью компонентов, учитывающих жизненный цикл . Часть Android Jetpack .
Компоненты с учетом жизненного цикла выполняют действия в ответ на изменение состояния жизненного цикла другого компонента, например действий и фрагментов. Эти компоненты помогают создавать более организованный и часто более легкий код, который легче поддерживать.
Распространенной схемой является реализация действий зависимых компонентов в методах жизненного цикла активностей и фрагментов. Однако этот шаблон приводит к плохой организации кода и увеличению количества ошибок. Используя компоненты, учитывающие жизненный цикл, вы можете переместить код зависимых компонентов из методов жизненного цикла в сами компоненты.
Пакет androidx.lifecycle
предоставляет классы и интерфейсы, которые позволяют создавать компоненты, учитывающие жизненный цикл — компоненты, которые могут автоматически корректировать свое поведение в зависимости от текущего состояния жизненного цикла действия или фрагмента.
К большинству компонентов приложения, определенных в Android Framework, прикреплены жизненные циклы. Жизненные циклы управляются операционной системой или кодом платформы, выполняющимся в вашем процессе. Они являются основой работы Android, и ваше приложение должно их уважать. Невыполнение этого требования может вызвать утечку памяти или даже сбой приложения.
Представьте, что у нас есть активность, которая показывает местоположение устройства на экране. Общая реализация может выглядеть следующим образом:
Котлин
internal class MyLocationListener( private val context: Context, private val callback: (Location) -> Unit ) { fun start() { // connect to system location service } fun stop() { // disconnect from system location service } } class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener override fun onCreate(...) { myLocationListener = MyLocationListener(this) { location -> // update UI } } public override fun onStart() { super.onStart() myLocationListener.start() // manage other components that need to respond // to the activity lifecycle } public override fun onStop() { super.onStop() myLocationListener.stop() // manage other components that need to respond // to the activity lifecycle } }
Ява
class MyLocationListener { public MyLocationListener(Context context, Callback callback) { // ... } void start() { // connect to system location service } void stop() { // disconnect from system location service } } class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; @Override public void onCreate(...) { myLocationListener = new MyLocationListener(this, (location) -> { // update UI }); } @Override public void onStart() { super.onStart(); myLocationListener.start(); // manage other components that need to respond // to the activity lifecycle } @Override public void onStop() { super.onStop(); myLocationListener.stop(); // manage other components that need to respond // to the activity lifecycle } }
Несмотря на то, что этот пример выглядит нормально, в реальном приложении у вас будет слишком много вызовов, которые управляют пользовательским интерфейсом и другими компонентами в ответ на текущее состояние жизненного цикла. При управлении несколькими компонентами в методах жизненного цикла, таких как onStart()
и onStop()
, размещается значительный объем кода, что затрудняет их поддержку.
Более того, нет никакой гарантии, что компонент запустится до остановки действия или фрагмента. Это особенно актуально, если нам нужно выполнить длительную операцию, например проверку конфигурации в onStart()
. Это может вызвать состояние гонки, когда метод onStop()
завершается раньше, чем метод onStart()
, что сохраняет работоспособность компонента дольше, чем это необходимо.
Котлин
class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener override fun onCreate(...) { myLocationListener = MyLocationListener(this) { location -> // update UI } } public override fun onStart() { super.onStart() Util.checkUserStatus { result -> // what if this callback is invoked AFTER activity is stopped? if (result) { myLocationListener.start() } } } public override fun onStop() { super.onStop() myLocationListener.stop() } }
Ява
class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; public void onCreate(...) { myLocationListener = new MyLocationListener(this, location -> { // update UI }); } @Override public void onStart() { super.onStart(); Util.checkUserStatus(result -> { // what if this callback is invoked AFTER activity is stopped? if (result) { myLocationListener.start(); } }); } @Override public void onStop() { super.onStop(); myLocationListener.stop(); } }
Пакет androidx.lifecycle
предоставляет классы и интерфейсы, которые помогут вам решать эти проблемы гибким и изолированным способом.
Жизненный цикл
Lifecycle
— это класс, который хранит информацию о состоянии жизненного цикла компонента (например, активности или фрагмента) и позволяет другим объектам наблюдать за этим состоянием.
Lifecycle
использует два основных перечисления для отслеживания статуса жизненного цикла связанного с ним компонента:
- Событие
- События жизненного цикла, отправляемые из платформы и класса
Lifecycle
. Эти события сопоставляются с событиями обратного вызова в действиях и фрагментах. - Состояние
- Текущее состояние компонента, отслеживаемое объектом
Lifecycle
.
Думайте о состояниях как об узлах графа, а о событиях как о ребрах между этими узлами.
Класс может отслеживать состояние жизненного цикла компонента, реализуя DefaultLifecycleObserver
и переопределяя соответствующие методы, такие как onCreate
, onStart
и т. д. Затем вы можете добавить наблюдателя, вызвав метод addObserver()
класса Lifecycle
и передав экземпляр вашего наблюдателя, как показано. в следующем примере:
Котлин
class MyObserver : DefaultLifecycleObserver { override fun onResume(owner: LifecycleOwner) { connect() } override fun onPause(owner: LifecycleOwner) { disconnect() } } myLifecycleOwner.getLifecycle().addObserver(MyObserver())
Ява
public class MyObserver implements DefaultLifecycleObserver { @Override public void onResume(LifecycleOwner owner) { connect() } @Override public void onPause(LifecycleOwner owner) { disconnect() } } myLifecycleOwner.getLifecycle().addObserver(new MyObserver());
В приведенном выше примере объект myLifecycleOwner
реализует интерфейс LifecycleOwner
, который описан в следующем разделе.
Владелец жизненного цикла
LifecycleOwner
— это интерфейс с одним методом, который обозначает, что у класса есть Lifecycle
. У него есть один метод getLifecycle()
, который должен быть реализован классом. Если вместо этого вы пытаетесь управлять жизненным циклом всего процесса приложения, см. ProcessLifecycleOwner
.
Этот интерфейс абстрагирует владение Lifecycle
от отдельных классов, таких как Fragment
и AppCompatActivity
, и позволяет писать компоненты, которые работают с ними. Любой пользовательский класс приложения может реализовать интерфейс LifecycleOwner
.
Компоненты, реализующие DefaultLifecycleObserver
без проблем работают с компонентами, реализующими LifecycleOwner
, поскольку владелец может предоставить жизненный цикл, который наблюдатель может зарегистрироваться для просмотра.
В примере с отслеживанием местоположения мы можем заставить класс MyLocationListener
реализовать DefaultLifecycleObserver
, а затем инициализировать его с помощью Lifecycle
действия в методе onCreate()
. Это позволяет классу MyLocationListener
быть самодостаточным, а это означает, что логика реагирования на изменения состояния жизненного цикла объявляется в MyLocationListener
вместо действия. Наличие у отдельных компонентов хранения собственной логики упрощает управление логикой действий и фрагментов.
Котлин
class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener override fun onCreate(...) { myLocationListener = MyLocationListener(this, lifecycle) { location -> // update UI } Util.checkUserStatus { result -> if (result) { myLocationListener.enable() } } } }
Ява
class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; public void onCreate(...) { myLocationListener = new MyLocationListener(this, getLifecycle(), location -> { // update UI }); Util.checkUserStatus(result -> { if (result) { myLocationListener.enable(); } }); } }
Распространенный вариант использования — избегать вызова определенных обратных вызовов, если Lifecycle
в данный момент не находится в хорошем состоянии. Например, если обратный вызов запускает транзакцию фрагмента после сохранения состояния активности, это приведет к сбою, поэтому мы никогда не захотим вызывать этот обратный вызов.
Чтобы упростить этот вариант использования, класс Lifecycle
позволяет другим объектам запрашивать текущее состояние.
Котлин
internal class MyLocationListener( private val context: Context, private val lifecycle: Lifecycle, private val callback: (Location) -> Unit ): DefaultLifecycleObserver { private var enabled = false override fun onStart(owner: LifecycleOwner) { if (enabled) { // connect } } fun enable() { enabled = true if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { // connect if not connected } } override fun onStop(owner: LifecycleOwner) { // disconnect if connected } }
Ява
class MyLocationListener implements DefaultLifecycleObserver { private boolean enabled = false; public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) { ... } @Override public void onStart(LifecycleOwner owner) { if (enabled) { // connect } } public void enable() { enabled = true; if (lifecycle.getCurrentState().isAtLeast(STARTED)) { // connect if not connected } } @Override public void onStop(LifecycleOwner owner) { // disconnect if connected } }
Благодаря этой реализации наш класс LocationListener
полностью учитывает жизненный цикл. Если нам нужно использовать наш LocationListener
из другого действия или фрагмента, нам просто нужно его инициализировать. Все операции установки и удаления управляются самим классом.
Если библиотека предоставляет классы, которые должны работать с жизненным циклом Android, мы рекомендуем вам использовать компоненты, учитывающие жизненный цикл. Клиенты вашей библиотеки могут легко интегрировать эти компоненты без ручного управления жизненным циклом на стороне клиента.
Реализация пользовательского LifecycleOwner
Фрагменты и действия в библиотеке поддержки версии 26.1.0 и более поздних версий уже реализуют интерфейс LifecycleOwner
.
Если у вас есть собственный класс, который вы хотите создать LifecycleOwner
, вы можете использовать класс LifecycleRegistry , но вам необходимо пересылать события в этот класс, как показано в следующем примере кода:
Котлин
class MyActivity : Activity(), LifecycleOwner { private lateinit var lifecycleRegistry: LifecycleRegistry override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleRegistry = LifecycleRegistry(this) lifecycleRegistry.markState(Lifecycle.State.CREATED) } public override fun onStart() { super.onStart() lifecycleRegistry.markState(Lifecycle.State.STARTED) } override fun getLifecycle(): Lifecycle { return lifecycleRegistry } }
Ява
public class MyActivity extends Activity implements LifecycleOwner { private LifecycleRegistry lifecycleRegistry; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); lifecycleRegistry = new LifecycleRegistry(this); lifecycleRegistry.markState(Lifecycle.State.CREATED); } @Override public void onStart() { super.onStart(); lifecycleRegistry.markState(Lifecycle.State.STARTED); } @NonNull @Override public Lifecycle getLifecycle() { return lifecycleRegistry; } }
Лучшие практики для компонентов, учитывающих жизненный цикл
- Держите ваши контроллеры пользовательского интерфейса (действия и фрагменты) максимально компактными. Им не следует пытаться получить собственные данные; вместо этого используйте для этого
ViewModel
и наблюдайте за объектомLiveData
, чтобы отразить изменения обратно в представлениях. - Попробуйте написать управляемые данными пользовательские интерфейсы, в которых ответственность вашего контроллера пользовательского интерфейса заключается в обновлении представлений по мере изменения данных или уведомлении о действиях пользователя обратно в
ViewModel
. - Поместите логику данных в класс
ViewModel
.ViewModel
должен служить связующим звеном между вашим контроллером пользовательского интерфейса и остальной частью вашего приложения. Однако будьте осторожны: в обязанностиViewModel
не входит получение данных (например, из сети). Вместо этогоViewModel
должен вызвать соответствующий компонент для получения данных, а затем вернуть результат контроллеру пользовательского интерфейса. - Используйте привязку данных , чтобы поддерживать чистый интерфейс между вашими представлениями и контроллером пользовательского интерфейса. Это позволяет вам сделать ваши представления более декларативными и свести к минимуму код обновления, который вам нужно написать в ваших действиях и фрагментах. Если вы предпочитаете делать это на языке программирования Java, используйте такую библиотеку, как Butter Knife, чтобы избежать шаблонного кода и получить лучшую абстракцию.
- Если ваш пользовательский интерфейс сложен, рассмотрите возможность создания класса презентатора для обработки изменений пользовательского интерфейса. Это может оказаться трудоемкой задачей, но она может облегчить тестирование компонентов пользовательского интерфейса.
- Избегайте ссылок на контекст
View
илиActivity
в вашейViewModel
. ЕслиViewModel
переживает действие (в случае изменения конфигурации), ваша активность протекает и не удаляется должным образом сборщиком мусора. - Используйте сопрограммы Kotlin для управления долго выполняющимися задачами и другими операциями, которые могут выполняться асинхронно.
Варианты использования компонентов с учетом жизненного цикла
Компоненты, учитывающие жизненный цикл, могут значительно упростить управление жизненным циклом в различных случаях. Вот несколько примеров:
- Переключение между грубым и детальным обновлением местоположения. Используйте компоненты с учетом жизненного цикла, чтобы обеспечить детальные обновления местоположения, пока ваше приложение определения местоположения видимо, и переключаться на общие обновления, когда приложение работает в фоновом режиме.
LiveData
, компонент, учитывающий жизненный цикл, позволяет вашему приложению автоматически обновлять пользовательский интерфейс, когда ваш пользователь меняет местоположение. - Остановка и запуск буферизации видео. Используйте компоненты с учетом жизненного цикла, чтобы начать буферизацию видео как можно скорее, но отложите воспроизведение до полного запуска приложения. Вы также можете использовать компоненты, учитывающие жизненный цикл, для прекращения буферизации при уничтожении вашего приложения.
- Запуск и остановка сетевого подключения. Используйте компоненты с учетом жизненного цикла, чтобы обеспечить оперативное обновление (потоковую передачу) сетевых данных, пока приложение находится на переднем плане, а также автоматически приостанавливать работу, когда приложение переходит в фоновый режим.
- Приостановка и возобновление анимированных рисунков. Используйте компоненты, учитывающие жизненный цикл, для обработки приостановки анимированных объектов рисования, когда приложение находится в фоновом режиме, и возобновления рисования после того, как приложение перейдет на передний план.
Обработка событий остановки
Если Lifecycle
принадлежит AppCompatActivity
или Fragment
, состояние Lifecycle
меняется на CREATED
, а событие ON_STOP
отправляется при вызове AppCompatActivity
или Fragment
onSaveInstanceState()
.
Когда состояние Fragment
или AppCompatActivity
сохраняется с помощью onSaveInstanceState()
, его пользовательский интерфейс считается неизменным до тех пор, пока не будет вызван ON_START
. Попытка изменить пользовательский интерфейс после сохранения состояния может привести к несогласованности состояния навигации вашего приложения, поэтому FragmentManager
выдает исключение, если приложение запускает FragmentTransaction
после сохранения состояния. Подробности смотрите commit()
.
LiveData
предотвращает этот крайний случай по умолчанию, воздерживаясь от вызова своего наблюдателя, если связанный с наблюдателем Lifecycle
не имеет хотя бы STARTED
. За кулисами он вызывает isAtLeast()
прежде чем принять решение о вызове своего наблюдателя.
К сожалению, метод onStop()
AppCompatActivity
вызывается после onSaveInstanceState()
, что оставляет пробел, в котором изменения состояния пользовательского интерфейса не допускаются, но Lifecycle
еще не переведен в состояние CREATED
.
Чтобы предотвратить эту проблему, класс Lifecycle
в версии beta2
и ниже помечает состояние как CREATED
без отправки события, чтобы любой код, проверяющий текущее состояние, получал реальное значение, даже если событие не отправляется до тех пор, пока onStop()
не будет вызван система.
К сожалению, у этого решения есть две основные проблемы:
- На уровне API 23 и ниже система Android фактически сохраняет состояние действия, даже если оно частично покрыто другим действием. Другими словами, система Android вызывает
onSaveInstanceState()
но не обязательно вызываетonStop()
. Это создает потенциально длинный интервал, в течение которого наблюдатель все еще считает, что жизненный цикл активен, хотя его состояние пользовательского интерфейса невозможно изменить. - Любой класс, который хочет реализовать поведение, аналогичное классу
LiveData
, должен реализовать обходной путь, предусмотренный бета-версиейLifecycle
версииbeta 2
и ниже.
Дополнительные ресурсы
Чтобы узнать больше об управлении жизненными циклами с помощью компонентов, учитывающих жизненный цикл, обратитесь к следующим дополнительным ресурсам.
Образцы
- Sunflower — демо-приложение, демонстрирующее лучшие практики работы с архитектурными компонентами.
Кодлабы
Блоги
{% дословно %}Рекомендуется для вас
- Примечание. Текст ссылки отображается, когда JavaScript отключен.
- Обзор LiveData
- Используйте сопрограммы Kotlin с компонентами, учитывающими жизненный цикл.
- Модуль сохраненного состояния для ViewModel