Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect when app is opened/resumed

I'd like to check-in and checkout a user with the server every time that the app is opened/closed, whether it is launched or resumed from the task drawer. Is there is a way to do this while avoiding having to call a function in each Activity?

Thank you!

like image 425
Tom McFarlin Avatar asked Apr 28 '15 16:04

Tom McFarlin


People also ask

How do you know when an app is opened?

To open Quick Settings, from the top of the screen, swipe down twice. To see the number of active apps running in the background: At the bottom left, tap # active apps. Or, at the bottom right, tap the number next to Settings and Power .

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

The onPause() and onResume() methods are called when the application is brought to the background and into the foreground again. However, they are also called when the application is started for the first time and before it is killed. You can read more in Activity.


2 Answers

EDIT

In this answer, matdev brought to my attention the more modern approach to listening to app lifecycle events via ProcessLifeCycleOwner. See https://developer.android.com/topic/libraries/architecture/lifecycle

As such, to better organize the desired session management functionality, the following structure should be used. Register the SessionTracker in onCreate of the MyApplication. Functionality related to tracking user sessions is then sequestered to the SessionTracker class.

First add to your build.gradle

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

Then, implement the following:

public class MyApplication extends Application {  

    @Override
    public void onCreate() {
        super.onCreate();
        ProcessLifecycleOwner.get().getLifecycle().addObserver(SessionTracker.getInstance());
    }
}

public class SessionTracker implements LifecycleObserver {
    private static SessionTracker sSessionTracker;
    private static final long MAX_ACTIVITY_TRANSITION_TIME_MS = 2000;  // Time allowed for transitions

    private Timer mStopDelayTimer;
    private TimerTask mActivityTransitionTimerTask;
    private boolean mWasInBackground = true;
    private AppSession mAppSession;

    public static SessionTracker getInstance() {
        if (sSessionTracker == null) {
            sSessionTracker = new SessionTracker();
        }
        return sSessionTracker;
    }

    private SessionTracker() {
        // no-op
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    private void onLifecycleStop() {
        submitAppSession(appSession);
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    private void onLifecycleStart() {
        mAppSession = new AppSession();
    }

    private void submitAppSession(AppSession appSession) {
        // TODO submit app session here
    }
}

public class AppSession {
    /* TODO */
}

PREVIOUS ANSWER

The answer provided by d60402 here and the suggestion by Hanno Binder to register activity callbacks using Application.registerActivityLifecycleCallbacks() led me to this solution.

I extended Application and registered callbacks to Activity methods onPause and onStart as shown below. In these methods a timer is started/stopped (one activity being exited where onPause is called, a new one being entered where onStart is called). The flag "wasInBackground" is toggled when the app determined to be in the background/foreground (true/false). If the app was in the background when the onStart callback is run, "appEntered" is called. If the time passed between onPause and onStart callbacks is greater than a specified time (giving enough time for activity transitions) "appExited" is called where the app session is considered to be finished.

public class MyApplication extends Application {

public static final String LOG_TAG = "MyApp";

public boolean wasInBackground = true;

private AppSession appSession;
private Timer mActivityTransitionTimer;
private TimerTask mActivityTransitionTimerTask;
private final long MAX_ACTIVITY_TRANSITION_TIME_MS = 2000;  // Time allowed for transitions

Application.ActivityLifecycleCallbacks activityCallbacks = new Application.ActivityLifecycleCallbacks() {

    @Override
    public void onActivityResumed(Activity activity) {

        if (wasInBackground) {
            //Do app-wide came-here-from-background code
            appEntered();
        }
        stopActivityTransitionTimer();
    }

    @Override
    public void onActivityPaused(Activity activity) {
        startActivityTransitionTimer();
    }

    ...

};

@Override
public void onCreate() {
    super.onCreate();
    registerActivityLifecycleCallbacks(activityCallbacks);
}

public void startActivityTransitionTimer() {
    this.mActivityTransitionTimer = new Timer();
    this.mActivityTransitionTimerTask = new TimerTask() {
        public void run() {
            // Task is run when app is exited
            wasInBackground = true;
            appExited();
        }
    };

    this.mActivityTransitionTimer.schedule(mActivityTransitionTimerTask,
            MAX_ACTIVITY_TRANSITION_TIME_MS);
}

public void stopActivityTransitionTimer() {
    if (this.mActivityTransitionTimerTask != null) {
        this.mActivityTransitionTimerTask.cancel();
    }

    if (this.mActivityTransitionTimer != null) {
        this.mActivityTransitionTimer.cancel();
    }

    this.wasInBackground = false;
}

private void appEntered() {
    Log.i(LOG_TAG, "APP ENTERED");

    appSession = new AppSession();
}

private void appExited() {
    Log.i(LOG_TAG, "APP EXITED");

    appSession.finishAppSession();

    // Submit AppSession to server
    submitAppSession(appSession);
    long sessionLength = (appSession.getT_close() - appSession.getT_open())/1000L;
    Log.i(LOG_TAG, "Session Length: " + sessionLength);
}
like image 143
Tom McFarlin Avatar answered Oct 25 '22 19:10

Tom McFarlin


You could look into Application.registerActivityLifecycleCallbacks() &c.

like image 43
JimmyB Avatar answered Oct 25 '22 20:10

JimmyB