Pull to refresh

Android Lifecycle-aware Architecture Components

Reading time 7 min
Views 14K


6 ноября 2017 года компания Google опубликовала информацию об анонсе стабильной версии
архитектурных компонентов. Разработчики Google предоставили руководство по архитектуре приложений и представили ряд классов и интерфейсов, которые упрощают создание приложений с выстроенной архитектурой, облегчают присоединение новых программистов к проекту и уменьшают порог вхождения в мир взрослой разработки для тех людей, которые только начали программировать под систему Android.

Представленные компоненты для работы с жизненным циклом Android можно сравнить со скрытым от глаз часовым механизмом. Всего пара строчек кода и все работает. Но как все устроено? Да и вообще, стоит ли использовать архитектурные компоненты в своих домашних проектах или даже в проектах с сотнями тысяч активных установок?

Disclaimer
Код разработчика предоставлен на языке разработки Kotlin. Выдержки исходного кода компании Google предоставлены на языке Java. В выдержках часть кода может быть опущена.

Lifecycle, lifecycle-aware components and activities


Жизненный цикл — очень важный пункт в мире андроид разработки, которому зачастую уделяется недостаточно внимания. По этой причине у пользователей могут происходить ошибки в работе приложения. Например, при телефонном звонке приложение может завершить работу с критической ошибкой. Это происходит из-за пересоздания активити и необработанного сохранения состояния.

Частично проблемы пересоздания активити можно избежать. Например, запретить пересоздание — поставить в манифесте настройку активити android:screenOrientation=«portrait». Но это решит проблемы только с пересозданием активити во время изменения конфигурации (например, смены ориентации экрана). Проблему того, что в какой-то момент операционной системе не хватит памяти и она уничтожит процесс с исполняемой активити этот способ не решает. Вернувшись к работе с приложением, первое, что увидит пользователь — критическую ошибку.

Так или иначе разработчику нужно позаботиться об обработке состояний жизненного цикла. На помощь приходят lifecycle-aware components. Архитектурные компоненты имеют стабильную версию 1.0 и их можно применять в production-разработке приложений.

Плюсы и минусы использования lifecycle-aware components


Рассмотрим практические плюсы и минусы использования компонентов.

Плюсов несомненно больше

  1. подключение нового сотрудника к команде разработки приложения. Все android разработчики знают и умеют пользоваться официальными библиотеками от компании Google. Не надо тратить время на обучение локальным решениям для поддержания архитектуры;
  2. меньше кода при разработке фичи;
  3. стабильность работы компонентов;
  4. улучшение стабильности работы приложения после внедрения компонентов.

Минусы

  1. время на ознакомление с компонентами и добавление в проект;
  2. добавился код для сопровождения архитектуры при разработке новой функции, но этот минус легко решается генерацией кода. Хорошие статьи на эту тему здесь и здесь.

Как работать с lifecycle-aware components?


Начиная с Support Library версии 26.1.0 фрагменты и активити из коробки реализуют интерфейс LifecycleOwner. Этот интерфейс имеет только один метод — getLifecycle().
Чтобы добавить наблюдателя за событиями жизненного цикла достаточно всего лишь в классе-наблюдателе реализовать интерфейс LifecycleObserver и написать в активити/фрагменте

private fun addLifecycleObserver() {
    lifecycle.addObserver(observer)
} 

И все? Да, для разработчика на этом работа заканчивается. Достаточно в коде наблюдателя пометить нужные методы аннотациями и реагировать на события жизненного цикла.

@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun init(){}

Интересный факт, что одной и той же аннотацией можно пометить несколько методов и все они будут вызваны при изменении состояния жизненного цикла.

Что же скрывается за парой строчек, как все работает, какие есть нюансы?
Найдем ответы на перечисленные ниже вопросы на примере фрагмента.

Что возвращает фрагмент, реализуя интерфейс LifecycleOwner в методе getLifecycle()? Описание основных методов


Фрагмент имплементирует интерфейс LifecycleOwner, реализуя метод getLifecycle(): Lifecycle.

Lifecycle — абстрактный класс, определяющий объект, как объект имеющий жизненный цикл Android.

Реализация данного класса LifecycleRegistry берет на себя всю работу по контролю за добавлением, удалением наблюдателей, обработкой событий жизненного цикла, сообщением об изменениях жизненного цикла всем наблюдателям.

Добавление наблюдателя.

@MainThread
public abstract void addObserver(@NonNull LifecycleObserver observer);

Важный нюанс состоит в том, что при добавлении LifecycleObserver к списку наблюдающих — наблюдатель получит события об изменении всех состояний, которые предшествуют текущему.

То есть если LifecycleOwner находится в состоянии Lifecycle.State.STARTED при добавлении LifecycleObserver, то последний получит два события Lifecycle.Event.ON_CREATE и Lifecycle.Event.ON_START.

Значит, мы имеем гарантию того, что наш наблюдатель пройдет все ступени инициализации, опираясь на события жизненного цикла, и не пропустит какой-либо стадии конфигурации.

Удаление наблюдателя из списка наблюдающих происходит в методе.

@MainThread
public abstract void removeObserver(@NonNull LifecycleObserver observer);

Если удаление наблюдателя происходит во время изменения состояния жизненного цикла и отправка события об изменении состояния вызвана после удаления — наблюдатель не получит данного события.

Если в наблюдателе несколько методов ожидают одно событие и хотя бы один из методов был вызван во время удаления наблюдателя из списка наблюдащих, то все остальные методы также будут вызваны и только после этого произойдет удаление.

Возврат текущего состояния по запросу.

@MainThread
public abstract State getCurrentState();

В любой момент можно запросить текущее состояние жизненного цикла и, опираясь на ответ, предпринять какие-либо действия. Метод вернет экземпляр перечисления State.

Существуют следующие виды State

INITIALIZED — данное состояние соответствует тому времени, когда сущность реализующая интерфейс LifecycleOwner создана, но метод onCreate() еше не был вызван.

CREATED — данное состояние активно после вызова метода onCreate() и до вызова onStop().

STARTED — данное состояние активно после вызова метода onStart() и до вызова onPause().

RESUMED — данное состояние наступает после вызова метода onResume().

DESTROYED — данное состояние наступает непосредственно перед вызовом onDestroy(). По наступлению этого состояния LifecycleOwner более не отправляет событий об изменении состояний.

Что происходит при добавлении наблюдателя к списку наблюдающих?


@Override
public void addObserver(@NonNull LifecycleObserver observer) {

    State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
    ObserverWithState statefulObserver = new ObserverWithState(observer, 
initialState);
    <...>
}

При вызове метода lifecycle.addObserver(observer) observer помещается в конструктор экземпляра класса-обертки ObserverWithState. Как понятно из названия класса — этот класс хранит observer с последним обработанным состоянием жизненного цикла. Изначально устанавливает состояние DESTROYED или INITIALIZED.

@Override
public void addObserver(@NonNull LifecycleObserver observer) {

    <...>
    ObserverWithState previous = mObserverMap.putIfAbsent(observer, 
statefulObserver);

    if (previous != null) {
         return;
    }

    LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
    if (lifecycleOwner == null) {
        // it is null we should be destroyed. Fallback quickly
        return;
    }

    <...>
}

После создания экземпляра наблюдателя с последним обработанным состоянием жизненного цикла — пытаемся добавить наблюдателя в коллекцию FastSafeIterableMap методом putIfAbsent().

@Override
public V putIfAbsent(@NonNull K key, @NonNull V v) {
    Entry<K, V> current = get(key);
    if (current != null) {
        return current.mValue;
    }
    mHashMap.put(key, put(key, v));
    return null;
}

Если метод возвращает какой-то элемент — значит он уже есть в коллекции и добавлять повторно не надо. Что и происходит далее в коде. Работа метода addObserver() в случае имеющегося observer'а в списке прекращается. Также работа прекращается в том случае, если lifecycleOwner == null.

@Override
public void addObserver(@NonNull LifecycleObserver observer) {

    <...>
    State targetState = calculateTargetState(observer);
    mAddingObserverCounter++;

    while ((statefulObserver.mState.compareTo(targetState) < 0
        && mObserverMap.contains(observer))) {
        pushParentState(statefulObserver.mState);
        statefulObserver.dispatchEvent(lifecycleOwner, 
upEvent(statefulObserver.mState));

        popParentState();
        // mState / subling may have been changed recalculate
        targetState = calculateTargetState(observer);
    }
        <...>
}

Происходит вычисление текущего состояния жизненного цикла и начинают отправляться события до тех пор, пока состояние хранящееся в наблюдателе меньше текущего.

Что же за upEvent(state: State)? Замечу также, что существует downEvent(state: State). В зависимости от того, что сейчас происходит с жизненным циклом, опираясь на текущее состояние можно определить какое событие надо отправлять наблюдателю.

Разобраться с этим просто, посмотрев на тело методов и на схему, приведенные ниже.



private static Event downEvent(State state) {
    switch (state) {
        case INITIALIZED:
            throw new IllegalArgumentException();
        case CREATED:
            return ON_DESTROY;
        case STARTED:
            return ON_STOP;
        case RESUMED:
            return ON_PAUSE;
        case DESTROYED:
            throw new IllegalArgumentException();
    }
    throw new IllegalArgumentException("Unexpected state value " + state);
}

private static Event upEvent(State state) {
    switch (state) {
        case INITIALIZED:
        case DESTROYED:
            return ON_CREATE;
        case CREATED:
            return ON_START;
        case STARTED:
            return ON_RESUME;
        case RESUMED:
            throw new IllegalArgumentException();
    }
    throw new IllegalArgumentException("Unexpected state value " + state);
}

Как LifecycleOwner сообщает LifecycleObserver'у о событиях, происходящих с жизненным циклом фрагмента?


Фрагмент, реализующий интерфейс LifecycleOwner, содержит ряд доступных для вызова методов, соответствующих событиям жизненного цикла: такие как performCreate(savedInstanceState: Bundle), performStart(), performStop() и другие.

Класс FragmentManagerImpl вызывает данные методы, во фрагменте в свою очередь вызываются соответствующие методы onStart, onStop и прочие. А также вызываются методы класса LifecycleRegistry.

void performStart() {

    <...>
    onStart();
    mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
}

В классе LifecycleRegistry вычисляется состояние, о котором надо отправить следующее событие на основе принятого события.

public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
    State next = getStateAfter(event);
    moveToState(next);
}

И после этого вычисляется какой тип события надо отправить наблюдателю — upEvent(state: State) или downEvent(state: State)

void dispatchEvent(LifecycleOwner owner, Event event) {

        State newState = getStateAfter(event);
        mState = min(mState, newState);
        mLifecycleObserver.onStateChanged(owner, event);
        mState = newState;
}

Заключение


На самом деле описана только часть классов и интерфейсов, созданных компанией Google. Но для представления того, как все происходит и что кроется за парой строчек кода этого достаточно.
Разработчики Google дали по-настоящему мощный инструмент для разработки приложений с поддержанием архитектуры. В совокупности с другими компонентами у разработчика появляется возможность разрабатывать надежные приложения и не писать собственных, не всегда идеальных решений.
Tags:
Hubs:
+14
Comments 0
Comments Leave a comment

Articles