Search code examples
androidcachingsharedpreferencesonpause

What can I use in Application that is similar to Activity.onPause()


I am looking for some callback that is similar to Activity.onPause(), but is more general (I don't have an Activity, since this code runs in the background) that is called before my Application is being killed. I din't try Application.onLowMemory() 'cause according to the docs it's not always called [I mean my app could be (and I know sometimes is) killed also when I exit from some activity by back or home button].

I have a LocationListener class in my app that does some pretty heavy calculations, therefore I use some "caching" or optimisation. I only do the heavy calculation when the location has changed quite a bit or enough time passed.

I noticed that most of the time, even when I don't move the phone, my code for the heavy calculations is called again and again. It turned out that it's because the oldLocation is null (my app was killed by android) The app works, because it's being waken up by the LocationService, but it "forgets" the cached lastLocation (=null). So I added saveLastLocation() which indeed helps, but I'm trying to figure out the best place to call it from. At the beginning I called it from finalize(), but it was never called. It seems that android killed my app "too fast" for it to be called. Then I moved it to it's current place, right after the heavy calculation.

I wonder if there's a way, similar to what would be ideal if I was in an Activity where I would use onPause() to call saveLastLocation and onResume to call loadLastLocation.

I hold the instance of MyLocationListener as a static member of MyApplication (for caching purposes)

class MyLocationListener implements LocationListener {
    private static MyLocationListener instance;
    private Location lastLocation;

    public static MyLocationListener getInstance() {
        if (null == instance) {
            instance = new MyLocationListener();
        }
        return instance;
    }

    private MyLocationListener() {
        loadLastLocation();
    }

    @Override
    public synchronized void onLocationChanged(final Location location) {
        if (null == lastLocation || lastLocation.distanceTo(location) > MIN_LOCATION_CHANGE_DISTANCE
                    || getElapsedNanoSec(location) - getElapsedNanoSec(lastLocation) > MIN_LOCATION_CHANGE_NANOSEC) {
            // Just to see how many times we enter the heavy calculations
            Toast.makeText(MyApp.getContext(), "old: " + lastLocation + "new: " + location, Toast.LENGTH_LONG).show();

            // do the heavy calculations

            lastLocation = location;

            // Is there a better way to call saveLastLocation???
            saveLastLocation();
        }
    }

    private synchronized void saveLastLocation() {
        final SharedPreferences sharedPreferences = getSharedPreferences();
        SharedPreferences.Editor editor = sharedPreferences.edit();

        if (null != lastLocation) {
            editor.putString(PREF_LAST_LOCATION_PROVIDER, lastLocation.getProvider());
            editor.putLong(PREF_LAST_LOCATION_ELAPSED_REALTIME, getElapsedNanoSec(lastLocation));
            putDouble(editor, PREF_LAST_LOCATION_LATITUDE, lastLocation.getLatitude());
            putDouble(editor, PREF_LAST_LOCATION_LONGITUDE, lastLocation.getLongitude());
            editor.apply();
        }
    }

    private synchronized void loadLastLocation() {
        final SharedPreferences sharedPreferences = getSharedPreferences();
        final String provider = sharedPreferences.getString(PREF_LAST_LOCATION_PROVIDER, null);
        if (null != provider) {
            final long elapsed = sharedPreferences.getLong(PREF_LAST_LOCATION_ELAPSED_REALTIME, Long.MIN_VALUE);
            final double latitude = getDouble(sharedPreferences, PREF_LAST_LOCATION_LATITUDE, Double.MIN_VALUE);
            final double longitude = getDouble(sharedPreferences, PREF_LAST_LOCATION_LONGITUDE, Double.MIN_VALUE);
            if (null == lastLocation) {
                lastLocation = new Location(provider);
            } else {
                lastLocation.setProvider(provider);
            }
            setElapsedNanoSec(lastLocation, elapsed);
            lastLocation.setLatitude(latitude);
            lastLocation.setLongitude(longitude);
        }
    }

    private long getElapsedNanoSec(final Location location) {
        long elapsed;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            elapsed = location.getElapsedRealtimeNanos();
        } else {
            elapsed = location.getTime() * 1000;
        }
        return elapsed;
    }
}

Just to clarify: MyLocationListener is not called straight by google play services, I have another "real" LocationListener that is being called, and from it's onLocationChanged I call MyLocationListener.getInstance().onLocationChanged(location) [the reason I do this is that I use the results of the heavy calculations (by calling other functions not presented here) from many places in my code and I want it to cache as much as possible]


Solution

  • If you are using the new FusedLocationProviderApi (which replaces the LocationClient in Google Play Services 6.5), then you can set a smallest displacement as part of your LocationRequest which will ensure you will only get a callback if the location has changed more than your smallest displacement - this ensures that you will be able to do your heavy calculation each time you receive a callback.

    If you really also need to redo the calculation after a certain time, you can set an alarm with AlarmManager to act as a timeout mechanism on each location update received (and cancel a previously set alarm) which would fire your heavy calculation method if there hasn't been a location update, using FusedLocationApi.getLastLocation() as the current location (or using a cached value if you wish).

    Note, FusedLocationProviderApi also provides a requestLocationupdates() method that takes a PendingIntent - this is perfect for background services which don't want or need to maintain a constantly running listener, but want to trigger a short running handler (which an IntentService is perfect for) in response to an event. This ensures that your application will always receive an update even if the app is stopped by Android.