Monday, December 10, 2012

Determining if your Android Application is in Background or Foreground

This question comes up a lot. How can you tell if your application is going into the background, or if you're just switching between activities in your own application? The "right" solution is both intrusive and, I might say, outdated. getRunningTasks() is certainly wrong (see the same question as for why). So what's the real right way?

ActivityLifecycleCallbacks

The answer is the poorly documented Application.ActivityLifecycleCallbacks, added in API level 14 (Android 4.0 Ice Cream Sandwich). Granted, this method doesn't work if you're targeting something earlier than API level 14, but hopefully we can encourage people to develop for later versions of Android and encourage phone manufacturers to update their phones.

How it works

The key is in understanding how activities coordinate with each other. When switching between activities A and B, their methods are called in this order:

A.onPause();
B.onCreate();
B.onStart();
B.onResume(); (Activity B now has user focus)
A.onStop(); (if Activity A is no longer visible on screen)

I wrote about the callback calling order between the ActivityLifecycleCallbacks and Activity classes earlier, and the key is checking if the number of resumed activities equals the number of stopped activities. If so, then your application is being backgrounded. If more activities have been resumed then stopped, then your application is in the foreground. Check it out:

// File MyLifecycleHandler.java
public class MyLifecycleHandler implements ActivityLifecycleCallbacks {
    // I use two separate variables here. You can, of course, just use one and
    // increment/decrement it instead of using two and incrementing both.
    private int resumed;
    private int stopped;
    
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    }

    public void onActivityDestroyed(Activity activity) {
    }
    
    public void onActivityResumed(Activity activity) {
        ++resumed;
    }
    
    public void onActivityPaused(Activity activity) {
    }

    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    }

    public void onActivityStarted(Activity activity) {
    }

    public void onActivityStopped(Activity activity) {
        ++stopped;
        android.util.Log.w("test", "application is being backgrounded: " + (resumed == stopped));
    }
    
    // If you want a static function you can use to check if your application is
    // foreground/background, you can use the following:
    /*
    // Replace the two variables above with these two
    private static int resumed;
    private static int stopped;
    
    // And add this public static function
    public static boolean isApplicationInForeground() {
        return resumed > stopped;
    }
    */
}



// File MyApplication.java
// Don't forget to add it to your manifest by doing
// <application android:name="your.package.MyApplication" ...
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        // Simply add the handler, and that's it! No need to add any code
        // to every activity. Everything is contained in MyLifecycleHandler
        // with just a few lines of code. Now *that's* nice.
        registerActivityLifecycleCallbacks(new MyLifecycleHandler());
    }
}

I posted this method on StackOverflow and someone asked about a special detail of the onStop() method of activities. The docs for onStop() say:

Note that this method may never be called, in low memory situations where the system does not have enough memory to keep your activity's process running after its onPause() method is called.

Since this method depends on onStop()/onResume(), you might be worried that if onStop() isn't called (because of low memory), that this method of checking for backgrounding will be invalid. This, however, is not the case. Note specifically that the docs say "your activity's process". If you're in a low memory situation, your process is actually killed (not just your activity). This method remains valid because a) if your process is killed, you can't check for backgrounding anyway, and b) if your process starts again (because one of your activities is being created again) the counter variables will be reset to zero.

Two Caveats

#1: You need to handle your own configuration changes (that is, specify android:configChanges="keyboardHidden|orientation" in your Android.xml file). If you don't, and you rotate your device, for example, then your activity will be stopped and fully destroyed before it is restarted and resumed. If you want more details on exactly why this is problematic, I talk about it in my SO answer.

#2: Note that if you use this method, you shouldn't check if your application is being backgrounded in your Activity's onPause(). This is the wrong place for checking for backgrounding. You should check for backgrounding in your Activity's onStop() method, after you call super.onStop().

As for why this is the case: onPause() is called while your activity is still in the foreground, so checking if it's in the background or foreground doesn't really make sense. If you're checking for backgrounding in onPause() it's like trying to know the future before it happens. Android doesn't give you information on what's going to happen next (because Android says it's none of your business), so you must keep track of this information yourself. You could probably work this out, but it would require you to carefully track all your started activities and Intents, plus whenever an Activity finish()es. In other words: it gets complicated. Just design your app so that you check for backgrounding in onStop().

4 comments:

  1. nice post. still searching for a reliable pre 4.0 method but the information is good.

    ReplyDelete
    Replies
    1. Yeah, I would've like a pre-4.0 method as well, but from all the searching I've done there isn't a good, reliable method (aside from manually tracking your own activities and transitions, which can get quite hairy).

      Delete
  2. Would this Library help with backward compatibility?
    https://github.com/BoD/android-activitylifecyclecallbacks-compat

    ReplyDelete
    Replies
    1. It looks like it very well might. It does require every activity to inherit from a specific base class, though.

      Delete

Note: Only a member of this blog may post a comment.