I have created a service which finds and then stores the user's coordinates in an SQLite database.
public class GPS_Service extends Service {
DatabaseHelper myDb;
private LocationListener locationListener;
private LocationManager locationManager;
private String latitude, longitude;
@Override
public void onCreate() {
super.onCreate();
myDb = new DatabaseHelper(this);
}
@SuppressLint("MissingPermission")
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Service")
.setContentText("Coordinates Location Running")
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
Log.d("myTag", "Hello");
latitude = String.valueOf(location.getLatitude());
longitude = String.valueOf(location.getLongitude());
insertCoordinates(latitude, longitude);
Intent i = new Intent("location_update");
i.putExtra("latitude", latitude);
i.putExtra("longitude",longitude);
sendBroadcast(i);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
Intent i = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
}
};
locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10000, 0, locationListener);
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
if(locationManager != null)
locationManager.removeUpdates(locationListener);
}
private void insertCoordinates(String latitude, String longitude) {
boolean inserted = myDb.insertData(latitude, longitude); //Insert coordinates
//Check if insertion is completed
if(inserted)
Toast.makeText(GPS_Service.this, "Coordinates Inserted", Toast.LENGTH_SHORT).show();
else
Toast.makeText(GPS_Service.this, "Coordinates Not Inserted", Toast.LENGTH_SHORT).show();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
I can either start or stop the service from the MainActivity like this
private void enable_buttons() {
buttonStartService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent serviceIntent = new Intent(getApplicationContext(), GPS_Service.class);
//Checks if the SDK version is higher than 26 to act accordingly
ContextCompat.startForegroundService(MainActivity.this, serviceIntent);
}
});
buttonStopService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent serviceIntent = new Intent(MainActivity.this, GPS_Service.class);
stopService(serviceIntent);
}
});
}
The problem is that when I start this service, if I either completely close the app or leave it in the background, the locationListener will work for 30 seconds and then it will stop. If I reopen the app, the service continues to work from where it stopped. Also I checked in the developer options if the service is running, and it indeed is even though the locationListener doesn't output the expected results. Any ideas?
The service must stop itself by calling stopSelf(), or another component can stop it by calling stopService(). Once requested to stop with stopSelf() or stopService(), the system destroys the service as soon as possible.
A Background Service is a service that runs only when the app is running so it'll get terminated when the app is terminated. A Foreground Service is a service that stays alive even when the app is terminated. And a Bound Service is a service that runs only if the component it is bound to is still active.
A foreground service performs some operation that is noticeable to the user. For example, an audio app would use a foreground service to play an audio track. Foreground services must display a Notification. Foreground services continue running even when the user isn't interacting with the app.
TL;DR:
Add android:foregroundServiceType="location"
to your Service's
manifest entry.
EXPLANATION
This new behavior, for Android 10, is exactly as you've described: Even though you may be using a foreground service, 30 seconds after your app leaves the screen -- location updates cease.
You might've noticed that Android 10 devices present two new choices to the user when granting location permissions (for legacy (API < 29) apps, or apps that declare the ACCESS_BACKGROUND_LOCATION
permission):
"Allow only while using this app" effectively means, "Allow while the app is visible onscreen". That's because the user now has the option of selectively removing location access -- even to a foreground service -- based on that criteria. Users can change this setting at any time, even if your app is running.
The Android docs explain that the solution, android:foregroundServiceType="location"
, was intended for your precise use case: "Google Maps"-like apps, which have a foreground service, but are expected to continue processing location data if the user switches to another app. The technique is called "continuing a user-initiated action", and it allows you to get location updates even after your app is placed in the "background".
(The docs seem to be expanding the definition of the term "background", here. In the past, if you had a foreground service, your app was considered "in the foreground" -- at least for the purposes of task priority, Doze, and so forth. Now it appears that an app is considered "in the background", with respect to location access, if it hasn't been onscreen in the last 30 seconds.)
I am not sure what UI changes (like in the Google Play store) take place when you set a particular foregroundServiceType
. Regardless, it seems to me that users are unlikely to object.
OTHER SOLUTIONS FOR ANDROID 10 DEVICES
Alternatively, you could've declared a targetSdkVersion
of 28 or less, which will let your app function in a location "compatibility mode".
You also have the option of gaining the ACCESS_BACKGROUND_LOCATION
permission, but the docs caution against this:
If your app doesn't require location access while running in the background, it's highly recommended that you not request
ACCESS_BACKGROUND_LOCATION
...
This approach isn't required, for your use case, because your Activity
is used to start your Service
; you can be guaranteed that your app has been onscreen at least once before the Service
starts getting background location updates. (At least, I assume that that is how the OS determines the start of a "user-initiated action". Presumably, the foregroundServiceType
approach won't work if you're starting the Service
from a JobScheduler
, or a BroadcastReceiver
, or something.)
PS: Hang on to that WakeLock
code. You're going to need to keep the device awake, if you want to keep getting updates at a steady 10-second pace.
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