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.

Другие вопросы по тегам