Checking if an Android application is running in the background

前端 未结 30 2839
无人共我
无人共我 2020-11-21 06:19

By background, I mean none of the application\'s activities are currently visible to the user?

相关标签:
30条回答
  • 2020-11-21 06:51

    Starting support library version 26 you can use ProcessLifecycleOwner, just add it to your dependency like described here, for example:

    dependencies {
        def lifecycle_version = "1.1.1"
    
        // ViewModel and LiveData
        implementation "android.arch.lifecycle:extensions:$lifecycle_version"
        // alternatively - Lifecycles only (no ViewModel or LiveData).
        //     Support library depends on this lightweight import
        implementation "android.arch.lifecycle:runtime:$lifecycle_version"
        annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version" // use kapt for Kotlin
    }
    

    And then just query ProcessLifecycleOwner whenever you want for app state, examples:

    //Check if app is in background
    ProcessLifecycleOwner.get().getLifecycle().getCurrentState() == Lifecycle.State.CREATED;
    
    //Check if app is in foreground
    ProcessLifecycleOwner.get().getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED);
    
    0 讨论(0)
  • 2020-11-21 06:51

    Building on @Cornstalks answer to include a couple of useful features.

    Extra features:

    • introduced singleton pattern so you can do this anywhere in the application: AppLifecycleHandler.isApplicationVisible() and AppLifecycleHandler.isApplicationInForeground()
    • added handling of duplicate events (see comments // take some action on change of visibility and // take some action on change of in foreground)

    App.java

    public class App extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
    
            registerActivityLifecycleCallbacks(AppLifecycleHandler.getInstance());
        }
    }
    

    AppLifecycleHandler.java

    public class AppLifecycleHandler implements Application.ActivityLifecycleCallbacks {
        private int resumed;
        private int started;
    
        private final String DebugName = "AppLifecycleHandler";
    
        private boolean isVisible = false;
        private boolean isInForeground = false;
    
        private static AppLifecycleHandler instance;
    
        public static AppLifecycleHandler getInstance() {
            if (instance == null) {
                instance = new AppLifecycleHandler();
            }
    
            return instance;
        }
    
        private AppLifecycleHandler() {
        }
    
        @Override
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        }
    
        @Override
        public void onActivityDestroyed(Activity activity) {
        }
    
        @Override
        public void onActivityResumed(Activity activity) {
            ++resumed;
            android.util.Log.w(DebugName, "onActivityResumed -> application is in foreground: " + (resumed > 0) + " (" + activity.getClass() + ")");
            setForeground((resumed > 0));
        }
    
        @Override
        public void onActivityPaused(Activity activity) {
            --resumed;
            android.util.Log.w(DebugName, "onActivityPaused -> application is in foreground: " + (resumed > 0) + " (" + activity.getClass() + ")");
            setForeground((resumed > 0));
        }
    
        @Override
        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
        }
    
        @Override
        public void onActivityStarted(Activity activity) {
            ++started;
            android.util.Log.w(DebugName, "onActivityStarted -> application is visible: " + (started > 0) + " (" + activity.getClass() + ")");
            setVisible((started > 0));
        }
    
        @Override
        public void onActivityStopped(Activity activity) {
            --started;
            android.util.Log.w(DebugName, "onActivityStopped -> application is visible: " + (started > 0) + " (" + activity.getClass() + ")");
            setVisible((started > 0));
        }
    
        private void setVisible(boolean visible) {
            if (isVisible == visible) {
                // no change
                return;
            }
    
            // visibility changed
            isVisible = visible;
            android.util.Log.w(DebugName, "App Visiblility Changed -> application is visible: " + isVisible);
    
            // take some action on change of visibility
        }
    
        private void setForeground(boolean inForeground) {
            if (isInForeground == inForeground) {
                // no change
                return;
            }
    
            // in foreground changed
            isInForeground = inForeground;
            android.util.Log.w(DebugName, "App In Foreground Changed -> application is in foreground: " + isInForeground);
    
            // take some action on change of in foreground
    
        }
    
        public static boolean isApplicationVisible() {
            return AppLifecycleHandler.getInstance().started > 0;
        }
    
        public static boolean isApplicationInForeground() {
            return AppLifecycleHandler.getInstance().resumed > 0;
        }
    }
    
    0 讨论(0)
  • 2020-11-21 06:51

    You should use a shared preference to store the property and act upon it using service binding from your activities. If you use binding only, (that is never use startService), then your service would run only when you bind to it, (bind onResume and unbind onPause) that would make it run on foreground only, and if you do want to work on background you can use the regular start stop service.

    0 讨论(0)
  • 2020-11-21 06:52

    DO NOT USE THIS ANSWER

    user1269737's answer is the proper (Google/Android approved) way to do this. Go read their answer and give them a +1.

    I'll leave my original answer here for posterity's sake. This was the best available back in 2012, but now Android has proper support for this.

    Original answer

    The key is using ActivityLifecycleCallbacks (note that this requires Android API level 14 (Android 4.0)). Just check if the number of stopped activities is equal to the number of started activities. If they're equal, your application is being backgrounded. If there are more started activities, your application is still visible. If there are more resumed than paused activities, your application is not only visible, but it's also in the foreground. There are 3 main states that your activity can be in, then: visible and in the foreground, visible but not in the foreground, and not visible and not in the foreground (i.e. in the background).

    The really nice thing about this method is that it doesn't have the asynchronous issues getRunningTasks() does, but you also don't have to modify every Activity in your application to set/unset something in onResumed()/onPaused(). It's just a few lines of code that's self contained, and it works throughout your whole application. Plus, there are no funky permissions required either.

    MyLifecycleHandler.java:

    public class MyLifecycleHandler implements ActivityLifecycleCallbacks {
        // I use four separate variables here. You can, of course, just use two and
        // increment/decrement them instead of using four and incrementing them all.
        private int resumed;
        private int paused;
        private int started;
        private int stopped;
    
        @Override
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        }
    
        @Override
        public void onActivityDestroyed(Activity activity) {
        }
    
        @Override
        public void onActivityResumed(Activity activity) {
            ++resumed;
        }
    
        @Override
        public void onActivityPaused(Activity activity) {
            ++paused;
            android.util.Log.w("test", "application is in foreground: " + (resumed > paused));
        }
    
        @Override
        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
        }
    
        @Override
        public void onActivityStarted(Activity activity) {
            ++started;
        }
    
        @Override
        public void onActivityStopped(Activity activity) {
            ++stopped;
            android.util.Log.w("test", "application is visible: " + (started > 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 four variables above with these four
        private static int resumed;
        private static int paused;
        private static int started;
        private static int stopped;
    
        // And these two public static functions
        public static boolean isApplicationVisible() {
            return started > stopped;
        }
    
        public static boolean isApplicationInForeground() {
            return resumed > paused;
        }
        */
    }
    

    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());
        }
    }
    

    @Mewzer has asked some good questions about this method that I'd like to respond to in this answer for everyone:

    onStop() is not called in low memory situations; is that a problem here?

    No. 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.

    The key here is "keep your activity's process running..." If this low memory situation is ever reached, your process is actually killed (not just your activity). This means that this method of checking for backgrounded-ness is still valid because a) you can't check for backgrounding anyway if your process is killed, and b) if your process starts again (because a new activity is created), the member variables (whether static or not) for MyLifecycleHandler will be reset to 0.

    Does this work for configuration changes?

    By default, no. You have to explicitly set configChanges=orientation|screensize (| with anything else you want) in your manifest file and handle the configuration changes, or else your activity will be destroyed and recreated. If you do not set this, your activity's methods will be called in this order: onCreate -> onStart -> onResume -> (now rotate) -> onPause -> onStop -> onDestroy -> onCreate -> onStart -> onResume. As you can see, there is no overlap (normally, two activities overlap very briefly when switching between the two, which is how this backgrounding-detection method works). In order to get around this, you must set configChanges so that your activity is not destroyed. Fortunately, I've had to set configChanges already in all of my projects because it was undesirable for my entire activity to get destroyed on screen rotate/resize, so I've never found this to be problematic. (thanks to dpimka for refreshing my memory on this and correcting me!)

    One note:

    When I've said "background" here in this answer, I've meant "your app is no longer visible." Android activities can be visible yet not in the foreground (for example, if there's a transparent notification overlay). That's why I've updated this answer to reflect that.

    It's important to know that Android has a weird limbo moment when switching activities where nothing is in the foreground. For this reason, if you check if your application is in the foreground when switching between activities (in the same app), you'll be told you're not in the foreground (even though your app is still the active app and is visible).

    You can check if your app is in the foreground in your Activity's onPause() method after super.onPause(). Just remember the weird limbo state I just talked about.

    You can check if your app is visible (i.e. if it's not in the background) in your Activity's onStop() method after super.onStop().

    0 讨论(0)
  • 2020-11-21 06:52

    You can use ComponentCallbacks2 to detect if the app is in background. BTW this callback is only available in API Level 14 (Ice Cream Sandwich) and above.

    You will get a call to the method:

    public abstract void onTrimMemory (int level)

    if the level is ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN then the app is in background.

    You can implement this interface to an activity, service, etc.

    public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 {
       @Override
       public void onConfigurationChanged(final Configuration newConfig) {
    
       }
    
       @Override
       public void onLowMemory() {
    
       }
    
       @Override
       public void onTrimMemory(final int level) {
         if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // app is in background
         }
       }
    }
    
    0 讨论(0)
  • 2020-11-21 06:53

    Offical docs:

    The system distinguishes between foreground and background apps. (The definition of background for purposes of service limitations is distinct from the definition used by memory management; an app might be in the background as pertains to memory management, but in the foreground as pertains to its ability to launch services.) An app is considered to be in the foreground if any of the following is true:

    1. It has a visible activity, whether the activity is started or paused.
    2. It has a foreground service.
    3. Another foreground app is connected to the app, either by binding to one of its services or by making use of one of its content providers. For example, the app is in the foreground if another app binds to its:
      • IME
      • Wallpaper service
      • Notification listener
      • Voice or text service

    If none of those conditions is true, the app is considered to be in the background.

    0 讨论(0)
提交回复
热议问题