Служба Foreground не получает обновления местоположения на Android 7. 0+, когда экран выключен

Я пытаюсь создать приложение Android, которое непрерывно регистрирует данные о местоположении устройства в реальном времени, пока экран устройства выключен. Мой код работает правильно с Android 6.0 и ранее, но кажется, что Android 7. 0+ ломает мое приложение.

Я внедрил службу Android foreground, которая использует wakelock и подписывается на API Google FusedLocation. Когда экран выключен, callback onLocationChanged никогда не запускается.

Кто-нибудь видел решение этой проблемы? Я попытался отключить Оптимизацию батареи с помощью настроек устройства Android для своего приложения, а также для Google Services Framework и 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());
    }
}

Я включил полный рабочий образец кода, который я использую для тестирования здесь: https://github.com/joshuajwitter/ForegroundLocationService

У FitBit, похоже, такая же проблема, вот дискуссия

Вот ресурсы, которые я уже рассмотрел:

Образцы кода для Android O

Храните приложение в фоновом режиме на Android все время

Android, получить местоположение, когда экран выключен

Фоновые ограничения местоположения

Непоследовательные обновления местоположения в режиме переднего плана при глубоком сне (дозе)

Служба Foreground не получает обновления местоположения в режиме Doze в Android O

EDIT 2018.01.30: Я также попытался запустить ForegroundLocationService в своем собственном процессе:

<service
    android:name="foregroundlocation.stackoverflowexample.com.foregroundlocation.ForegroundLocationService"
    android:enabled="true"
    android:stopWithTask="false"
    android:process=":ForegroundLocationService"
    android:exported="false" >
</service>

а также обновление моего кода для использования FusedLocationProviderClient, все равно не повезло.

Ответы

Ответ 1

Я испытывал эту проблему на эмулированных устройствах, работающих под управлением 7.0 и 8.0, и постоянный обратный вызов местоположения не запускался, когда экран был выключен (cmd + P). Наконец, я смог получить реальное устройство 7.0 (Galaxy S7), и я не смог воспроизвести проблему. Извините, если я потратил впустую любое время, видимо, это проблема с эмулятором.

Ответ 2

У меня была такая же проблема, и я нашел рабочее решение. Чтобы прослушать обновления местоположения, вы не должны вызывать этот метод:

public Task<Void> requestLocationUpdates (LocationRequest request, 
LocationCallback callback, Looper looper)

Вы должны использовать ожидающее намерение вместо обратного вызова, то есть вы должны вызвать метод:

public Task<Void> requestLocationUpdates (LocationRequest request, 
PendingIntent callbackIntent)

Оформить ссылку на ссылку: https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderClient.html#requestLocationUpdates(com.google.android.gms.location.LocationRequest, android.app.PendingIntent)

Ответ 3

LocationServices.FusedLocationApi устарел.

Я просто реорганизовал свое приложение следующим образом. Надеюсь, поможет:

Это то, что я вызываю в onCreate-Method моего сервиса:

public void start(Context ctx) {
        FusedLocationProviderClient client = LocationServices
                .getFusedLocationProviderClient(ctx);
        //Define quality of service:
        LocationRequest request = LocationRequest.create();
        request.setInterval(1000); //Every second
        request.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        if (locCallback != null) {
            client.removeLocationUpdates(locCallback);
        }
        locCallback = createNewLocationCallback();
        client.requestLocationUpdates(request, locCallback, null);
}

И это метод создания обратного вызова:

private LocationCallback createNewLocationCallback() {
    LocationCallback locationCallback = new LocationCallback() {
        @Override
        public void onLocationResult(LocationResult result) {
            Location loc = result.getLastLocation();
            // Do something with the location (may be null!)
        }
    };
    return locationCallback;
}

Полагаю, я также нашел это где-то в качестве примера, но не сохранил ссылку.

У меня нет Oreo самостоятельно. Но некоторые тестеры подтвердили, что он работает.

Чтобы сохранить сервис на переднем плане, я просто называю "startForeground" впоследствии в методе onCreate моей службы.

Ответ 4

Одним из решений может быть следующее:

проверьте, не было ли получено местоположение в течение 2 минут, остановите/снова запустите обновление всего начального местоположения. хотя передний план должен решить проблему

Ответ 5

Кто-нибудь сталкивался с этой проблемой на Android Pie9? Работает нормально на устройствах O и ниже. Как преодолеть это?