Управление жизненными циклами с помощью компонентов, учитывающих жизненный цикл . Часть 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 .
Диаграмма состояний жизненного цикла
Рисунок 1. Состояния и события, составляющие жизненный цикл активности Android

Думайте о состояниях как об узлах графа, а о событиях как о ребрах между этими узлами.

Класс может отслеживать состояние жизненного цикла компонента, реализуя 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 — демо-приложение, демонстрирующее лучшие практики работы с архитектурными компонентами.

Кодлабы

Блоги

{% дословно %} {% дословно %} {% дословно %} {% дословно %}