I create things. I blog about it. Sometimes.

24 August 2015

Tracking the Application Lifecycle on Android

tl;dr Below, you can grab a simple Java class that robustly tracks when your overall Android application is in the fore- and background.

So you have been developing that fancy Android app for a while now. It has grown and contains a sizable number of Activities. Depending on what your app actually does, you have also had the need to manage some application state or even some functional modules independently of those Activities. This is most likely where you started to write a MyApplication class, subclassed from Android’s Application.

Things you might be doing in that central application class:

  • Initialize a crash reporter (such as Fabric/Crashlytics)
  • Initialize a dependency injection framework (such as Dagger)
  • Initialize an ad SDK or an analytics SDK
  • Set a thread policy for your app
  • Open/close a disk cache
  • Start/stop a location listener
  • Register/unregister an application-wide event bus

While initialization tasks can easily be performed in the .onCreate() method of your application class, the latter tasks require you to call certain methods twice - once when the application becomes awake, and once when it goes to sleep. Well, easy enough - just override the .onStart() and .onStop() methods of the Android Application and you’re done, right?

Take a card, my friend:

I’m sorry: Go directly to Jail. Do not pass Go. Do not collect $200. While you and I might have a pretty clear idea of what it means for an application to be started or stopped, the methods .onStart() and .onStop(), which we know and take for granted with Activities, do not exist for Android’s Application.

The Android Application class basically only provides methods for events that affect the application as a whole, e.g. a change of the system locale or a low memory warning. To track the application lifecycle, we have to jump through a few hoops. I’ll show you two ways to do it.

The Ugly Way: Custom Activities

Of course we could use the .onPause() and .onResume() methods of each Activity to track the overall application status. This would go something like:

public class MyApplication extends Application {
    
    private int numStarted;
    
    ...
    
    public activityStarted() {
        numStarted++;
    }
    
    public activityStopped() {
        numStarted--;
    }
}

public class SomeActivity extends Activity {

    ...

    @Override
    protected void onStart() {
        super.onStart();
        ((MyApplication) getApplication()).activityStarted();
    }

    @Override
    protected void onStop() {
        ((My Application) getApplication()).activityStopped();
        super.onStop();
    }
}

The parameter numStarted would provide us with the count of Activities that are currently active. As long as this number is ≥ 1 (actually, it should never be greater than one), the application itself is active. Obviously, adding this code to every single activity in your project is ugly. So you would want to encapsulate the above logic in a generic TrackingActivity and have each of your Activities extend that, like so:

public class TrackingActivity extends Activity {

    ...

    @Override
    protected void onStart() {
        super.onStart();
        ((MyApplication) getApplication()).activityStarted();
    }

    @Override
    protected void onStop() {
        ((My Application) getApplication()).activityStopped();
        super.onStop();
    }
}

public class SomeActivity1 extends TrackingActivity {
    ...
}

public class SomeActivity2 extends TrackingActivity {
    ...
}

While this is slightly more elegant, you still have to modify each of your activities and introduce an extra inheritance step. It turns out there is a better way.

The Elegant Way: ActivityLifecycleCallbacks

If you’re willing and able to build your app for API level 14 and up, there is a much more elegant way to implement the above logic. As a reminder: API level 14 means Android 4.0.3 - more than 97% of Android users run this or a newer version. Via the newly introduced ActivityLifeCycleCallbacks, the Application does have access to the lifecycle state of each activity. With these callbacks at hand, we can rewrite our example as follows:

public class MyApplication extends Application
    implements Application.ActivityLifecycleCallbacks {

    private int numStarted;
    
    ...
    
    @Override
    public void onCreate() {
        ...
        registerActivityLifecycleCallbacks(this);
    }
        
    @Override
    public void onActivityStarted(Activity activity) {
        numStarted++;
    }
    
    @Override
    public void onActivityStopped(Activity activity) {
        numStarted--;
    }
}

As promised, this variant is much cleaner, since we don’t have to modify a single Activity.

Careful with Resume/Pause

Note that in the above example, we have have only tracked the started/stopped state of our app’s Activities. For most usecases this will be sufficient. However, as you may remember, there is also a resumed/paused state. To recap, the difference is this:

  • “Between onStart() and onStop() […] the user can see the activity on-screen, though it may not be in the foreground and interacting with the user.”

  • “Between onResume() and onPause() […] the activity is in front of all other activities and interacting with the user.”

In other words, there are some lifecycle corner cases, in which your current Activity could be paused but not (yet) stopped, i.e. the user will see it, but cannot interact with it. You might be tempted to detect this application state by counting the number of resumed activities, like so:

public class MyApplication extends Application
    implements Application.ActivityLifecycleCallbacks {

    private int numResumed;
    
    ...
    
    @Override
    public void onCreate() {
        ...
        registerActivityLifecycleCallbacks(this);
    }
        
    @Override
    public void onActivityResumed(Activity activity) {
        numResumed++;
    }
    
    @Override
    public void onActivityPaused(Activity activity) {
        // CAREFUL - NOT A GOOD IDEA!
        numResumed--;
    }
}

Unfortunately, this is not a good idea because of the order of events in Activity transitions. When we transition from Activity A to Activity B, the deterministic order of lifecycle events is:

Pause ACreate BStart BResume BStop A

Because A is paused before B is resumed, your numResumed count would intermittently be 0. This would only be for a brief time, but long enough to trigger any application logic based on this count to think that your application was briefly paused. You might attempt to debounce such transitions, however, such logic is bound to fail when your application really does go to sleep.

In a nutshell, if you don’t consider your application asleep during Activity transitions (I certainly don’t) you have to accept that onPause() and onStop() can only be reliably detected at the same time (namely, when the last Activity is stopped).

The Takeaway

If you read all the mambojambo above - congratulations! If you didn’t - no worries. I have put the essential logic in a small class called ActivityLifecycleHandler. It provides the following simple interface:

public class ActivityLifecycleHandler
    implements Application.ActivityLifecycleCallbacks {

    /** Informs the listener about application lifecycle events. */
    public interface LifecycleListener {
        /** Called right before the application is stopped. */
        void onApplicationStopped();

        /** Called right after the application has been started. */
        void onApplicationStarted();

        /** Called when the application has gone to the background. */
        void onApplicationPaused();

        /** Called right after the application has come to the foreground. */
        void onApplicationResumed();
    }
    
    ...
}

The callback methods should be self-explanatory and do what we initially set out to do: They alert you when your application becomes awake or goes to sleep. Use the class like this:

public class MyApplication extends Application
    implement ActivityLifecycleHandler.LifecycleListener {
    
    @Override
    public void onCreate() {
        ...
        // Register a lifecycle handler to track the foreground/background state
        registerActivityLifecycleCallbacks(new ActivityLifecycleHandler(this));
    }
    
    @Override
    public void onApplicationStopped() {
        // Do something
    }

    @Override
    public void onApplicationStarted() {
        // Do something
    }

    @Override
    public void onApplicationPaused() {
        // Do something
    }

    @Override
    public void onApplicationResumed() {
        // Do something
    }
}

That’s it. As always, you can

Grab the code here

and I appreciate your feedback and comments below.