androiddata-collection

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!


Solution

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