I have read a lot of other similar questions, and have checked out the Android documentation on Background Execution Limits (https://developer.android.com/about/versions/oreo/background#services), and still could not find a solution. I have tried most of the phone's settings as well.
The issue: I have an app, that is used to collect location data in the background. I am using a Service to achieve this. At the moment, it collects location data every 5 seconds. It works perfectly fine on my Nexus 5 (API 23); however, it only works on my Nexus 5X (API 27), when the application is in the foreground. As soon as it goes in the background it stops. So this has nothing to do with long running tasks in the background, as soon as I navigate away from the app, the service stops immediately.
Here is my service class code:
import android.app.Service;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import app.khash.climbcollector.DataBase.DataContract.DataEntry;
public class GPSService extends Service implements
GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,
com.google.android.gms.location.LocationListener {
private LocationRequest mLocationRequest;
private GoogleApiClient mGoogleApiClient;
private static final String TAG = GPSService.class.getSimpleName();
@Override
public void onCreate() {
super.onCreate();
buildGoogleApiClient();
}//onCreate
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!mGoogleApiClient.isConnected())
mGoogleApiClient.connect();
return START_STICKY;
}//onStartCommand
@Override
public void onConnected(Bundle bundle) {
startLocationUpdate();
}//onConnected
@Override
public void onConnectionSuspended(int i) {
Log.i(TAG, "onConnectionSuspended " + i);
}//onConnectionSuspended
@Override
public void onLocationChanged(Location location) {
//insert location to the database passing in the location object
insertDataToDb(location);
}//onLocationChanged
//method for adding the location data to db
private void insertDataToDb(Location location) {
final DateFormat dateFormat = new SimpleDateFormat("MM.dd.yyyy 'at' HH:mm:ss z");
//Current date and time using the format declared at the beginning
final String currentDateTime = dateFormat.format(Calendar.getInstance().getTime());
double lat = location.getLatitude();
double lng = location.getLongitude();
double alt = location.getAltitude();
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
String key = getString(R.string.route_name_intent_extra);
String routeName = sharedPref.getString(key, "default");
// Create a new map of values,
ContentValues values = new ContentValues();
values.put(DataEntry.COLUMN_DATA_LATITUDE, lat);
values.put(DataEntry.COLUMN_DATA_LONGITUDE, lng);
values.put(DataEntry.COLUMN_DATA_ALTITUDE, alt);
values.put(DataEntry.COLUMN_DATA_DATE, currentDateTime);
values.put(DataEntry.COLUMN_DATA_ROUTE_NAME, routeName);
// Insert a new location into the provider, returning the content URI for the new location.
Uri newUri = getContentResolver().insert(DataEntry.CONTENT_URI, values);
// Show a toast message depending on whether or not the insertion was successful
if (newUri == null) {
// If the new content URI is null, then there was an error with insertion.
Log.v(TAG, "error saving data");
} else {
//since the insert method return the Uri of the row created, we can extract the ID of
//the new row using the parseID method with our newUri as an input. This method gets the
//last segment of the Uri, which is our new ID in this case and we store it in an object
// And add it to the confirmation method.
String id = String.valueOf(ContentUris.parseId(newUri));
// Otherwise, the insertion was successful and we can log
Log.v(TAG, "Successfully added: " + id);
}
}//insertDataToDb
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.i(TAG, "onConnectionFailed ");
}//onConnectionFailed
private void initLocationRequest() {
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(5000);
mLocationRequest.setFastestInterval(2000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}//initLocationRequest
private void startLocationUpdate() {
initLocationRequest();
//check for location permission
if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
return;
}//check permission
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
}//startLocationUpdate
private void stopLocationUpdate() {
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
}
protected synchronized void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addOnConnectionFailedListener(this)
.addConnectionCallbacks(this)
.addApi(LocationServices.API)
.build();
}//buildGoogleApiClient
@Override
public void onDestroy() {
super.onDestroy();
mGoogleApiClient.disconnect();
}//onDestroy
}//GPSService
I call the service from my main activity using this:
Intent serviceStartIntent = new Intent(this, GPSService.class);
startService(serviceStartIntent);
And stop it using this code:
Intent serviceStopIntent = new Intent(this, GPSService.class);
stopService(serviceStopIntent);
Here is the code I have tried using the JobScheduler.
public class GPSJobService extends JobService implements
GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,
com.google.android.gms.location.LocationListener {
private String TAG = GPSJobService.class.getSimpleName();
private LocationRequest mLocationRequest;
private GoogleApiClient mGoogleApiClient;
private JobParameters mParam;
@Override
public void onCreate() {
Log.v(TAG, "onCreate called");
super.onCreate();
buildGoogleApiClient();
}//onCreate
@Override
public void onDestroy() {
Log.v(TAG, "onDestroy called");
super.onDestroy();
mGoogleApiClient.disconnect();
}//onDestroy
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.v(TAG, "onStartCommand called");
if (!mGoogleApiClient.isConnected())
mGoogleApiClient.connect();
return START_STICKY;
}//onStartCommand
@Override
public boolean onStartJob(JobParameters params) {
Log.v(TAG, "onStartJob called");
if (!mGoogleApiClient.isConnected())
mGoogleApiClient.connect();
mParam = params;
return true;
}//onStartJob
@Override
public void onLocationChanged(Location location) {
//insert location to the database passing in the location object
insertDataToDb(location);
}//onLocationChanged
private void initLocationRequest() {
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(5000);
mLocationRequest.setFastestInterval(2000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}//initLocationRequest
private void startLocationUpdate() {
initLocationRequest();
//check for location permission
if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
return;
}//check permission
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
}//startLocationUpdate
//method for adding the location data to db
private void insertDataToDb(Location location) {
final DateFormat dateFormat = new SimpleDateFormat("MM.dd.yyyy 'at' HH:mm:ss z");
//Current date and time using the format declared at the beginning
final String currentDateTime = dateFormat.format(Calendar.getInstance().getTime());
double lat = location.getLatitude();
double lng = location.getLongitude();
double alt = location.getAltitude();
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
String key = getString(R.string.route_name_intent_extra);
String routeName = sharedPref.getString(key, "default");
// Create a new map of values,
ContentValues values = new ContentValues();
values.put(DataEntry.COLUMN_DATA_LATITUDE, lat);
values.put(DataEntry.COLUMN_DATA_LONGITUDE, lng);
values.put(DataEntry.COLUMN_DATA_ALTITUDE, alt);
values.put(DataEntry.COLUMN_DATA_DATE, currentDateTime);
values.put(DataEntry.COLUMN_DATA_ROUTE_NAME, routeName);
// Insert a new location into the provider, returning the content URI for the new location.
Uri newUri = getContentResolver().insert(DataEntry.CONTENT_URI, values);
// Show a toast message depending on whether or not the insertion was successful
if (newUri == null) {
// If the new content URI is null, then there was an error with insertion.
Log.v(TAG, "error saving data");
} else {
//since the insert method return the Uri of the row created, we can extract the ID of
//the new row using the parseID method with our newUri as an input. This method gets the
//last segment of the Uri, which is our new ID in this case and we store it in an object
// And add it to the confirmation method.
String id = String.valueOf(ContentUris.parseId(newUri));
// Otherwise, the insertion was successful and we can log
Log.v(TAG, "Successfully added: " + id);
//finish the job
jobFinished(mParam, true);
}
}//insertDataToDb
@Override
public boolean onStopJob(JobParameters params) {
Log.v(TAG, "onStopJob called");
return false;
}//onStopJob
@Override
public void onConnected(@Nullable Bundle bundle) {
startLocationUpdate();
}//onConnected
@Override
public void onConnectionSuspended(int i) {
Log.v(TAG, "onConnectionSuspended called");
}//onConnectionSuspended
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
Log.v(TAG, "onConnectionFailed called");
}//onConnectionFailed
protected synchronized void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addOnConnectionFailedListener(this)
.addConnectionCallbacks(this)
.addApi(LocationServices.API)
.build();
}//buildGoogleApiClient
}//GPSJobService
I am calling it in my main activity like this:
case R.id.bttn_job_start:
ComponentName serviceComponent = new ComponentName(this, GPSJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(mJobId, serviceComponent);
builder.setMinimumLatency(5000);
builder.setBackoffCriteria(5000, JobInfo.BACKOFF_POLICY_LINEAR);
//repeat every 5 seconds
// builder.setPeriodic(5000);
JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(builder.build());
Toast.makeText(this, "Started", Toast.LENGTH_SHORT).show();
break;
I have tried either setting latency and backoff criteria, or set as periodic (commented out right now). Both methods work while the app is in the foreground. Neither works when the app goes to the background.
As I mentioned earlier, it works perfect on my Nexus 5, but not on Nexus 5X.
Any suggestions on how I can fix this problem?
Thanks,