Goal

Replace manual handling of callbacks and potential memory leak sources with Lifecycle-Aware Components.

Problem

An Activity or Fragment may include callbacks such as network calls or heavy computational calls that may take a noticeable amount of time and are thus performed on a different thread to avoid blocking the main thread and freezing the user’s screen.

During this period, the activity may transition into a state where it can no longer accept UI changes. It may have called onStop where it is no longer on the foreground, or onDestroy where it no longer exists. Doing so could result in a memory leak or even crash your application.

One such solution to this problem is to manually dispose of callbacks and observers in these lifecycle methods of the Activity or Fragment. The problem with this approach is that it results in much boilerplate and could get disaorganised very quickly, especially if you have lots of callbacks to dispose.

Callbacks and observers are not the only cases that require lifecycle-based management. Other such cases include but are not limited to:

  • Switching between fine-grained location updates when the app is in the foreground, and course-grained location updates when the app is in the background
  • Starting video buffering as soon as possible but deferring playback to when the activity/app is fully started
  • Starting and stopping live-network update streams (such as when listening to a real-time database)
  • Pausing and resuming animated drawables based on whether the activity is in the foreground

Examples taken from the official documentation on Lifecycle Components

 

The Better Solution

One of the many Architecture Components include Lifecycle-Aware Components. The two main components to use are LifecycleObserver and LifecycleOwner.

If an Activity/Fragment implements LifecycleOwner, it can utilise components that implement LifecycleObserver to handle all the lifecycle-based logic internally within those components instead of in the Activity/Fragment. This is possible because classes that implement LifecycleOwner must implement a single method: getLifecycle().

Based on the Lifecycle value returned by this method, a LifecycleObserver can avoid invoking certain callbacks if the owner is not in a state to do so.

A very popular Android component that makes use of Lifecycle-Aware components is LiveData. The observe(lifecycleOwner, observer) method takes in a LifecycleOwner as a parameter and makes use of a LifecycleObserver within its code.

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    assertMainThread("observe");
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // ignore
        return;
    }
    LiveData.LifecycleBoundObserver wrapper = new LiveData.LifecycleBoundObserver(owner, observer);
    LiveData.ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    if (existing != null && !existing.isAttachedTo(owner)) {
        throw new IllegalArgumentException("Cannot add the same observer"
                + " with different lifecycles");
    }
    if (existing != null) {
        return;
    }
    owner.getLifecycle().addObserver(wrapper);
}

It does this to achieve three lifecycle-based functionalities:

  • The observer will only receive events if the owner is in Lifecycle.State#STARTED or Lifecycle.State#RESUMED state (active).
  • If the owner moves to the Lifecycle.State#DESTROYED state, the observer will automatically be removed.
  • If the given owner is already in Lifecycle.State#DESTROYED state, LiveData ignores the call.

The first parameter passed into this method, the owner, is often overlooked as this (referring to the Activity or Fragment). Activities and Fragments in Support Library 26.1.0 already implement LifecycleOwner.

Implementing your own LifecycleOwner

Beyond Activities and Fragments, it’s possible to make any class a LifecycleOwner.

LifecycleRegistry is a class that extends Lifecycle which allows you to determine how it transitions through lifecycle states. You can use this to return the lifecycle state you want your class to be in and return it with getLifecycle().

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
    }
}

Snippet taken from the official documentation on Lifecycle Components

How do Lifecycle states move in Activities?

Before any lifecycle methods occur, the activity’s state is simply INITIALIZED. After which, the state is moved naturally in its lifecycle methods. The state is CREATED upon onCreate(), and RESUMED upon onResume(), you know the drill.

There is no PAUSED or STOPPED state (there is DESTROYED though), however, these “wind down” lifecycle methods simply reduce the state. E.g. onPause() moves the state from RESUMED to STARTED.

There is however, a special case for the ON_STOP lifecycle event. This is triggered in the activity’s onStop(), yes, but it is also triggered onSaveInstanceState(). This is to prevent any inconsistencies in the navigation state of your app, as there is a gap between when onSavedInstanceState() and onStop() are called.