49 votes

Android : Comment envoyer périodiquement la position à un serveur ?

Je gère un service Web qui permet aux utilisateurs d'enregistrer leurs déplacements (un peu comme MyTracks de Google) dans le cadre d'une application plus vaste. Le problème est qu'il est facile de transmettre des données, notamment des coordonnées et d'autres éléments, au serveur lorsqu'un utilisateur commence ou termine un trajet. En tant que débutant, je ne sais pas comment mettre en place un service d'arrière-plan qui envoie les mises à jour de localisation une fois par période (prédéterminée) (min 3 minutes, max 1 heure) jusqu'à ce que l'utilisateur signale la fin du voyage, ou jusqu'à ce qu'une quantité de temps prédéfinie s'écoule.

Une fois que le voyage est lancé depuis le téléphone, le serveur répond avec une période d'interrogation que le téléphone doit utiliser comme intervalle entre les mises à jour. Cette partie fonctionne, en ce sens que je peux afficher la réponse sur le téléphone, et mon serveur enregistre l'action de l'utilisateur. De même, le voyage est fermé côté serveur lors de la demande de fermeture du voyage.

Cependant, lorsque j'ai essayé de lancer une méthode de suivi périodique à partir de l'activité StartTrack, en utilisant requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener) où minTime est la période d'interrogation du serveur, cela n'a pas fonctionné, et je n'ai pas obtenu d'erreur. Cela signifie donc que je suis désemparé à ce stade, n'ayant jamais utilisé Android auparavant.

J'ai vu de nombreux messages ici sur l'utilisation de services d'arrière-plan avec des gestionnaires, des intentions en attente, et d'autres choses pour faire des choses similaires, mais je ne comprends vraiment pas comment faire. J'aimerais que l'utilisateur puisse faire d'autres choses sur le téléphone pendant que les mises à jour sont en cours, donc si vous pouviez m'indiquer un tutoriel qui montre comment écrire des services d'arrière-plan (peut-être qu'ils sont exécutés en tant que classes séparées ?) ou d'autres façons de faire, ce serait génial.

73voto

Rob Kent Points 3133

J'en ai récemment écrit un et j'ai décidé que ce n'était pas une bonne idée de laisser un service d'arrière-plan en marche. Il sera probablement arrêté par le système d'exploitation de toute façon, ou il pourrait l'être. Ce que j'ai fait, c'est d'utiliser un filtre pour l'intention de démarrage, puis de définir une alarme à l'aide du gestionnaire d'alarmes afin que mon application soit redémarrée à intervalles réguliers, puis qu'elle envoie les données. Vous trouverez de bonnes informations sur les services et le gestionnaire d'alarmes dans la documentation Android.

Tout d'abord, j'ai créé un récepteur de diffusion qui démarre simplement mon service lorsqu'une connexion internet est ouverte (je ne suis intéressé que s'il y a une connexion - vous pourriez vouloir filtrer pour l'événement de démarrage aussi). Le récepteur de lancement doit être de courte durée, donc juste démarrer votre service :

public class LaunchReceiver extends BroadcastReceiver {

    public static final String ACTION_PULSE_SERVER_ALARM = 
            "com.proofbydesign.homeboy.ACTION_PULSE_SERVER_ALARM";

    @Override
    public void onReceive(Context context, Intent intent) {
        AppGlobal.logDebug("OnReceive for " + intent.getAction());
        AppGlobal.logDebug(intent.getExtras().toString());
        Intent serviceIntent = new Intent(AppGlobal.getContext(),
                MonitorService.class);
        AppGlobal.getContext().startService(serviceIntent);
    }
}

Dans le manifeste, j'ai :

<receiver
    android:name="LaunchReceiver"
    android:label="@string/app_name" >
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
    <intent-filter>
        <action android:name="com.proofbydesign.homeboy.ACTION_PULSE_SERVER_ALARM" />
    </intent-filter>
</receiver>

Remarquez que j'ai un filtre pour ma propre alarme, qui me permet de fermer le service et de le faire redémarrer après qu'il ait fait son travail.

Le haut du service de mon moniteur ressemble à ça :

public class MonitorService extends Service {

    private LoggerLoadTask mTask;
    private String mPulseUrl;
    private HomeBoySettings settings;
    private DataFile dataFile;
    private AlarmManager alarms;
    private PendingIntent alarmIntent;
    private ConnectivityManager cnnxManager;

    @Override
    public void onCreate() {
        super.onCreate();
        cnnxManager = (ConnectivityManager) 
                getSystemService(Context.CONNECTIVITY_SERVICE);
        alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        Intent intentOnAlarm = new Intent(
                LaunchReceiver.ACTION_PULSE_SERVER_ALARM);
        alarmIntent = PendingIntent.getBroadcast(this, 0, intentOnAlarm, 0);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        // reload our data
        if (mPulseUrl == null) {
            mPulseUrl = getString(R.string.urlPulse);
        }
        AppGlobal.logDebug("Monitor service OnStart.");
        executeLogger();
    }

executeLogger démarre une asyncTask, ce qui est probablement un excès de prudence de ma part (ce n'est que ma troisième application Android). L'asyncTask récupère les données GPS, les envoie à l'internet et enfin règle l'alarme suivante :

private void executeLogger() {
    if (mTask != null
        && mTask.getStatus() != LoggerLoadTask.Status.FINISHED) {
        return;
    }
    mTask = (LoggerLoadTask) new LoggerLoadTask().execute();
}

private class LoggerLoadTask extends AsyncTask<Void, Void, Void> {

    // TODO: create two base service urls, one for debugging and one for live.
    @Override
    protected Void doInBackground(Void... arg0) {
        try {
            // if we have no data connection, no point in proceeding.
            NetworkInfo ni = cnnxManager.getActiveNetworkInfo();
            if (ni == null || !ni.isAvailable() || !ni.isConnected()) {
                AppGlobal
                        .logWarning("No usable network. Skipping pulse action.");
                return null;
            }
            // / grab and log data
        } catch (Exception e) {
            AppGlobal.logError(
                    "Unknown error in background pulse task. Error: '%s'.",
                    e, e.getMessage());
        } finally {
            // always set the next wakeup alarm.
            int interval;
            if (settings == null
                || settings.getPulseIntervalSeconds() == -1) {
                interval = Integer
                        .parseInt(getString(R.string.pulseIntervalSeconds));
            } else {
                interval = settings.getPulseIntervalSeconds();
            }
            long timeToAlarm = SystemClock.elapsedRealtime() + interval
                * 1000;
            alarms.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToAlarm,
                    alarmIntent);
        }
        return null;
    }
}

Je remarque que je n'appelle pas stopSelf() après avoir déclenché l'alarme, donc mon service va rester assis à ne rien faire jusqu'à ce qu'il soit arrêté par l'op sys. Comme je suis le seul utilisateur de cette application, cela n'a pas d'importance, mais pour une application publique, l'idée est de régler l'alarme pour le prochain intervalle, puis d'appeler stopSelf pour fermer.

Mise à jour Voir le commentaire de @juozas sur l'utilisation de 'alarms.setRepeating()'.

0 votes

Rob, merci. Je suis en train de chercher dans la documentation pour voir comment utiliser les filtres d'intention et comment définir une alarme pour appeler périodiquement le service, au lieu de le faire fonctionner en permanence.

1 votes

Votre solution est fantastique. J'ai finalement réussi à faire fonctionner la mienne, mais j'ai certainement pris plusieurs notes de votre approche. Par exemple, je n'ai pas du tout vérifié l'existence d'une connexion réseau, de sorte qu'en l'absence de connexion, j'obtiens une erreur. Bravo !

0 votes

L'appel ni.isAvailable(), si je me souviens bien, vous empêche d'ouvrir une connexion si l'itinérance des données est désactivée.

17voto

Mark Points 2257

Vous devez créer une classe distincte qui est une sous-classe de la classe Service classe.

Documentation sur les services

Votre application principale doit pouvoir appeler startService et stopService pour lancer le processus d'arrière-plan. Il y a aussi d'autres appels utiles dans la classe contextuelle pour gérer le service :

Documentation sur le contexte

0 votes

Merci beaucoup. J'ai pu construire le service, le démarrer et l'arrêter. Je dois maintenant m'assurer que le système ne le tue pas et qu'il est appelé si nécessaire.

2 votes

Gardez à l'esprit que le système d'exploitation arrêtera (et redémarrera) périodiquement votre service comme il l'entend : developer.Android.com/reference/Android/app/

0 votes

Jscharf, comment puis-je m'assurer que le service est garanti d'être appelé périodiquement, et seulement exécuté ensuite, jusqu'à ce que l'utilisateur effectue une action particulière pour arrêter les appels ? Je cherche à utiliser une alarme et je consulte les documents sur la façon de les utiliser...

2voto

brucemax Points 33

Je suis d'accord avec Rob Kent, et en plus je pense qu'il serait mieux d'étendre WakefulBroadcastReceiver dans votre BroadcastReceiver et d'utiliser sa méthode statique. startWakefulService(android.content.Context context,android.content.Intent intent) parce que ça garantissait que votre service ne serait pas fermé par os.

public class YourReceiver extends WakefulBroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {

        Intent service = new Intent(context, YourService.class);
        startWakefulService(context, service);
    }
}

Documentation officielle

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X