What are the conditions for an application to be in the background

There is a simple criterion for determining whether an application is currently in the background. When an application is in the background, all activities in the application must not be in the running state, and all activities in the application must execute onPause when cutting the background. Therefore, we can know that the current application is in the background by judging that all the activities in the application are not in the running state. When there is one application or more activities in the running state, the application is in the foreground. Here is a typical activity lifecycle diagram.

The relationship between activity and Application

In order to determine whether an application is in the background state, it is necessary to determine whether all the activities in the application are not running. How can you tell if an activity in an application is not running? Because an application has only Application, and this application is common to all activities. How to determine if all activities in an application are not running? The Activity’s onCreate() method calls Application’s dispatchActivityCreated method.

 @MainThread
    @CallSuper
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        mFragments.dispatchCreate();
      	getApplication().dispatchActivityCreated(this, savedInstanceState);
    }
Copy the code

In fact, each life cycle of the Activity will call the corresponding method in the Application, and the corresponding relationship is shown as follows:

Application and ActivityLifecycleCallbacks relations

The disPatchActivityOnResume (disPatchActivityOnPause) and disPatchActivityOnResume (disPatchActivityOnResume) methods are used to determine whether the application is in the background. Here are the dispatchActivityResumed and dispatchActivityPaused methods in the application Because dispatchActivityResumed and dispatchActivityPaused are package permissions, we can’t inherit application and override dispatchActivityResumed and dispatchActiv The ityPaused method counts the dispatchActivityResumed and dispatchActivityPaused methods and determines whether to skip the background.


    /* package */ void dispatchActivityResumed(Activity activity) {
        Object[] callbacks = collectActivityLifecycleCallbacks();
        if(callbacks ! =null) {
            for (int i=0; i<callbacks.length; i++) { ((ActivityLifecycleCallbacks)callbacks[i]).onActivityResumed(activity); }}}/* package */ void dispatchActivityPaused(Activity activity) {
        Object[] callbacks = collectActivityLifecycleCallbacks();
        if(callbacks ! =null) {
            for (int i=0; i<callbacks.length; i++) { ((ActivityLifecycleCallbacks)callbacks[i]).onActivityPaused(activity); }}}Copy the code

But in these two methods are invoked the ActivityLifecycleCallbacks corresponding method, thus can start from the relationship between ActivityLifecycleCallbacks and application processing. Examine the code in the application, the ActivityLifecycleCallbacks is an inner class in application, the following is a method in ActivityLifecycleCallbacks.


    public interface ActivityLifecycleCallbacks {
        void onActivityCreated(Activity activity, Bundle savedInstanceState);
        void onActivityStarted(Activity activity);
        void onActivityResumed(Activity activity);
        void onActivityPaused(Activity activity);
        void onActivityStopped(Activity activity);
        void onActivitySaveInstanceState(Activity activity, Bundle outState);
        void onActivityDestroyed(Activity activity);
    }
    
Copy the code

The application and the methods of ActivityLifecycleCallbacks relationship is as follows:

/ / will ActivityLifecycleCallbacks registered in the application
    public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
        synchronized(mActivityLifecycleCallbacks) { mActivityLifecycleCallbacks.add(callback); }}// Unregister
    public void unregisterActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
        synchronized(mActivityLifecycleCallbacks) { mActivityLifecycleCallbacks.remove(callback); }}Copy the code

Because we can’t directly in the application of method to process statistics, but according to the application and ActivityLifecycleCallbacks relationship, One can define your own ActivityLifecycleCallbacks then register to the application, and then in the method of ActivityLifecycleCallbacks determine whether application of cutting Taiwan before and after statistics processing. The activity and the application and ActivityLifecycleCallbacks three relationship is as follows:

Realize the front and background switch monitoring framework

By above knowable, can realize ActivityLifecycleCallbacks interface, and turn it into application, according to the activity, application, and ActivityLifecycleCallbacks relationship, As long as the realization of the corresponding in ActivityLifecycleCallbacks interface Taiwan before and after switching logic.

Method 1: Determine whether it is in the front and back

1, implement Application. ActivityLifecycleCallbacks onActivityPaused and onActivityResumed method of interface

class Foreground
implements Application.ActivityLifecycleCallbacks {
	private boolean foreground;// Is in the foreground
    private static Foreground instance;

    public static void init(Application app){
        if (instance == null){
            instance = newForeground(); app.registerActivityLifecycleCallbacks(instance); }}public static Foreground get(a){
        return instance;
    }

	public boolean isForeground(a){
    return foreground;
	}

	public boolean isBackground(a){
    return! foreground; }public void onActivityPaused(Activity activity){
    foreground = false;
	}

	public void onActivityResumed(Activity activity){
    foreground = true; }}Copy the code

In the Foreground class, inject Foreground into the current application through init method, and monitor the life cycle changes of all activities in the application. Because you only need to check whether to switch between the resumed and the resumed methods in the onActivityPaused and onActivitymethods, you can rewrite those two methods. How to use it: You can use it in any activity:

    Foreground.get(context.getApplication()).isForeground()
Copy the code

To determine whether the current application is in the foreground or background, but this method has two disadvantages:

1, can only judge whether the current application is the front and background, but can not monitor the front and background transformation, that is to say, if you want to monitor the front and background transformation in real time, it can not.

IsForeground () is used to return false when the first activity is executed but the second activity is not executed; But the application is in the foreground.

Method 2 improved front and background listening method

The method 1 above, the problems of the first point is whether the application is in Taiwan before and after need in code acquisition, real-time switch cannot receive Taiwan before and after the notification of change, so you can use to monitor the way, the Foreground injection to monitor in the class, when the application switch Taiwan before and after traversal registered the listening class, notice the change of Taiwan before and after switching.

class Foreground
implements Application.ActivityLifecycleCallbacks {

    public interface Listener {
        public void onBecameForeground(a);// Call onBecameForeground() when the application cuts the foreground
        public void onBecameBackground(a);// Call onBecameBackground() when the application cuts the background}... }Copy the code

Defined in ActivityLifecycleCallbacks monitor Listener, and two methods used to monitor the background and foreground operation. Methods to register and unregister listeners are provided below.

private List listeners =
    new CopyOnWriteArrayList();

public void addListener(Listener listener){
    listeners.add(listener);
}

public void removeListener(Listener listener){
    listeners.remove(listener);
}
Copy the code

Then in ActivityLifecycleCallbacks onActivityResumed method and onActivityPaused method as follows:

@Override
public void onActivityResumed(Activity activity) {
    paused = false;
    booleanwasBackground = ! foreground; foreground =true;

    if(check ! =null)
        handler.removeCallbacks(check);

    if (wasBackground){
        Log.i(TAG, "went foreground");
        for (Listener l : listeners) {
            try {
                l.onBecameForeground();
            } catch (Exception exc) {
                Log.e(TAG, "Listener threw exception!", exc); }}}else {
        Log.i(TAG, "still foreground"); }}@Override
public void onActivityPaused(Activity activity) {
    paused = true;

    if(check ! =null)
        handler.removeCallbacks(check);

    handler.postDelayed(check = new Runnable(){
        @Override
        public void run(a) {
            if (foreground && paused) {
                foreground = false;
                Log.i(TAG, "went background");
                for (Listener l : listeners) {
                    try {
                        l.onBecameBackground();
                    } catch (Exception exc) {
                        Log.e(TAG, "Listener threw exception!", exc); }}}else {
                Log.i(TAG, "still foreground");
            }
        }
    }, CHECK_DELAY);
}

Copy the code

The above code provides a paused and wasBackground bool variables, which are very important.

When an activity starts an onResume method that triggers the onActivitymethod, you need to check whether the application is in the background before it starts. If it is, all listeners will be notified that the application is moved from the background to the foreground. Otherwise, if the application is already in the foreground, the onActivityResumed method cannot notify all listeners.

Paused variable as mentioned earlier, when two activities are switched, one activity executes onPause and the other onResume. In the interval between executing onPause and onResume, the application is in the foreground. However, isForeground condition is false, so this interval should be processed so that background listening cannot be called back within this interval. If the application is in the background and does not execute onActivityResumed, the onPause method is resumed by an interval. If onActivityResumed is resumed by an interval, the onPause method is resumed by an interval. Tell che to listen in on the background.

Pay attention to

Handler is added to the onPause method to delay the execution of the background listener notification, but there are still problems with the background listener, such as the user quickly cut back to the background, if the speed is less than the delay of the Handler interval, then the background listener will not receive the callback.

The complete code

public class Foreground implements Application.ActivityLifecycleCallbacks {

    public static final long CHECK_DELAY = 500;
    public static final String TAG = Foreground.class.getName();

    public interface Listener {

        void onBecameForeground(a);

        void onBecameBackground(a);

    }

    private static Foreground instance;

    private boolean foreground = false, paused = true;
    private Handler handler = new Handler();
    private List<Listener> listeners = new CopyOnWriteArrayList<>();
    private Runnable check;

    /** * Its not strictly necessary to use this method - _usually_ invoking * get with a Context gives us a path to retrieve the Application and * initialise, but sometimes (e.g. in test harness) the ApplicationContext * is ! = the Application, and the docs make no guarantees. * *@param application
     * @return an initialised Foreground instance
     */
    public static Foreground init(Application application) {
        if (instance == null) {
            instance = new Foreground();
            application.registerActivityLifecycleCallbacks(instance);
        }
        return instance;
    }

    public static Foreground get(Application application) {
        if (instance == null) {
            init(application);
        }
        return instance;
    }

    public static Foreground get(Context ctx) {
        if (instance == null) {
            Context appCtx = ctx.getApplicationContext();
            if (appCtx instanceof Application) {
                init((Application) appCtx);
            } else {
                throw new IllegalStateException(
                        "Foreground is not initialised and " +
                                "cannot obtain the Application object"); }}return instance;
    }

    public static Foreground get(a) {
        if (instance == null) {
            throw new IllegalStateException(
                    "Foreground is not initialised - invoke " +
                            "at least once with parameterised init/get");
        }
        return instance;
    }

    public boolean isForeground(a) {
        return foreground;
    }

    public boolean isBackground(a) {
        return! foreground; }public void addListener(Listener listener) {
        listeners.add(listener);
    }

    public void removeListener(Listener listener) {
        listeners.remove(listener);
    }

    @Override
    public void onActivityResumed(Activity activity) {
        paused = false;
        booleanwasBackground = ! foreground; foreground =true;

        if(check ! =null)
            handler.removeCallbacks(check);

        if (wasBackground) {
            Log.i(TAG, "went foreground");
            for (Listener l : listeners) {
                try {
                    l.onBecameForeground();
                } catch (Exception exc) {
                    Log.e(TAG, "Listener threw exception!", exc); }}}else {
            Log.i(TAG, "still foreground"); }}@Override
    public void onActivityPaused(Activity activity) {
        paused = true;

        if(check ! =null)
            handler.removeCallbacks(check);

        handler.postDelayed(check = new Runnable() {
            @Override
            public void run(a) {
                if (foreground && paused) {
                    foreground = false;
                    Log.i(TAG, "went background");
                    for (Listener l : listeners) {
                        try {
                            l.onBecameBackground();
                        } catch (Exception exc) {
                            Log.e(TAG, "Listener threw exception!", exc); }}}else {
                    Log.i(TAG, "still foreground");
                }
            }
        }, CHECK_DELAY);
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}@Override
    public void onActivityStarted(Activity activity) {}@Override
    public void onActivityStopped(Activity activity) {}@Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}@Override
    public void onActivityDestroyed(Activity activity) {}}Copy the code

Method of use

You can switch listeners before and after registering them in any activity and handle them in the callback as follows:

 public class MainActivity extends AppCompatActivity {
    Foreground.Listener foregroundListener = new Foreground.Listener() {
        @Override
        public void onBecameForeground(a) {
            Log.d("foreground"."onBecomeForeground");
        }

        @Override
        public void onBecameBackground(a) {
            Log.d("foreground"."onBecameBackground"); }};@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Foreground.get(this).addListener(foregroundListener);
    }

    @Override
    protected void onDestroy(a) {
        super.onDestroy();
        Foreground.get(this).removeListener(foregroundListener); }}Copy the code

supplement

Complement 1: The most important thing is to make use of the relationship between activity and application life cycle, because each activity will call the corresponding method in application when executing the life cycle function. Therefore, you can perform a statistical processing on the life cycle functions of all activities executed in the application, and use this statistics to determine whether the application is in the foreground or background. The relationship diagram of the three:

Add 2:

At that time, when dealing with the front and back of switching, the first thought was to judge according to the state of application. But it turns out that Application doesn’t actually provide a way to determine whether an application is in the front or back. Application is actually a process and let’s see what states this is in.

A simpler, more perfect way

The above method is too complicated to judge the application state according to the state of all the activities. Android provides a more convenient method to monitor the switch between the front and back of the application. Using ProcessLifecycleOwner to handle the ProcessLifecycleOwner listens for the state of the application. The instructions are as follows:

 * It is useful for use cases where you would like to react on your app coming to the foreground or
 * going to the background and you don't need a milliseconds accuracy in receiving lifecycle
 * events.
Copy the code

The ProcessLifecycleOwner class is specifically designed to listen for the status of the application before and after switching. LifecycleObserver implements the LifecycleObserver interface using the method definition ApplicationObserver method, which defines onForeground(), and onBackground() methods to handle the foreground and background switching.

static class ApplicationObserver implements LifecycleObserver {
        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        void onForeground(a) {
            Log.d(TAG, "onForeground!");
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        void onBackground(a) {
            Log.d(TAG, "onBackground!"); }}Copy the code

Put it where you want it

ProcessLifecycleOwner.get().getLifecycle().addObserver(new ApplicationObserver());
Copy the code

A complete example of use:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ProcessLifecycleOwner.get().getLifecycle().addObserver(new ApplicationObserver());
        setContentView(R.layout.activity_main);
    }

    class ApplicationObserver implements LifecycleObserver {
        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        void onForeground(a) {
            Log.d(TAG, "onForeground!");
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        void onBackground(a) {
            Log.d(TAG, "onBackground!"); }}}Copy the code

Very simple note that using ProcessLifecycleOwner needs to be configured in your project’s build.gradle:

 implementation "Android. Arch. Lifecycle: extensions: 1.1.1"
Copy the code

reference

1, steveliles. Making. IO/is_my_andro…

2, developer.android.com/guide/compo…

3, medium.com/@arturogdg/…