Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get an Android "bound" service to survive a configuration restart

Tags:

java

android

I have an android app with some global state (including some large SoundPools) that requires clean up, so following answers to my previous question I'm attempting to handle this using a Service.

I'm currently using a bound service that every activity binds/unbinds to in onStart/onStop, and when all activities stop the service becomes unbound and onDestroy called on the service, letting me release the SoundPools.

Since the activity lifecycle deliberately overlaps (the new activity onStart fires before the old one fires onStop), when navigating between activities there is always at least one activity bound, and the service stays alive.

However, if I rotate the screen to cause a configuration restart, the service is unbound and dies when the active activity goes through the configuration restart lifecycle.

How can I get around this and keep the service alive across restarts, whilst still allowing the service to die when the application is stopped?

like image 384
cjn Avatar asked Apr 23 '13 12:04

cjn


1 Answers

Ok, since this has proven to be an especially difficult question, I thought I'd post my solution here in case anyone ever hits anything similar.

There are clearly several ways to approach this, but the simplest I've used is to have a "started" service that decides when to shut itself down. My activities each bind/unbind to the service, and after a set time delay (I've used 1 minute) the service will shut itself down if no further activities have bound - this covers if the user stops using the app plus if there are any fatal activity errors.

The shutdown timer is scheduled within onUnbind() and gets cancelled in onStartCommand(), onBind(), onRebind(). If it fires, it shuts the service down cleanly, which in turn triggers a clean up of the managed state in the service's onDestroy().

My Service code is as follows:

public class LocalStateService extends Service {

    /** The binder to give to clients. */
    private final IBinder binder = new LocalStateBinder();

    /** Used for time-delayed shutdown. */
    private final Handler handler = new Handler();

    /**
     * Called before starting or the first binding.
     */
    @Override
    public void onCreate() {
        // initialise state...
    }

    /**
     * Called when this service is explicitly started.
     * @param intent    The intent passed on starting, unused
     * @param flags     Startup flags, unused
     * @param startId   Identifies each start request 
     * @return Desired restart behaviour
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        cancelShutdown();

        // if killed, we would like Android to restart this service, but don't bother re-delivering
        // the original intent used to start the service
        return START_STICKY;
    }

    /**
     * Called when the first client binds.
     * @param intent        The intent passed on binding
     * @return The binding to use
     */
    @Override
    public IBinder onBind(Intent intent) {
        cancelShutdown();
        return binder;
    }

    /**
     * Called when the first of previous clients re-binds.
     * @param intent        The intent passed on binding
     */
    @Override
    public void onRebind(Intent intent) {
        cancelShutdown();
    }

    /**
     * Called when all clients have unbound.
     * @param intent        The first intent originally passed on binding
     * @return Whether this service should be notified of rebinding
     */
    @Override
    public boolean onUnbind(Intent intent) {

        // post a callback to be run in 1 minute
        handler.postDelayed(delayedShutdown, 1000L * 60);

        // we do want onRebind called when clients return
        return true;
    }


    @Override
    public void onDestroy() {
        // state cleanup...
    }

    private Runnable delayedShutdown = new Runnable() {

        @Override
        public void run() {
            LocalStateService.this.stopSelf();
        }

    };

    /**
     * Cancel any shutdown timer that may have been set.
     */
    private void cancelShutdown() {
        // remove any shutdown callbacks registered
        handler.removeCallbacks(delayedShutdown);
    }
}

Instead of doing this from my Application, my main activity calls startService(..) in onCreate() since this will work both for initial startup and when the user goes back to using a paused app (where the service may or may not have decided to shut itself down).

Each activity then binds and unbinds as per normal.

I found that:

  • When navigating between activities, no service callback gets fired. Since the activity lifecycle overlaps, these are secondary bind/unbinds

  • When an activity restarts (e.g. screen rotation), the service gets an onUnbind() then an onRebind() call

  • When pausing the app (e.g. press home from main activity) or when finishing (e.g. press back from main activity), the service gets onUnbind() then the timer fires.

like image 63
cjn Avatar answered Sep 18 '22 11:09

cjn