Android Lifecycle-aware Architecture Components
On November 6, 2017, Google published information about the announcement of a stable version of
architectural components . Google developers provided guidance on application architecture and introduced a number of classes and interfaces that simplify the creation of applications with a built-in architecture, facilitate the joining of new programmers to the project, and reduce the threshold for entering the adult development world for those people who have just started programming for Android.
The presented components for working with the Android life cycle can be compared with a clockwork hidden from the eyes. Just a couple of lines of code and everything works. But how is everything arranged? Anyway, is it worth using architectural components in your home projects or even in projects with hundreds of thousands of active installations?
Disclaimer
Developer code provided in the Kotlin development language. Google source code excerpts are provided in Java. In excerpts, part of the code may be omitted.
Lifecycle, lifecycle-aware components and activities
The life cycle is a very important point in the world of android development, which often does not receive enough attention. For this reason, users may experience errors in the application. For example, when making a phone call, the application may terminate with a critical error. This is due to the re-creation of the activity and the raw state preservation.
Part of the problem of re-creating activity can be avoided. For example, to prohibit re-creation - set the activity setting in the manifest android: screenOrientation = "portrait". But this will solve the problems only with the recreation of activity during configuration changes (for example, changing the orientation of the screen). The problem of the fact that at some point the operating system runs out of memory and it will destroy the process with executable activity does not solve this method. Returning to working with the application, the first thing a user sees is a critical error.
One way or another, the developer needs to take care of handling the states of the life cycle. Lifecycle-aware components come to the rescue. Architectural components have a stable version 1.0 and can be used in production development of applications.
Pros and cons of using lifecycle-aware components
Consider the practical pros and cons of using components.
There are undoubtedly more pluses
- connecting a new employee to the application development team. All android developers know and are able to use official libraries from Google. No need to spend time learning local solutions to maintain the architecture;
- less code when developing features;
- stability of the components;
- Improving the stability of the application after the implementation of components.
Minuses
- time to familiarize yourself with the components and add to the project;
- code has been added to accompany the architecture when developing a new function, but this minus is easily solved by code generation. Good articles on this topic here and here .
How to work with lifecycle-aware components?
Starting with the Support Library version 26.1.0, fragments and activities out of the box implement the LifecycleOwner interface. This interface has only one method - getLifecycle () .
To add an observer for life cycle events, it’s enough just to implement the LifecycleObserver interface in the observer class and write in the activity / fragment
private fun addLifecycleObserver() {
lifecycle.addObserver(observer)
}
Is that all? Yes, for the developer, the work ends here. It is enough in the observer code to mark the necessary methods with annotations and respond to life cycle events.
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun init(){}
It is an interesting fact that several methods can be marked with the same annotation and all of them will be called when the state of the life cycle changes.
What is hidden behind a couple of lines, how does everything work, what are the nuances?
We will find the answers to the questions below by the example of a fragment.
What returns the fragment by implementing the LifecycleOwner interface in the getLifecycle () method? Description of the main methods
The fragment implements the LifecycleOwner interface, implementing the getLifecycle (): Lifecycle method .
Lifecycle is an abstract class that defines an object as an object having an Android life cycle.
The implementation of this class LifecycleRegistry takes all the work of controlling the addition, deletion of observers, processing of life cycle events, and reporting of changes in the life cycle to all observers.
Adding an observer.
@MainThread
public abstract void addObserver(@NonNull LifecycleObserver observer);
An important nuance is that when LifecycleObserver is added to the list of observers, the observer will receive events about changes in all states that precede the current one.
That is, if LifecycleOwner is in the Lifecycle.State.STARTED state when adding LifecycleObserver, the latter will receive two Lifecycle.Event.ON_CREATE and Lifecycle.Event.ON_START events .
This means that we have a guarantee that our observer will go through all stages of initialization, relying on events in the life cycle, and will not miss any stage of configuration.
Removing an observer from the observer list occurs in the method.
@MainThread
public abstract void removeObserver(@NonNull LifecycleObserver observer);
If the removal of the observer occurs during a change in the state of the life cycle and the sending of an event about a change in state is called after the removal, the observer will not receive this event.
If in the observer several methods expect one event and at least one of the methods was called during the removal of the observer from the list of observers, then all other methods will also be called and only after this will be deleted.
Return current status upon request.
@MainThread
public abstract State getCurrentState();
At any time, you can request the current state of the life cycle and, based on the answer, take any action. The method will return an instance of the State enumeration.
The following types of State
INITIALIZED exist - this state corresponds to the time when the entity implementing the LifecycleOwner interface was created, but the onCreate () method was not called yet.
CREATED - this state is active after calling onCreate () and before calling onStop ().
STARTED - this state is active after calling the onStart () method and before calling onPause ().
RESUMED - this state occurs after calling the onResume () method.
DESTROYED- This state occurs immediately before the onDestroy () call. When this state occurs, LifecycleOwner no longer sends state change events.
What happens when an observer is added to the observer list?
@Override
public void addObserver(@NonNull LifecycleObserver observer) {
State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
ObserverWithState statefulObserver = new ObserverWithState(observer,
initialState);
<...>
}
When the lifecycle.addObserver (observer) method is called, the observer is placed in the constructor of the ObserverWithState wrapper class instance. As the class name implies, this class stores an observer with the last processed state of the life cycle. Initially sets the state to DESTROYED or 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;
}
<...>
}
After creating an instance of the observer with the last processed state of the life cycle, we try to add the observer to the FastSafeIterableMap collection using the putIfAbsent () method.
@Override
public V putIfAbsent(@NonNull K key, @NonNull V v) {
Entry current = get(key);
if (current != null) {
return current.mValue;
}
mHashMap.put(key, put(key, v));
return null;
}
If the method returns an element, then it already exists in the collection and you do not need to add it again. Which happens later in the code. The work of the addObserver () method in the case of an existing observer in the list is terminated. Also, work is terminated if 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);
}
<...>
}
The current state of the life cycle is calculated, and events begin to be sent until the state stored in the observer is less than the current one.
What is upEvent (state: State)? I also note that there is downEvent (state: State). Depending on what is happening with the life cycle, based on the current state, you can determine which event should be sent to the observer.
Dealing with this is simple by looking at the body of the methods and at the diagram below.
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);
}
How does LifecycleOwner inform LifecycleObserver about events that happen to the fragment life cycle?
The fragment that implements the LifecycleOwner interface contains a number of callable methods that correspond to life cycle events: such as performCreate (savedInstanceState: Bundle) , performStart () , performStop (), and others.
The FragmentManagerImpl class calls these methods, in turn the corresponding onStart, onStop, and other methods are called in the fragment. It also calls the methods of the LifecycleRegistry class.
void performStart() {
<...>
onStart();
mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
}
The LifecycleRegistry class computes the state about which the next event should be sent based on the received event.
public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
State next = getStateAfter(event);
moveToState(next);
}
And after that it is calculated what type of event should be sent to the observer - upEvent (state: State) or downEvent (state: State)
void dispatchEvent(LifecycleOwner owner, Event event) {
State newState = getStateAfter(event);
mState = min(mState, newState);
mLifecycleObserver.onStateChanged(owner, event);
mState = newState;
}
Conclusion
In fact, only part of the classes and interfaces created by Google is described. But to imagine how everything happens and what lies behind a couple of lines of code, this is enough.
Google developers have provided a truly powerful tool for developing applications with architecture support. Together with other components, the developer has the opportunity to develop reliable applications and not write their own, not always ideal solutions.