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
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.
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.
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)
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
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.