How to collect location updates for later processing outside of onLocationChanged

249 Views Asked by At

I want to collect location updates for a specific time duration and do something with the locations at the end. I currently keep track of the time passed in the method onLocationChanged.

public class LocationProvider implements
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener,
        LocationListener {
    private static final String TAG = LocationProvider.class.getSimpleName();

    private Context mContext;

    private GoogleApiClient mGoogleApiClient;
    private LocationRequest mLocationRequest;

    private long mStartTime;
    private long mDuration;

    List<Location> locations;

    public LocationProvider(Context context) {
        mContext = context;

        GoogleApiAvailability api = GoogleApiAvailability.getInstance();
        if (api.isGooglePlayServicesAvailable(mContext) == ConnectionResult.SUCCESS) {
            mGoogleApiClient = new GoogleApiClient.Builder(mContext)
                    .addConnectionCallbacks(this)
                    .addOnConnectionFailedListener(this)
                    .addApi(LocationServices.API)
                    .build();
        } else {
            Log.e(TAG, "Could not connect to Google Play services");
        }
    }

    @Override
    public void onConnected(Bundle bundle) {
        LocationServices.FusedLocationApi.requestLocationUpdates(
                mGoogleApiClient, mLocationRequest, this);
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        Log.e(TAG, "onConnectionFailed: " + connectionResult.toString());
    }

    @Override
    public void onConnectionSuspended(int i) {
        Log.e(TAG, "GoogleApiClient connection has been suspended: " + String.valueOf(i));
    }

    @Override
    public void onLocationChanged(Location location) {
        locations.add(location);

        if (System.currentTimeMillis() - mStartTime > mDuration) {
            stopTracking();
            // do stuff
        }
    }

    public void startTracking(long interval, long fastInterval, long duration) {
        mDuration = duration;
        locations = new ArrayList<>();

        mLocationRequest = LocationRequest.create()
                .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
                .setInterval(interval)
                .setFastestInterval(fastInterval);

        if (!mGoogleApiClient.isConnected() || !mGoogleApiClient.isConnecting()) {
            mGoogleApiClient.connect();
        }

        mStartTime = System.currentTimeMillis();
    }

    private void stopTracking() {
        if (mGoogleApiClient.isConnected()) {
            LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
            mGoogleApiClient.disconnect();
        }
    }
}

However, I want to divorce this logic, because I want to do different things with the locations depending on my needs. I reasoned that if I created a new thread for registering my location listener, I could wait on the main thread until the location collection completed to use the locations list.

public class LocationUpdates {
    private LocationProvider mLocationProvider;
    private Looper mLooper;

    public List<Location> gatherUpdates(final Context context,
                                        final long interval,
                                        final long fastInterval,
                                        final long duration)
            throws InterruptedException {

        long startTime = System.currentTimeMillis();

        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                mLooper = Looper.myLooper();
                mLocationProvider = new LocationProvider(context);
                mLocationProvider.startTracking(interval, fastInterval, duration);
                Looper.loop();
            }
        }.start();

        while (System.currentTimeMillis() - startTime < duration) {

        }

        mLooper.quit();
        mLooper.getThread().join();

        return mLocationProvider.locations;
    }
}

Instead, what I observed was (interval 3 seconds, duration 10 seconds):

  • the line mLocationProvider.startTracking(interval, fastInterval, duration); is reached
  • gatherUpdates returns after however long
  • onLocationChanged is called for the first time only now

So, even though the location listener is registered, something clearly blocks it from receiving updates. I can't figure out why my logic doesn't do what I expect it to do.

Is there a way, even without a thread, to collect a bunch of location updates and work with them outside of onLocationChanged only after the collection has finished?

1

There are 1 best solutions below

2
On

You could build your Location Updates request with an Intent to a BroadcastReceiver. Then remove the updates when the location object's time exceeds your duration.

int requestCode = 1;
Intent intent = new Intent(this, LocationReceiver.class); // Request updates to Receiver
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, requestCode, intent, PendingIntent.FLAG_CANCEL_CURRENT);
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, pendingIntent);

In your broadcast receiver:

@Override
public void onReceive(Context context, Intent intent) {

    if(LocationResult.hasResult(intent)){
        LocationResult locResult = LocationResult.extractResult(intent);
        Location location = locResult.getLocations().get(0);
        // Check the location's time and maybe stop location updates
    }
}