web-dev-qa-db-fra.com

Service de premier plan ne recevant pas de mises à jour de localisation dans Android 7.0+ lorsque l'écran est éteint

J'essaie de créer une application Android qui enregistre en permanence les données d'emplacement de périphérique en temps réel lorsque l'écran du périphérique est éteint. Mon code fonctionne correctement avec Android 6.0 et les versions antérieures, mais il semble que Android 7.0+ casse mon application. 

J'ai implémenté un service de premier plan Android qui utilise un wakelock et s'abonne à l'API Google FusedLocation. Une fois l'écran éteint, le rappel onLocationChanged n'est jamais déclenché.

Quelqu'un at-il vu une solution à ce problème? J'ai essayé de désactiver l'optimisation de la batterie via les paramètres du périphérique Android pour mon application, ainsi que pour Google Services Framework et l'emplacement fusionné.

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());
    }
}

J'ai inclus un échantillon de travail complet du code que j'utilise pour tester ici: https://github.com/joshuajwitter/ForegroundLocationService

FitBit semble avoir le même problème, voici la discussion

Voici les ressources que j'ai déjà consultées:

Exemples de code pour Android O

Garder l'application en arrière-plan sur Android tout le temps

Android, obtenez l'emplacement lorsque l'écran est éteint

Limites d'emplacement de fond

Mises à jour incohérentes du service de premier plan en veille profonde (assoupi)

Le service de premier plan ne reçoit pas les mises à jour de position en mode Doze sous Android O

EDIT 2018.01.30: J'ai également essayé d'exécuter ForegroundLocationService dans son propre processus:

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

ainsi que la mise à jour de mon code pour utiliser la FusedLocationProviderClient, toujours pas de chance.

10
Joshua W

Je rencontrais ce problème sur des périphériques émulés exécutant les versions 7.0 et 8.0 et, de manière constante, le rappel de localisation n’était pas déclenché lorsque l’écran était éteint (cmd + P). J'ai finalement pu mettre la main sur un véritable appareil 7.0 (Galaxy S7) et je n'ai pas été en mesure de reproduire le problème. Désolé si j'ai perdu le temps de quelqu'un, apparemment c'est un problème d'émulateur.

1
Joshua W

J'ai eu le même problème et j'ai trouvé une solution de travail. Pour écouter les mises à jour d'emplacement, vous ne devez PAS appeler cette méthode:

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

Vous devez utiliser une intention en attente au lieu d’un rappel, c’est-à-dire que vous devriez appeler la méthode:

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

Cliquez sur le lien pour en savoir plus: https://developers.google.com/Android/reference/com/google/Android/gms/location/FusedLocationProviderClient.html#requestLocationUpdates(com.google.Android.gms.location.Location.LocationRequest , Android.app.PendingIntent)

4
zavanton

une solution pourrait être la suivante:

vérifiez si l'emplacement n'est pas reçu dans les 2 minutes. arrêtez/démarrez à nouveau la mise à jour de l'emplacement de départ. bien que le service de premier plan doive résoudre le problème

0
tahaDev

LocationServices.FusedLocationApi est obsolète.

Je viens de refacturer mon application comme suit. J'espère que ça aide:

C'est ce que j'appelle dans la méthode onCreate de mon service:

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);
}

Et voici la méthode pour créer le rappel:

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;
}

Devinez, j'ai aussi trouvé cela quelque part à titre d'exemple, mais je n'ai pas gardé la référence.

Je n'ai pas Oreo par moi-même. Mais certains testeurs ont confirmé que cela fonctionnait.

Pour garder le service au premier plan, j'appelle ensuite "startForeground" dans la méthode onCreate de mon service.

0
user2808624