Android O убивает мой Сервис, как только он уходит на задний план
Я прочитал много других подобных вопросов и проверил документацию по Android об ограничениях фонового выполнения ( https://developer.android.com/about/versions/oreo/background), но так и не смог найти решение, Я также перепробовал большинство настроек телефона.
Проблема: у меня есть приложение, которое используется для сбора данных о местоположении в фоновом режиме. Я использую Сервис для достижения этой цели. В данный момент он собирает данные о местоположении каждые 5 секунд. Он отлично работает на моем Nexus 5 (API 23); однако, это работает только на моем Nexus 5X (API 27), когда приложение находится на переднем плане. Как только он уходит в фоновом режиме, он останавливается. Так что это не имеет никакого отношения к долгосрочным задачам в фоновом режиме, как только я ухожу из приложения, сервис немедленно останавливается.
Вот мой код класса обслуживания:
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
Я звоню в службу из моей основной деятельности, используя это:
Intent serviceStartIntent = new Intent(this, GPSService.class);
startService(serviceStartIntent);
И остановите это, используя этот код:
Intent serviceStopIntent = new Intent(this, GPSService.class);
stopService(serviceStopIntent);
Вот код, который я пытался использовать 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
Я называю это в своей основной деятельности следующим образом:
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;
Я пытался либо установить критерии задержки и отката, либо установить периодические (закомментировано прямо сейчас). Оба метода работают, пока приложение находится на переднем плане. Ни одна из них не работает, когда приложение переходит в фоновый режим.
Как я упоминал ранее, он отлично работает на моем Nexus 5, но не на Nexus 5X.
Любые предложения о том, как я могу решить эту проблему?
Спасибо,
1 ответ
Это ведет себя как ожидалось. Начиная с Android o, когда приложение находится на переднем плане, оно может свободно создавать и запускать как передний, так и фоновый сервисы. Когда приложение переходит в фоновый режим, оно имеет окно в несколько минут (по моим наблюдениям оно составляет около 1–2 минут), в котором ему все еще разрешено создавать и использовать службы. Система останавливает фоновые сервисы приложения, как если бы приложение вызывало сервисы. Service.stopSelf()
методы.
Если задача должна быть выполнена через завершение немедленно и надежно, лучшей альтернативой будет использование ForegroundService
, Вы можете следовать этому SO, который описывает этот подход.
Если задание необходимо выполнить позднее, когда будут соблюдены определенные ограничения, рассмотрите возможность использования JobScheduler или WorkManager.