Problem
I have this background service, which runs continuously even when the application closed (Paused or Stopped).
The service is to retrieve the user location and post it to server.
public class LocationUpdaterService extends Service
{
public static final int TWO_MINUTES = 120000; // 120 seconds
public static Boolean isRunning = false;
public LocationManager mLocationManager;
public LocationUpdaterListener mLocationListener;
public Location previousBestLocation = null;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
mLocationListener = new LocationUpdaterListener();
super.onCreate();
}
Handler mHandler = new Handler();
Runnable mHandlerTask = new Runnable(){
@Override
public void run() {
if (!isRunning) {
startListening();
}
mHandler.postDelayed(mHandlerTask, TWO_MINUTES);
}
};
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mHandlerTask.run();
return START_STICKY;
}
@Override
public void onDestroy() {
stopListening();
mHandler.removeCallbacks(mHandlerTask);
super.onDestroy();
}
private void startListening() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
if (mLocationManager.getAllProviders().contains(LocationManager.NETWORK_PROVIDER))
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, mLocationListener);
if (mLocationManager.getAllProviders().contains(LocationManager.GPS_PROVIDER))
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mLocationListener);
}
isRunning = true;
}
private void stopListening() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
mLocationManager.removeUpdates(mLocationListener);
}
isRunning = false;
}
public class LocationUpdaterListener implements LocationListener
{
@Override
public void onLocationChanged(Location location) {
if (isBetterLocation(location, previousBestLocation)) {
previousBestLocation = location;
try {
// Script to post location data to server..
}
catch (Exception e) {
e.printStackTrace();
}
finally {
stopListening();
}
}
}
@Override
public void onProviderDisabled(String provider) {
stopListening();
}
@Override
public void onProviderEnabled(String provider) { }
@Override
public void onStatusChanged(String provider, int status, Bundle extras) { }
}
protected boolean isBetterLocation(Location location, Location currentBestLocation) {
if (currentBestLocation == null) {
// A new location is always better than no location
return true;
}
// Check whether the new location fix is newer or older
long timeDelta = location.getTime() - currentBestLocation.getTime();
boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
boolean isNewer = timeDelta > 0;
// If it's been more than two minutes since the current location, use the new location
// because the user has likely moved
if (isSignificantlyNewer) {
return true;
// If the new location is more than two minutes older, it must be worse
} else if (isSignificantlyOlder) {
return false;
}
// Check whether the new location fix is more or less accurate
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
boolean isLessAccurate = accuracyDelta > 0;
boolean isMoreAccurate = accuracyDelta < 0;
boolean isSignificantlyLessAccurate = accuracyDelta > 200;
// Check if the old and new location are from the same provider
boolean isFromSameProvider = isSameProvider(location.getProvider(), currentBestLocation.getProvider());
// Determine location quality using a combination of timeliness and accuracy
if (isMoreAccurate) {
return true;
} else if (isNewer && !isLessAccurate) {
return true;
} else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
return true;
}
return false;
}
/** Checks whether two providers are the same */
private boolean isSameProvider(String provider1, String provider2) {
if (provider1 == null) {
return provider2 == null;
}
return provider1.equals(provider2);
}
}
So, I figured to use a Handler
to listen to LocationListener
once every 2 minutes because I am assuming this is saving more battery usage.
But, I don’t know if there’s still any way I can improve my code so that:
- It’s saving much battery power
- Memory friendly
- The code is easier to read and maintain
Solution
To save power you can use/implement a function to detect when the user is stationary. You can for example define a user in stationary state when the location has not changed in more than 2 minutes (set this parameter so it does not fire too often, as when the user stops at a red light). When stationary states are detected, stop listen for location updates from the GPS and start listen for SIGNIFICANT_MOTION_SENSOR to detect active states. Be aware that not all models have this software sensor so you have to support situations when this is the case.
One good approach is to request location updates in batch like this.
so you will be less frequently requesting for location updates:
LocationRequest request = new LocationRequest(); request.setInterval(10 * 60 * 1000); request.setMaxWaitTime(60 * 60 * 1000);
In this case, location is computed roughly every ten minutes, and
approximately six location data points are delivered in a batch
approximately every hour. While you still get location updates every
ten minutes or so, you conserve battery because your device is woken
up only every hour or so.
reference
What about location listener running in the background is’nt like two services running in the background .And you have minimum time update interval 0 .that will consume lot of battery