How to detect when an Android app goes to the background and come back to the foreground

后端 未结 30 1353
独厮守ぢ
独厮守ぢ 2020-11-22 00:56

I am trying to write an app that does something specific when it is brought back to the foreground after some amount of time. Is there a way to detect when an app is sent to

相关标签:
30条回答
  • 2020-11-22 01:12

    2018: Android supports this natively through lifecycle components.

    March 2018 UPDATE: There is now a better solution. See ProcessLifecycleOwner. You will need to use the new architecture components 1.1.0 (latest at this time) but it’s specifically designed to do this.

    There’s a simple sample provided in this answer but I wrote a sample app and a blog post about it.

    Ever since I wrote this back in 2014, different solutions arose. Some worked, some were thought to be working, but had flaws (including mine!) and we, as a community (Android) learned to live with the consequences and wrote workarounds for the special cases.

    Never assume a single snippet of code is the solution you’re looking for, it’s unlikely the case; better yet, try to understand what it does and why it does it.

    The MemoryBoss class was never actually used by me as written here, it was just a piece of pseudo code that happened to work.

    Unless there’s valid reason for you not to use the new architecture components (and there are some, especially if you target super old apis), then go ahead and use them. They are far from perfect, but neither were ComponentCallbacks2.

    UPDATE / NOTES (November 2015): People has been making two comments, first is that >= should be used instead of == because the documentation states that you shouldn't check for exact values. This is fine for most cases, but bear in mind that if you only care about doing something when the app went to the background, you will have to use == and also combine it with another solution (like Activity Lifecycle callbacks), or you may not get your desired effect. The example (and this happened to me) is that if you want to lock your app with a password screen when it goes to the background (like 1Password if you're familiar with it), you may accidentally lock your app if you run low on memory and are suddenly testing for >= TRIM_MEMORY, because Android will trigger a LOW MEMORY call and that's higher than yours. So be careful how/what you test.

    Additionally, some people have asked about how to detect when you get back.

    The simplest way I can think of is explained below, but since some people are unfamiliar with it, I'm adding some pseudo code right here. Assuming you have YourApplication and the MemoryBoss classes, in your class BaseActivity extends Activity (you will need to create one if you don't have one).

    @Override
    protected void onStart() {
        super.onStart();
    
        if (mApplication.wasInBackground()) {
            // HERE YOU CALL THE CODE YOU WANT TO HAPPEN ONLY ONCE WHEN YOUR APP WAS RESUMED FROM BACKGROUND
            mApplication.setWasInBackground(false);
        }
    }
    

    I recommend onStart because Dialogs can pause an activity so I bet you don't want your app to think "it went to the background" if all you did was display a full screen dialog, but your mileage may vary.

    And that's all. The code in the if block will only be executed once, even if you go to another activity, the new one (that also extends BaseActivity) will report wasInBackground is false so it won't execute the code, until onMemoryTrimmed is called and the flag is set to true again.

    Hope that helps.

    UPDATE / NOTES (April 2015): Before you go all Copy and Paste on this code, note that I have found a couple of instances where it may not be 100% reliable and must be combined with other methods to achieve the best results. Notably, there are two known instances where the onTrimMemory call back is not guaranteed to be executed:

    1. If your phone locks the screen while your app is visible (say your device locks after nn minutes), this callback is not called (or not always) because the lockscreen is just on top, but your app is still "running" albeit covered.

    2. If your device is relatively low on memory (and under memory stress), the Operating System seems to ignore this call and go straight to more critical levels.

    Now, depending how important it's for you to know when your app went to the background, you may or may not need to extend this solution together with keeping track of the activity lifecycle and whatnot.

    Just keep the above in mind and have a good QA team ;)

    END OF UPDATE

    It may be late but there's a reliable method in Ice Cream Sandwich (API 14) and Above.

    Turns out that when your app has no more visible UI, a callback is triggered. The callback, which you can implement in a custom class, is called ComponentCallbacks2 (yes, with a two). This callback is only available in API Level 14 (Ice Cream Sandwich) and above.

    You basically get a call to the method:

    public abstract void onTrimMemory (int level)
    

    The Level is 20 or more specifically

    public static final int TRIM_MEMORY_UI_HIDDEN
    

    I've been testing this and it always works, because level 20 is just a "suggestion" that you might want to release some resources since your app is no longer visible.

    To quote the official docs:

    Level for onTrimMemory(int): the process had been showing a user interface, and is no longer doing so. Large allocations with the UI should be released at this point to allow memory to be better managed.

    Of course, you should implement this to actually do what it says (purge memory that hasn't been used in certain time, clear some collections that have been sitting unused, etc. The possibilities are endless (see the official docs for other possible more critical levels).

    But, the interesting thing, is that the OS is telling you: HEY, your app went to the background!

    Which is exactly what you wanted to know in the first place.

    How do you determine when you got back?

    Well that's easy, I'm sure you have a "BaseActivity" so you can use your onResume() to flag the fact that you're back. Because the only time you will be saying you're not back is when you actually receive a call to the above onTrimMemory method.

    It works. You don't get false positives. If an activity is resuming, you're back, 100% of the times. If the user goes to the back again, you get another onTrimMemory() call.

    You need to suscribe your Activities (or better yet, a custom class).

    The easiest way to guarantee that you always receive this is to create a simple class like this:

    public class MemoryBoss 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) {
                // We're in the Background
            }
            // you might as well implement some memory cleanup here and be a nice Android dev.
        }
    }
    

    In order to use this, in your Application implementation (you have one, RIGHT?), do something like:

    MemoryBoss mMemoryBoss;
    @Override
    public void onCreate() {
       super.onCreate();
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
          mMemoryBoss = new MemoryBoss();
          registerComponentCallbacks(mMemoryBoss);
       } 
    }
    

    If you create an Interface you could add an else to that if and implement ComponentCallbacks (without the 2) used in anything below API 14. That callback only has the onLowMemory() method and does not get called when you go to the background, but you should use it to trim memory.

    Now launch your App and press home. Your onTrimMemory(final int level) method should be called (hint: add logging).

    The last step is to unregister from the callback. Probably the best place is the onTerminate() method of your App, but, that method doesn't get called on a real device:

    /**
     * This method is for use in emulated process environments.  It will
     * never be called on a production Android device, where processes are
     * removed by simply killing them; no user code (including this callback)
     * is executed when doing so.
     */
    

    So unless you really have a situation where you no longer want to be registered, you can safety ignore it, since your process is dying at OS level anyway.

    If you decide to unregister at some point (if you, for example, provide a shutdown mechanism for your app to clean up and die), you can do:

    unregisterComponentCallbacks(mMemoryBoss);
    

    And that's it.

    0 讨论(0)
  • 2020-11-22 01:12

    Based on Martín Marconcinis answer (thanks!) I finally found a reliable (and very simple) solution.

    public class ApplicationLifecycleHandler implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {
    
        private static final String TAG = ApplicationLifecycleHandler.class.getSimpleName();
        private static boolean isInBackground = false;
    
        @Override
        public void onActivityCreated(Activity activity, Bundle bundle) {
        }
    
        @Override
        public void onActivityStarted(Activity activity) {
        }
    
        @Override
        public void onActivityResumed(Activity activity) {
    
            if(isInBackground){
                Log.d(TAG, "app went to foreground");
                isInBackground = false;
            }
        }
    
        @Override
        public void onActivityPaused(Activity activity) {
        }
    
        @Override
        public void onActivityStopped(Activity activity) {
        }
    
        @Override
        public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
        }
    
        @Override
        public void onActivityDestroyed(Activity activity) {
        }
    
        @Override
        public void onConfigurationChanged(Configuration configuration) {
        }
    
        @Override
        public void onLowMemory() {
        }
    
        @Override
        public void onTrimMemory(int i) {
            if(i == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN){
                Log.d(TAG, "app went to background");
                isInBackground = true;
            }
        }
    }
    

    Then add this to your onCreate() of your Application class

    public class MyApp extends android.app.Application {
    
        @Override
        public void onCreate() {
            super.onCreate();
    
            ApplicationLifeCycleHandler handler = new ApplicationLifeCycleHandler();
            registerActivityLifecycleCallbacks(handler);
            registerComponentCallbacks(handler);
    
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 01:12

    In your Application add the callback and check for root activity in a way like this:

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityStopped(Activity activity) {
            }
    
            @Override
            public void onActivityStarted(Activity activity) {
            }
    
            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
            }
    
            @Override
            public void onActivityResumed(Activity activity) {
            }
    
            @Override
            public void onActivityPaused(Activity activity) {
            }
    
            @Override
            public void onActivityDestroyed(Activity activity) {
            }
    
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                if (activity.isTaskRoot() && !(activity instanceof YourSplashScreenActivity)) {
                    Log.e(YourApp.TAG, "Reload defaults on restoring from background.");
                    loadDefaults();
                }
            }
        });
    }
    
    0 讨论(0)
  • 2020-11-22 01:14

    The android.arch.lifecycle package provides classes and interfaces that let you build lifecycle-aware components

    Your application should implement the LifecycleObserver interface:

    public class MyApplication extends Application implements LifecycleObserver {
    
        @Override
        public void onCreate() {
            super.onCreate();
            ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
        }
    
        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        private void onAppBackgrounded() {
            Log.d("MyApp", "App in background");
        }
    
        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        private void onAppForegrounded() {
            Log.d("MyApp", "App in foreground");
        }
    }
    

    To do that, you need to add this dependency to your build.gradle file:

    dependencies {
        implementation "android.arch.lifecycle:extensions:1.1.1"
    }
    

    As recommended by Google, you should minimize the code executed in the lifecycle methods of activities:

    A common pattern is to implement the actions of the dependent components in the lifecycle methods of activities and fragments. However, this pattern leads to a poor organization of the code and to the proliferation of errors. By using lifecycle-aware components, you can move the code of dependent components out of the lifecycle methods and into the components themselves.

    You can read more here: https://developer.android.com/topic/libraries/architecture/lifecycle

    0 讨论(0)
  • 2020-11-22 01:14

    There are no straightforward lifecycle methods to tell you when the whole Application goes background/foreground.

    I have done this with simple way. Follow the below instructions to detect application background/foreground phase.

    With a little workaround, it is possible. Here, ActivityLifecycleCallbacks comes to the rescue. Let me walk through step-by-step.

    1. First, create a class that extends the android.app.Application and implements the ActivityLifecycleCallbacks interface. In the Application.onCreate(), register the callback.

      public class App extends Application implements 
          Application.ActivityLifecycleCallbacks {
      
          @Override
          public void onCreate() {
              super.onCreate();
              registerActivityLifecycleCallbacks(this);
          }
      }
      
    2. Register the “App” class in the Manifest as below, <application android:name=".App".

    3. There will be at least one Activity in the started state when the app is in the foreground and there will be no Activity in the started state when the app is in the background.

      Declare 2 variables as below in the “App” class.

      private int activityReferences = 0;
      private boolean isActivityChangingConfigurations = false;
      

      activityReferences will keep the count of number of activities in the started state. isActivityChangingConfigurations is a flag to indicate if the current Activity is going through configuration change like an orientation switch.

    4. Using the following code you can detect if the App comes foreground.

      @Override
      public void onActivityStarted(Activity activity) {
          if (++activityReferences == 1 && !isActivityChangingConfigurations) {
              // App enters foreground
          }
      }
      
    5. This is how to detect if the App goes background.

      @Override
      public void onActivityStopped(Activity activity) {
          isActivityChangingConfigurations = activity.isChangingConfigurations();
          if (--activityReferences == 0 && !isActivityChangingConfigurations) {
              // App enters background
          }
      }
      

    How it works:

    This is a little trick done with the way the Lifecycle methods are called in sequence. Let me walkthrough a scenario.

    Assume that the user launches the App and the Launcher Activity A is launched. The Lifecycle calls will be,

    A.onCreate()

    A.onStart() (++activityReferences == 1) (App enters Foreground)

    A.onResume()

    Now Activity A starts Activity B.

    A.onPause()

    B.onCreate()

    B.onStart() (++activityReferences == 2)

    B.onResume()

    A.onStop() (--activityReferences == 1)

    Then the user navigates back from Activity B,

    B.onPause()

    A.onStart() (++activityReferences == 2)

    A.onResume()

    B.onStop() (--activityReferences == 1)

    B.onDestroy()

    Then the user presses Home button,

    A.onPause()

    A.onStop() (--activityReferences == 0) (App enters Background)

    In case, if the user presses Home button from Activity B instead of Back button, still it will be the same and activityReferences will be 0. Hence, we can detect as the App entering Background.

    So, what’s the role of isActivityChangingConfigurations? In the above scenario, suppose the Activity B changes the orientation. The callback sequence will be,

    B.onPause()

    B.onStop() (--activityReferences == 0) (App enters Background??)

    B.onDestroy()

    B.onCreate()

    B.onStart() (++activityReferences == 1) (App enters Foreground??)

    B.onResume()

    That’s why we have an additional check of isActivityChangingConfigurations to avoid the scenario when the Activity is going through the Configuration changes.

    0 讨论(0)
  • 2020-11-22 01:15

    Edit 2: What I've written below will not actually work. Google has rejected an app that includes a call to ActivityManager.getRunningTasks(). From the documentation, it is apparent that this API is for debugging and development purposes only. I'll be updating this post as soon as I have time to update the GitHub project below with a new scheme that uses timers and is almost as good.

    Edit 1: I've written up a blog post and created a simple GitHub repository to make this really easy.

    The accepted and top rated answer are both not really the best approach. The top rated answer's implementation of isApplicationBroughtToBackground() does not handle the situation where the Application's main Activity is yielding to an Activity that is defined in the same Application, but it has a different Java package. I came up with a way to do this that will work in that case.

    Call this in onPause(), and it will tell you if your application is going into the background because another application has started, or the user has pressed the home button.

    public static boolean isApplicationBroughtToBackground(final Activity activity) {
      ActivityManager activityManager = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
      List<ActivityManager.RunningTaskInfo> tasks = activityManager.getRunningTasks(1);
    
      // Check the top Activity against the list of Activities contained in the Application's package.
      if (!tasks.isEmpty()) {
        ComponentName topActivity = tasks.get(0).topActivity;
        try {
          PackageInfo pi = activity.getPackageManager().getPackageInfo(activity.getPackageName(), PackageManager.GET_ACTIVITIES);
          for (ActivityInfo activityInfo : pi.activities) {
            if(topActivity.getClassName().equals(activityInfo.name)) {
              return false;
            }
          }
        } catch( PackageManager.NameNotFoundException e) {
          return false; // Never happens.
        }
      }
      return true;
    }
    
    0 讨论(0)
提交回复
热议问题