Android - Best way to implement LocationListener across multiple activities
Asked Answered
V

4

41

I have been stuck on this problem for quite some time. I am working on an app that uses location quite extensively in several different Activities. Every example I have found uses a separate LocationListener in every Activity. This is not feasible in my situation.

I am wondering what is the most efficient way to keep track of the user's location across several activities. Right now I have created a service that implements LocationListener and uses a broadcast to update static lat and long fields in an Activity base class. This means that the service is always running, which isn't ideal. But if I shut down the service and restart it only when I need it, it takes time to get a good location. I need the location data in the Activity's onCreate(). It's the same if I try to implement it in the activity base class. If I constantly register the listener in onResume/onCreate and unregister it in onPause(), it takes too much time to start receiving updates. I also tried to create a service that I could bind to, so it only starts when I need a location. But I have the same problem, it takes too long to bind to the service and start getting updates.

The service that I am using now works but from everything I've read, I shouldn't be using a constantly running service for trivial things like this. But the whole point of the app is to provide relevant data based on the user's current location. So I have a service that just runs in the background and provides updates periodically. The one major problem that has caused me to reexamine the design at this point is that I recently discovered that onProviderEnabled() doesn't get called if the user starts the app without GPS turned on and then subsequently enables it. In this scenario the app has no way of recognizing that GPS was enabled so it can start listening for updates.

I thought I understood LocationManager and LocationListener from looking at the examples but I can't seem to apply it to this situation where I need location data in multiple Activities. Any help or advice would be greatly appreciated.

Vireo answered 25/4, 2011 at 21:41 Comment(0)
R
42

The way that I would typically implement this requirement is using a bound Service implementation, like the one in the Local Service Sample in the SDK Documentation. Obviously you're familiar with the advantage of the Service allowing you to create all the location code only once.

Accessing the Service through Bindings allows the Service to start and stop itself so it isn't running when your application isn't in the foreground (it will die as soon as no more Activities are bound). The key, IMO, to making this work well is to BIND the service in onStart() and UNBIND in onStop(), because those two calls overlap as you move from one Activity to another (Second Activity Starts before the First one Stops). This keeps the Service from dying when moving around inside the app, and only lets the service die when the entire application (or at least any part interested in location) leaves the foreground.

With Bindings, you don't have to pass the Location data in a Broadcast, because the Activity can call methods directly on the Service to get the latest location. However, a Broadcast would still be advantageous as a method of indicating WHEN a new update is available...but this would just become a notifier to the listening Activity to call the getLocation() method on the Service.

My $0.02. Hope that Helps!

Repetition answered 25/4, 2011 at 22:17 Comment(4)
I have been down this road before but I didn't know that onStart and onStop overlapped like that. This is appealing to me and I will try this out and let you know what I come up with tomorrow. Thanks!Vireo
Just wondering, how would this work with turning off the screen? Would it be wise to BIND the service on onResume() and UNBIND on onPause() to turn it on/off when the screen turns on/off as well as working when activities switch?Adulthood
@Grantland Valid point, since the Activity is still foreground the Service would live. If the service needs to throttle itself back in these instances, I would keep the binding the same, but have it register a BroadcastReceiver for Intent.ACTION_SCREEN_ON and Intent.ACTION_SCREEN_OFF to turn off heavy components like GPS. Moving the binding mechanism into onPause()/onResume() causes the service to possibly stop/restart often throughout use of the application, since those callbacks never overlap, letting the service fall into an unbound state frequently.Repetition
Great solution. I think that you need to bind with BIND_AUTO_CREATE like bindService(intent, mServiceConn, BIND_AUTO_CREATE ), can anyone confirm?Blackpool
A
18

I got the same problem and I tried to solve it with the good answer of Devunwired, but I had some troubles. I couldn't find a way to stop the service and when I finished my app the GPS-module was still running. So i found another way:

I wrote a GPS.java class:

public class GPS {

    private IGPSActivity main;

    // Helper for GPS-Position
    private LocationListener mlocListener;
    private LocationManager mlocManager;

    private boolean isRunning;

    public GPS(IGPSActivity main) {
        this.main = main;

        // GPS Position
        mlocManager = (LocationManager) ((Activity) this.main).getSystemService(Context.LOCATION_SERVICE);
        mlocListener = new MyLocationListener();
        mlocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mlocListener);
        // GPS Position END
        this.isRunning = true;
    }

    public void stopGPS() {
        if(isRunning) {
            mlocManager.removeUpdates(mlocListener);
            this.isRunning = false;
        }
    }

    public void resumeGPS() {
        mlocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mlocListener);
        this.isRunning = true;
    }

    public boolean isRunning() {
        return this.isRunning;
    }

    public class MyLocationListener implements LocationListener {

        private final String TAG = MyLocationListener.class.getSimpleName();

        @Override
        public void onLocationChanged(Location loc) {
            GPS.this.main.locationChanged(loc.getLongitude(), loc.getLatitude());
        }

        @Override
        public void onProviderDisabled(String provider) {
            GPS.this.main.displayGPSSettingsDialog();
        }

        @Override
        public void onProviderEnabled(String provider) {

        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {

        }

    }

}

This class is used in every Activity which needs the GPS coordinates. Every Activity has to implement the following Interface (needed for the communication):

public interface IGPSActivity {
    public void locationChanged(double longitude, double latitude);
    public void displayGPSSettingsDialog();
}

Now my main Activity looks like that:

public class MainActivity extends Activity implements IGPSActivity {

    private GPS gps;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        gps = new GPS(this);
    }


    @Override
    protected void onResume() { 
        if(!gps.isRunning()) gps.resumeGPS();   
        super.onResume();
    }

    @Override
    protected void onStop() {
        gps.stopGPS();
        super.onStop();
    }


    public void locationChanged(double longitude, double latitude) {
        Log.d(TAG, "Main-Longitude: " + longitude);
        Log.d(TAG, "Main-Latitude: " + latitude);
    }


    @Override
    public void displayGPSSettingsDialog() {
                Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                startActivity(intent);
    }
}

and a second one like that:

public class TEST4GPS extends Activity implements IGPSActivity{

    private GPS gps;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.gps = new GPS(this);
    }

    @Override
    public void locationChanged(double longitude, double latitude) {
        Log.d("TEST", "Test-Longitude: " + longitude);
        Log.d("TEST", "Test-Latitude: " + latitude);

    }

    @Override
    protected void onResume() { 
        if(!gps.isRunning()) gps.resumeGPS();   
        super. onResume();
    }

    @Override
    protected void onStop() {
        gps.stopGPS();
        super.onStop();
    }

    @Override
    public void displayGPSSettingsDialog() {
                Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                startActivity(intent);

    }
}

It's not as beautiful as the solution of Devunwired, but it works for me. cya

Afoot answered 20/4, 2012 at 17:45 Comment(4)
Loved your solution! I made a few improvements and worked fine! Thank you.Entertainer
@Spipau, I like your answer here. I tried it, but it's not working for me. Any chance you can take a look at my code and see if you can figure out what I did wrong? #29772217Abran
@Abran Sorry, but I would have to recreate the whole thing. My answer is now 3 years old and if I would have to implement a global GPS signal in my app again it would look completely different. Put the tracking code in your Application and use the LocalBroadcastManager to send the position to the Activities/Fragments of your choice. developer.android.com/reference/android/support/v4/content/…Afoot
It solved my problem of getting location in multiple activities. Though I had to switch from gms location to android location.Disagreeable
P
0

You could have your service writing your lat & long coords to an sqlite db on change that way you dont have the service binding overhead and your coords will be accessible across all your activities.

Perhaps in your main activity you can check the GPS status and if it is turned off you can prompt the user to enable it. Once GPS is enabled start your service.

Ponzo answered 25/4, 2011 at 21:52 Comment(0)
T
-1

Here is a very simple and straightforward way to do it:

In your main activity:

private boolean mFinish;

@Override
public void onBackPressed() {
    mFinish = true;
}


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mFinish = false;
    ...
}

@Override
public void onPause(){
    super.onPause();
    if (mFinish) {
        // remove updates here
        mLocationManager.removeUpdates(mLocationListener);
    }
}

This will only remove the updates listener if you press Back in your main activity, but otherwise stay on.

It's not the best way to do it, but it's a simple copy-paste solution.

Make sure you don't call location listener if it's already running (for example on screen rotate), otherwise you'll end up with multiple listeners running in the background.

Toilette answered 18/1, 2016 at 21:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.