问题
I am currently working on an app that has to check the user's location every five minutes and send the coordinates to a server. I decided to go with the FusedLocation API in Google Play Services instead of the plain old LocationManager API, mainly because I noticed the LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY priority level, which claims to offer a 100-meter accuracy level with reasonable battery usage, which is EXACTLY what I need.
In my case, I have an Activity whose inheritance structure is:
public class MainActivity extends AppCompatActivity implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener, LocationListener
and which implements the relevant callbacks (onConnected, onConnectionFailed, onConnectionSuspended, onLocationChanged). I also get an instance of the GoogleApiClient, with this method, as suggested by the official documentation:
protected synchronized GoogleApiClient buildGoogleApiClient() {
return new GoogleApiClient.Builder(this).addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API).build();
In onConnected, I start the location updates by using
LocationServices.FusedLocationApi.requestLocationUpdates(mApiClient,
mLocationRequest, this);
... and capture changes in onLocationChanged().
However, I quickly found out that the location updates seem to stop after a while. Perhaps it's because this method is tied to the Activity lifecycle, I'm not sure. Anyway, I tried to get around this by creating an inner class which extends IntentService and starting it by an AlarmManager. So in onConnected, I ended up doing this:
AlarmManager alarmMan = (AlarmManager) this
.getSystemService(Context.ALARM_SERVICE);
Intent updateIntent = new Intent(this, LocUpService.class);
PendingIntent pIntent = PendingIntent.getService(this, 0, updateIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
alarmMan.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, 0,
1000 * 60 * 5, pIntent);
The LocUpService class looks like this:
public static class LocUpService extends IntentService {
public LocUpService() {
super("LocUpService");
}
@Override
protected void onHandleIntent(Intent intent) {
Coords coords = LocationUpdater.getLastKnownLocation(mApiClient);
}
}
LocationUpdater is another class, which contains the static method getLastKnownLocation, which is this:
public static Coords getLastKnownLocation(GoogleApiClient apiClient) {
Coords coords = new Coords();
Location location = LocationServices.FusedLocationApi
.getLastLocation(apiClient);
if (location != null) {
coords.setLatitude(location.getLatitude());
coords.setLongitude(location.getLongitude());
Log.e("lat ", location.getLatitude() + " degrees");
Log.e("lon ", location.getLongitude() + " degrees");
}
return coords;
}
But surprise!! I get "IllegalArgumentException: GoogleApiClient parameter is required", when I clearly pass the reference to the static method, which again I guess must have something to do with the GoogleApiClient instance being implicated with the Activity's lifecycle and something going wrong with passing the instance into the IntentService.
So I'm thinking: how do I get regular location updates every five minutes without going crazy? Do I extend a Service, implement all interface callbacks on that component, build the GoogleApiClient instance in there and keep it running in the background? Do I have an AlarmManager start a service that extends IntentService every five minutes to do the work, again having all relevant callbacks and GoogleApiClient constructed in the IntentService? Do I keep doing what I'm doing right now but construct the GoogleApiClient as a singleton, expecting that it'll make a difference? How would you do it?
Thanks and sorry for this being so long-winded.
回答1:
I am currently working on an app that has to check the user's location every five minutes and send the coordinates to a server. I decided to go with the FusedLocation API in Google Play Services instead of the plain old LocationManager API
Our app has exactly that same requirement, I implemented that a couple of days ago and here is how I did it.
In the launch activity or wherever you want to start, configure a LocationTracker to run every 5 minutes, using an AlarmManager.
private void startLocationTracker() {
// Configure the LocationTracker's broadcast receiver to run every 5 minutes.
Intent intent = new Intent(this, LocationTracker.class);
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, Calendar.getInstance().getTimeInMillis(),
LocationProvider.FIVE_MINUTES, pendingIntent);
}
LocationTracker.java
public class LocationTracker extends BroadcastReceiver {
private PowerManager.WakeLock wakeLock;
@Override
public void onReceive(Context context, Intent intent) {
PowerManager pow = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
wakeLock = pow.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "");
wakeLock.acquire();
Location currentLocation = LocationProvider.getInstance().getCurrentLocation();
// Send new location to backend. // this will be different for you
UserService.registerLocation(context, new Handlers.OnRegisterLocationRequestCompleteHandler() {
@Override
public void onSuccess() {
Log.d("success", "UserService.RegisterLocation() succeeded");
wakeLock.release();
}
@Override
public void onFailure(int statusCode, String errorMessage) {
Log.d("error", "UserService.RegisterLocation() failed");
Log.d("error", errorMessage);
wakeLock.release();
}
}, currentLocation);
}
}
LocationProvider.java
public class LocationProvider {
private static LocationProvider instance = null;
private static Context context;
public static final int ONE_MINUTE = 1000 * 60;
public static final int FIVE_MINUTES = ONE_MINUTE * 5;
private static Location currentLocation;
private LocationProvider() {
}
public static LocationProvider getInstance() {
if (instance == null) {
instance = new LocationProvider();
}
return instance;
}
public void configureIfNeeded(Context ctx) {
if (context == null) {
context = ctx;
configureLocationUpdates();
}
}
private void configureLocationUpdates() {
final LocationRequest locationRequest = createLocationRequest();
final GoogleApiClient googleApiClient = new GoogleApiClient.Builder(context)
.addApi(LocationServices.API)
.build();
googleApiClient.registerConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(Bundle bundle) {
startLocationUpdates(googleApiClient, locationRequest);
}
@Override
public void onConnectionSuspended(int i) {
}
});
googleApiClient.registerConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
}
});
googleApiClient.connect();
}
private static LocationRequest createLocationRequest() {
LocationRequest locationRequest = new LocationRequest();
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationRequest.setInterval(FIVE_MINUTES);
return locationRequest;
}
private static void startLocationUpdates(GoogleApiClient client, LocationRequest request) {
LocationServices.FusedLocationApi.requestLocationUpdates(client, request, new com.google.android.gms.location.LocationListener() {
@Override
public void onLocationChanged(Location location) {
currentLocation = location;
}
});
}
public Location getCurrentLocation() {
return currentLocation;
}
}
I first create an instance of the LocationProvider in a class that extends application, creating the instance when the app is launched:
MyApp.java
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
LocationProvider locationProvider = LocationProvider.getInstance();
locationProvider.configureIfNeeded(this);
}
}
The LocationProvider is instantiated and configured for location updates exactly once, because it is a singleton. Every 5 minutes it will update its currentLocation
value, which we can retrieve from anywhere we need with
Location loc = LocationProvider.getInstance().getCurrentLocation();
Running a background service of any kind is not required. The AlarmManager will broadcast to LocationTracker.onReceive() every 5 minutes and the partial wakelock will ensure that the code will finish running even if the device is standby. This is also energy efficient.
Note that you need the following permissions
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- For keeping the LocationTracker alive while it is doing networking -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
and don't forget to register the receiver:
<receiver android:name=".LocationTracker" />
回答2:
About your first method where u are using the Activity to request for location updates, they should not stop unless you disconnect the Location Client in the onPause() method of the activity. So as long as your activity is in the background/foreground you should continue to receive location updates. But if the activity is destroyed then of course you won't get the updates.
Check if you are disconnecting the location Client in your activity lifecycle.
来源:https://stackoverflow.com/questions/32979633/how-to-receive-location-updates-every-5-minutes-using-the-fusedlocation-api