web-dev-qa-db-fra.com

Comment forcer un IntentService à s'arrêter immédiatement avec un bouton d'annulation d'une activité?

J'ai un IntentService qui est démarré à partir d'une activité et je voudrais pouvoir arrêter le service immédiatement de l'activité avec un bouton "Annuler" dans l'activité. Dès que ce bouton "Annuler" est enfoncé, je veux que le service arrête d'exécuter des lignes de code.

J'ai trouvé un certain nombre de questions similaires à cela (ie ici , ici , ici , ici ), mais pas de bonnes réponses. Activity.stopService() et Service.stopSelf() exécutez immédiatement la méthode Service.onDestroy() mais laissez ensuite le code dans onHandleIntent() terminer complètement avant de détruire le service.

Puisqu'il n'y a apparemment aucun moyen garanti de terminer immédiatement le thread du service, la seule solution recommandée que je peux trouver ( ici ) est d'avoir une variable membre booléenne dans le service qui peut être commutée dans la fonction onDestroy() méthode, puis avoir à peu près toutes les lignes du code dans onHandleIntent() enveloppé dans sa propre clause "if" en regardant cette variable. C'est une terrible façon d'écrire du code.

Quelqu'un connaît-il une meilleure façon de procéder dans un IntentService?

42
pvans

Arrêter un thread ou un processus immédiatement est souvent une chose sale. Cependant, cela devrait aller si votre service est apatride.

Déclarez le service en tant que processus distinct dans le manifeste:

<service
     Android:process=":service"
     ...

Et quand vous voulez arrêter son exécution, tuez simplement ce processus:

ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
List<RunningAppProcessInfo> runningAppProcesses = am.getRunningAppProcesses();

Iterator<RunningAppProcessInfo> iter = runningAppProcesses.iterator();

while(iter.hasNext()){
    RunningAppProcessInfo next = iter.next();

    String pricessName = getPackageName() + ":service";

    if(next.processName.equals(pricessName)){
        Process.killProcess(next.pid);
        break;
    }
}
12
kupsef

Voici l'astuce, utilisez une variable statique volatile et vérifiez l'état de continuer dans certaines lignes de votre service pour que le service continue soit vérifié:

class MyService extends IntentService {
    public static volatile boolean shouldContinue = true;
    public MyService() {
        super("My Service");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        doStuff();
    }

    private void doStuff() {
        // do something 

        // check the condition
        if (shouldContinue == false) {
            stopSelf();
            return;
        }

       // continue doing something

       // check the condition
       if (shouldContinue == false) {
           stopSelf();
           return;
       }

       // put those checks wherever you need
   }
}

et dans votre activité faites cela pour arrêter votre service,

 MyService.shouldContinue = false;
32
Sadegh

J'ai utilisé un BroadcastReceiver dans le service qui met simplement un booléen d'arrêt à vrai. Exemple:

private boolean stop=false;

public class StopReceiver extends BroadcastReceiver {

   public static final String ACTION_STOP = "stop";

   @Override
   public void onReceive(Context context, Intent intent) {
       stop = true;
   }
}


@Override
protected void onHandleIntent(Intent intent) {
    IntentFilter filter = new IntentFilter(StopReceiver.ACTION_STOP);
    filter.addCategory(Intent.CATEGORY_DEFAULT);
    StopReceiver receiver = new StopReceiver();
    registerReceiver(receiver, filter);

    // Do stuff ....

    //In the work you are doing
    if(stop==true){
        unregisterReceiver(receiver);
        stopSelf();
    }
}

Ensuite, à partir de l'appel d'activité:

//STOP SERVICE
Intent sIntent = new Intent();
sIntent.setAction(StopReceiver.ACTION_STOP);
sendBroadcast(sIntent);

Pour arrêter le service.

PD: J'utilise un booléen car dans mon cas, j'arrête le service dans une boucle mais vous pouvez probablement appeler unregisterReceiver et stopSelf dans onReceive.

PD2: N'oubliez pas d'appeler unregisterReceiver si le service se termine, il fonctionne normalement ou vous obtiendrez une erreur IntentReceiver fuite.

5
JoeyCK

Dans le cas de IntentService il n'arrête pas ou prend toute autre demande via une action d'intention jusqu'à ce que sa méthode onHandleIntent termine la demande précédente.

Si nous essayons de redémarrer IntentService avec une autre action, onHandleIntent sera appelé uniquement lorsque l'intention/la tâche précédente sera terminée.

De même, stopService(intent); ou stopSelf(); ne fonctionne pas tant que la méthode onHandleIntent() n'a pas terminé sa tâche.

Donc, je pense qu'ici, la meilleure solution consiste à utiliser normalement Service ici.

J'espère que cela aidera!

2
Manish DEWAN

Si vous utilisez un IntentService, je pense que vous êtes bloqué en train de faire quelque chose comme vous le décrivez, où le code onHandleIntent() doit interroger son signal "stop".

Si votre tâche d'arrière-plan est potentiellement longue, et si vous devez être en mesure de l'arrêter, je pense que vous feriez mieux d'utiliser plutôt un Service simple. À un niveau élevé, écrivez votre service à:

  • Exposer une intention de "démarrage" pour démarrer une tâche AsyncTask pour effectuer votre travail d'arrière-plan, en sauvegardant une référence à cette tâche AsyncT nouvellement créée.
  • Exposez une intention "annuler" pour invoquer AsyncTask.cancel(true), ou demandez à onDestroy () d'invoquer AsyncTask.cancel(true).
  • L'activité peut alors envoyer l'intention "annuler" ou simplement appeler stopService ().

En échange de la possibilité d'annuler les travaux en arrière-plan, le Service assume les responsabilités suivantes:

  • La fonction AsyncTask doInBackground() devra gérer avec élégance InterruptedException et/ou vérifier périodiquement Thread.interrupted () et retourner "tôt".
  • Le Service devra s'assurer que stopSelf() est appelée (peut-être dans AsyncTask onPostExecute/onCancelled).
1
rtsai2000
@Override
protected void onHandleIntent(Intent intent) {
    String action = intent.getAction();
    if (action.equals(Action_CANCEL)) {
        stopSelf();
    } else if (action.equals(Action_START)) {
        //handle
    }
}

Esperons que ça marche.

1
cycDroid

Comme @budius l'a déjà mentionné dans son commentaire, vous devez définir un booléen sur le Service lorsque vous cliquez sur ce bouton:

// your Activity.Java
public boolean onClick() {
   //...
   mService.performTasks = false;
   mService.stopSelf();
}

Et dans votre Intent gestion, avant d'effectuer la tâche importante de validation/envoi des informations d'intention, utilisez simplement ce booléen:

// your Service.Java
public boolean performTasks = true;

protected void onHandleIntent(Intent intent) {
   Bundle intentInfo = intent.getBundle();
   if (this.performTasks) {
      // Then handle the intent...
   }
}

Sinon, le Service fera sa tâche de traitement de ce Intent. Voilà comment il était censé être utilisé, parce que je ne vois pas comment vous pourriez le résoudre autrement si vous regardez le code principal.

0
tolgap