I am trying to create an Android application that continuously logs device location data in realtime while the device screen is off. My code works correctly with Android 6.0 and earlier but it seems that Android 7.0+ breaks my app.
I have implemented an Android foreground service that uses a wakelock and subscribes to the Google FusedLocation API. Once the screen is turned off the onLocationChanged
callback is never triggered.
Has anyone seen a solution to this problem? I have tried disabling Battery Optimization through the Android Device Settings for my app as well as for the Google Services Framework and the Fused Location.
public class ForegroundLocationService extends Service implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
LocationListener {
private static final String TAG = ForegroundLocationService.class.getSimpleName();
// the notification id for the foreground notification
public static final int GPS_NOTIFICATION = 1;
// the interval in seconds that gps updates are requested
private static final int UPDATE_INTERVAL_IN_SECONDS = 15;
// is this service currently running in the foreground?
private boolean isForeground = false;
// the google api client
private GoogleApiClient googleApiClient;
// the wakelock used to keep the app alive while the screen is off
private PowerManager.WakeLock wakeLock;
@Override
public void onCreate() {
super.onCreate();
// create google api client
googleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
// get a wakelock from the power manager
final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!isForeground) {
Log.v(TAG, "Starting the " + this.getClass().getSimpleName());
startForeground(ForegroundLocationService.GPS_NOTIFICATION,
notifyUserThatLocationServiceStarted());
isForeground = true;
// connect to google api client
googleApiClient.connect();
// acquire wakelock
wakeLock.acquire();
}
return START_REDELIVER_INTENT;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
Log.v(TAG, "Stopping the " + this.getClass().getSimpleName());
stopForeground(true);
isForeground = false;
// disconnect from google api client
googleApiClient.disconnect();
// release wakelock if it is held
if (null != wakeLock && wakeLock.isHeld()) {
wakeLock.release();
}
super.onDestroy();
}
private LocationRequest getLocationRequest() {
LocationRequest locationRequest = LocationRequest.create();
// we always want the highest accuracy
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
// we want to make sure that we get an updated location at the specified interval
locationRequest.setInterval(TimeUnit.SECONDS.toMillis(0));
// this sets the fastest interval that the app can receive updates from other apps accessing
// the location service. for example, if Google Maps is running in the background
// we can update our location from what it sees every five seconds
locationRequest.setFastestInterval(TimeUnit.SECONDS.toMillis(0));
locationRequest.setMaxWaitTime(TimeUnit.SECONDS.toMillis(UPDATE_INTERVAL_IN_SECONDS));
return locationRequest;
}
private Notification notifyUserThatLocationServiceStarted() {
// pop up a notification that the location service is running
Intent notificationIntent = new Intent(this, NotificationActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
final Notification.Builder builder = new Notification.Builder(this)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle(getString(R.string.foreground_location_service))
.setContentText(getString(R.string.service_is_running))
.setContentIntent(pendingIntent)
.setWhen(System.currentTimeMillis());
final Notification notification;
if (Build.VERSION.SDK_INT < 16) {
notification = builder.getNotification();
} else {
notification = builder.build();
}
return notification;
}
@Override
public void onConnected(@Nullable Bundle bundle) {
try {
// request location updates from the fused location provider
LocationServices.FusedLocationApi.requestLocationUpdates(
googleApiClient, getLocationRequest(), this);
} catch (SecurityException securityException) {
Log.e(TAG, "Exception while requesting location updates", securityException);
}
}
@Override
public void onConnectionSuspended(int i) {
Log.i(TAG, "Google API Client suspended.");
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
Log.e(TAG, "Failed to connect to Google API Client.");
}
@Override
public void onLocationChanged(Location location) {
Log.e(TAG, "onLocationChanged: " + location.toString());
}
}
I have included a complete working sample of the code that I am using to test here: https://github.com/joshuajwitter/ForegroundLocationService
FitBit seems to have the same issue, here is the discussion
Here are the resources that I have already looked at:
Code Samples for Android O
Keep app running in background on Android all the time
Android, get the location when the screen is off
Background Location Limits
Inconsistent location updates in foreground service when in deep sleep (doze)
Foreground service not receiving location updates in Doze mode in Android O
EDIT 2018.01.30: I have also tried running the ForegroundLocationService in its own process:
<service
android:name="foregroundlocation.stackoverflowexample.com.foregroundlocation.ForegroundLocationService"
android:enabled="true"
android:stopWithTask="false"
android:process=":ForegroundLocationService"
android:exported="false" >
</service>
as well as updating my code to use the FusedLocationProviderClient
, still no luck.
To set up a notification so it can be updated, issue it with a notification ID by calling NotificationManager. notify() . To update this notification after you've issued it, update or create a NotificationCompat.
The Android system stops a service only when memory is low and it must recover system resources for the activity that has user focus. If the service is bound to an activity that has user focus, it's less likely to be killed; if the service is declared to run in the foreground, it's rarely killed.
Foreground services show a status bar notification, so that users are actively aware that your app is performing a task in the foreground and is consuming system resources. Devices that run Android 12 (API level 31) or higher provide a streamlined experience for short-running foreground services.
A started Service or Activity which user can see and interact is said to be in a foreground state, and the system considers it to be something the user is actively aware of and thus not a candidate for killing when low on memory. The flappy bird activity is foreground because you can see it and interact with it.
I've spent the past week trying to get Location Services in Foreground. Finally got it working by including this in the manifest.
android:foregroundServiceType="location"
I hope this can help someone in the future!
I had the same issue and I have found a working solution. In order to listen to location updates, you should NOT call this method:
public Task<Void> requestLocationUpdates (LocationRequest request, LocationCallback callback, Looper looper)
You should use a pending intent instead of a callback, i.e. you should call the method:
public Task<Void> requestLocationUpdates (LocationRequest request, PendingIntent callbackIntent)
Checkout the link to read more: https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderClient.html#requestLocationUpdates(com.google.android.gms.location.LocationRequest, android.app.PendingIntent)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With