web-dev-qa-db-fra.com

Où arrêter / détruire les threads dans Android Classe de service?

J'ai créé un service fileté de la manière suivante:

public class TCPClientService extends Service{  
...

@Override
public void onCreate() {
    ...
    Measurements = new LinkedList<String>();
    enableDataSending();    
}

@Override
public IBinder onBind(Intent intent) {
    //TODO: Replace with service binding implementation
    return null;
}

@Override
public void onLowMemory() {
    Measurements.clear();
    super.onLowMemory();
}

@Override
public void onDestroy() {
    Measurements.clear();
    super.onDestroy();
    try {
        SendDataThread.stop();
    } catch(Exception e){
        ...     
    }

}

private Runnable backgrounSendData = new Runnable() {

    public void run() {
        doSendData();
    }
};

private void enableDataSending() {
    SendDataThread = new Thread(null, backgrounSendData, "send_data");
    SendDataThread.start();
}

 private void addMeasurementToQueue() {
     if(Measurements.size() <= 100) {
         String measurement = packData();
         Measurements.add(measurement);
     }
 }

 private void doSendData() {
     while(true) {
         try {      
             if(Measurements.isEmpty()) {
                 Thread.sleep(1000);
                 continue;
             }
             //Log.d("TCP", "C: Connecting...");
             Socket socket = new Socket();
             socket.setTcpNoDelay(true);
             socket.connect(new InetSocketAddress(serverAddress, portNumber), 3000);
             //socket.connect(new InetSocketAddress(serverAddress, portNumber));
             if(!socket.isConnected()) {
                 throw new Exception("Server Unavailable!");
             }
             try {
                 //Log.d("TCP", "C: Sending: '" + message + "'");
                 PrintWriter out = new PrintWriter( new BufferedWriter( new OutputStreamWriter(socket.getOutputStream())),true);
                 String message = Measurements.remove();
                 out.println(message);
                 Thread.sleep(200);
                 Log.d("TCP", "C: Sent.");
                 Log.d("TCP", "C: Done.");
                 connectionAvailable = true;              
             } catch(Exception e) {
                 Log.e("TCP", "S: Error", e);
                 connectionAvailable = false;
             } finally {
                 socket.close();
                 announceNetworkAvailability(connectionAvailable);
             }
         } catch (Exception e) {
             Log.e("TCP", "C: Error", e);
             connectionAvailable = false;
             announceNetworkAvailability(connectionAvailable);
         }
    }
}

...
}

Après avoir fermé l'application, le téléphone fonctionne très lentement et je suppose que cela est dû à l'échec de la terminaison du thread.

Quelqu'un sait-il quelle est la meilleure façon de terminer tous les threads avant de terminer l'application?

37
Niko Gamulin

Addendum : Le cadre Android fournit de nombreux assistants pour des travaux ponctuels, des travaux en arrière-plan, etc., ce qui peut être préférable plutôt que d'essayer de rouler votre propre fil dans de nombreux cas. Comme mentionné dans un article ci-dessous, AsyncTask est un bon point de départ à examiner. J'encourage les lecteurs à examiner les dispositions du cadre avant de commencer à penser à faire leur propre fil.

Il y a plusieurs problèmes dans l'exemple de code que vous avez publié, que je vais résoudre dans l'ordre:

1) Thread.stop () est obsolète depuis un certain temps maintenant, car il peut laisser des variables dépendantes dans des états incohérents dans certaines circonstances. Voir cette page de réponse de Sun pour plus de détails (Edit: ce lien est maintenant mort, voir cette page pour pourquoi ne pas utiliser Thread.stop () ). Une méthode préférée pour arrêter et démarrer un thread est la suivante (en supposant que votre thread s'exécute quelque peu indéfiniment):

private volatile Thread runner;

public synchronized void startThread(){
  if(runner == null){
    runner = new Thread(this);
    runner.start();
  }
}

public synchronized void stopThread(){
  if(runner != null){
    Thread moribund = runner;
    runner = null;
    moribund.interrupt();
  }
}

public void run(){
  while(Thread.currentThread() == runner){
    //do stuff which can be interrupted if necessary
  }
}

Ce n'est qu'un exemple de la façon d'arrêter un thread, mais la conclusion est que vous êtes responsable de quitter un thread comme vous le feriez pour toute autre méthode. Maintenez une méthode de communication entre les threads (dans ce cas, une variable volatile, peut également être via un mutex, etc.) et dans votre logique de thread, utilisez cette méthode de communication pour vérifier si vous devez quitter tôt, nettoyer, etc.

2) Votre liste de mesures est accessible par plusieurs threads (le thread d'événement et votre thread utilisateur) en même temps sans aucune synchronisation. Il semble que vous n'ayez pas à lancer votre propre synchronisation, vous pouvez utiliser un BlockingQueue .

3) Vous créez un nouveau socket à chaque itération de votre thread d'envoi. Il s'agit d'une opération plutôt lourde et n'a de sens que si vous vous attendez à ce que les mesures soient extrêmement rares (disons une heure ou moins). Soit vous voulez un socket persistant qui n'est pas recréé à chaque boucle du thread, soit vous voulez un exécutable en une fois que vous pouvez `` tirer et oublier '' qui crée un socket, envoie toutes les données pertinentes et se termine. (Une note rapide sur l'utilisation d'un socket persistant, les méthodes de socket qui bloquent, telles que la lecture, ne peuvent pas être interrompues par Thread.interrupt (), et donc lorsque vous voulez arrêter le thread, vous devez fermer le socket ainsi que l'appel d'interruption)

4) Il est inutile de lancer vos propres exceptions à partir d'un thread, sauf si vous vous attendez à l'attraper ailleurs. Une meilleure solution consiste à consigner l'erreur et si elle est irrécupérable, arrêtez le thread. Un thread peut s'arrêter avec du code comme (dans le même contexte que ci-dessus):

public void run(){
    while(Thread.currentThread() == runner){
      //do stuff which can be interrupted if necessary

      if(/*fatal error*/){
        stopThread();
        return; //optional in this case since the loop will exit anyways
      }
    }
  }

Enfin, si vous voulez être sûr qu'un thread se termine avec le reste de votre application, quoi qu'il arrive, une bonne technique consiste à appeler Thread.setDaemon (true) après la création et avant de démarrer le thread. Cela marque le thread comme un thread démon, ce qui signifie que le VM s'assurera qu'il est automatiquement détruit s'il n'y a pas de threads non démon en cours d'exécution (comme si votre application se ferme).

Le respect des meilleures pratiques en matière de threads doit garantir que votre application ne bloque pas ou ne ralentit pas le téléphone, bien qu'elles puissent être assez complexes :)

89
sooniln

En fait, vous n'avez pas besoin de la variable "runner" comme décrit ci-dessus, quelque chose comme:

while (!interrupted()) {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException ex) {
        break;
    }
}

Mais généralement, s'asseoir dans une boucle Thread.sleep () est une très mauvaise idée.

Regardez l'API AsyncTask dans la nouvelle API 1.5. Cela résoudra probablement votre problème plus élégamment que d'utiliser un service. Votre téléphone ralentit car le service ne s'arrête jamais - il n'y a rien qui provoquera la mort du service.

6
Mike Hearn