Aller au contenu

AsyncTask en parallèle dans un BroadcastReceiver


jedix

Recommended Posts

Bonjour à tous !

Je suis heureux de poser ma première question sur ce forum :) Je ne suis pas totalement noob sur android, mais il me manque indubitablement quelques éléments de compréhension ! D'où ma question.

J'ai un BroadcastReceiver pour les sms, quand celui ci attrape un sms il en fait le traitement dans un AsyncTask. Supposons que la tâche dans le AsyncTask soit assez longue (dans l'exemple de code ci dessous je le remplace par un Thread.sleep(6000) par exemple) : si j'envoie 2 sms successifs à mon émulateur, les sms vont être traités l'un après l'autre (donc 12s en tout) alors qu'ils sont traités dans des AsyncTask différents. Je pensais que les Threads s'éxécutaient en parallèle, non ? Y a t-il un moyen pour que les 2 travaillent en même temps sur leur sms respectif ?

Voici des codes simplifiés :

Le BroadcastReceiver basique :

public class SMSReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {

 // On traite ce message
 MessageProcessing messageProcess = new MessageProcessing(context);
 messageProcess.execute(intent);
}
}

Le AsyncTask

public class MessageProcessing extends AsyncTask<Intent, Void, Void> {
private Message message = null;
@Override
protected Boolean doInBackground(Intent... intents) {
 // On récupère notre intent
 Intent intent = intents[0];
 Bundle bundle = intent.getExtras();
 if (bundle != null) {
  Object[] pdus = (Object[]) bundle.get("pdus");
  // On récupère le dernier sms et on extait numéro et corps du
  // message
  final SmsMessage lastMessage = SmsMessage
 .createFromPdu((byte[]) pdus[0]);
  final String messageBody = lastMessage.getMessageBody();
  final String phoneNumber = lastMessage
 .getDisplayOriginatingAddress();

//On cré un objet Message (classe perso)
Date now = new Date();
message = new Message(phoneNumber, messageBody, now);
// On simule un traitement long
try {
 Thread.sleep(6000);
} catch (InterruptedException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
}
}
}
}

Je vous serais reconnaissant de m'éclairer de vos lumières :) Merci !

Modifié par jedix
Lien vers le commentaire
Partager sur d’autres sites

C'est un bug qui a été corrigé par notre compatriote Mr Romain GUY

http://groups.google...9bad2620e38496a

J'ai fait un test en API Level 7 et ça fonctionne bien.

package com.nbbu.examples.asynctask;
import android.app.Activity;
import android.os.Bundle;
import android.os.AsyncTask;
public class AsyncTaskTest extends Activity {
class MyTask extends AsyncTask<String, Void, Integer>
{
 @Override
  protected Integer doInBackground(String ... x) {  
	System.out.println("Start processing " + x[0]);
	try {
	 Thread.sleep(6000);
	} catch (InterruptedException ie) {
	 ie.printStackTrace();
	}	
	System.out.println(x[0] + " processed");
 return 0;
  }
 };
 @Override
 public void onCreate(Bundle savedInstanceState) {
	 super.onCreate(savedInstanceState);
	 setContentView(R.layout.main);
	 new MyTask().execute("SMS 1");
	 new MyTask().execute("SMS 2");		
 }
}

résultat :

:03.547: INFO/System.out(326): Start processing SMS 2
:03.744: INFO/System.out(326): Start processing SMS 1
:09.603: INFO/System.out(326): SMS 2 processed
:09.750: INFO/System.out(326): SMS 1 processed

  • Like 1
Lien vers le commentaire
Partager sur d’autres sites

Salut nbbu et merci de ta réponse !

J'ai testé ton code et effectivement là ça marche bien. J'ai donc du investiger pendant un petit moment... et je crois avoir trouvé là où ça coince ! C'est que dans mon BroadcasteListener je fais ensuite appel à la méthode .get() de AsyncTask pour récupérer un Boolean (si le traitement s'est bien passé), or évidemment cette méthode attend la fin du thread. Or vu que ça se trouve dans mon BroadcastListener et que ce dernier est dans le ThreadUI, ça bloque tout ! Et ça revient quasiment au même que si je ne faisais pas mon traitement dans un Thread... (d'où le fait que mes 2 AsyncTask sont éxécutés l'un après l'autre...).

Du coup je ne fois pas trop comment faire ! Car je dois absolument récupérer le résultat booléen pour ensuite appeler ou non abortBroadcast().

Voici le code complet de mon BrodcastReceiver :

public class SMSReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
 Hashtable<String, Boolean> result = null;
 // On traite ce message
 MessageProcessing messageProcess = new MessageProcessing(context);
 messageProcess.execute(intent);
 try {// On récupère un booléan pour savoir si le message a été validé/traité
  result = (Hashtable<String, Boolean>) messageProcess.get();
  // On arrête le broadcast si le message a été validé (pour ne pas
  // recevoir le sms dans sa boite de réception)
  if (result.get("validated")) {
   abortBroadcast();
  }
 } catch (InterruptedException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
 } catch (ExecutionException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
 }
}
}

De ce fait je suis dans l'impasse, est-ce que vous auriez des idées ?

Lien vers le commentaire
Partager sur d’autres sites

Malheureusement la doc est très claire à ce sujet :

A BroadcastReceiver object is only valid for the duration of the call to onReceive(Context, Intent)

anything that requires asynchronous operation is not available, because you will need to return from the function to handle the asynchronous operation, but at that point the BroadcastReceiver is no longer active and thus the system is free to kill its process before the asynchronous operation completes.

Donc je ne vois pas trop d'autres solutions à part peut être faire un abort systématique, faire le traitement en dehors du BroadcastReceiver, puis en fonction du résultat (via la surcharge de onPostExecute) re-broadcaster ou non via sendBroadcast.

C'est juste une idée comme ça, je ne sais pas si c'est réalisable ... :emo_im_undecided:

Dans tous les cas, tiens nous au courant de tes expérimentations passionnantes :P

Lien vers le commentaire
Partager sur d’autres sites

Donc si je comprends bien, le BroadcastListener lance l'AsyncTask puis meurt (alors que l'AsyncTask n'a pas terminé). Du fait que le BroadcastListener ne soit plus, Android considère qu'il peut tuer de façon impromptue mon AsyncTask ? Il y a donc une probabilité X (qu'on ne maitrise pas du tout) que le traitement soit avorté ?

Si c'est bien ça, n'est il pas possible de rattacher l'AsyncTask à l'activité en cours par exemple ? Ou même le rattacher à quelque chose d'encore plus durable qu'une simple activité (un service par exemple ?? je pose la question comme ça, je n'ai pas encore saisi tous les tenants et aboutissants des services). Ca semble étrange que la vie d'un thread soit si facilement menacée, je veux dire, même pour une activité, il suffit d'une rotation et elle est détruite pour être reconstruite !

Avez-vous quelques réflexions sur le sujet ? ^^

Modifié par jedix
Lien vers le commentaire
Partager sur d’autres sites

Ce thread (je parle de ta question) devient vraiment très intéressant :lol:

Donc si je comprends bien,
en tout cas je comprends la même chose :P

Donc j'ai parsé parcouru la doc ici :

http://developer.and...nd-threads.html

et j'ai repéré cette phrase :

Caution: Another problem you might encounter when using a worker thread is unexpected restarts in your activity due to a runtime configuration change (such as when the user changes the screen orientation), which may destroy your worker thread. To see how you can persist your task during one of these restarts and how to properly cancel the task when the activity is destroyed, see the source code for the Shelves sample application.

C'est exactement le cas qui nous intéresse. Persister le traitement fait dans l'AsychTask.

Bon je sais pas si je vais avoir le temps de lire ça avant le réveillon ... je voudrais pas faire mon no-life...

On en rediscute l'année prochaine si tu veux bien :lol:

Lien vers le commentaire
Partager sur d’autres sites

Content de trouver quelqu'un avec qui explorer le problème ^^

Dans le lien que tu as donné le passage qui m'a interpelé c'est

Because a process running a service is ranked higher than a process with background activities, an activity that initiates a long-running operation might do well to start a service for that operation, rather than simply create a worker thread—particularly if the operation will likely outlast the activity. For example, an activity that's uploading a picture to a web site should start a service to perform the upload so that the upload can continue in the background even if the user leaves the activity. Using a service guarantees that the operation will have at least "service process" priority, regardless of what happens to the activity. This is the same reason that broadcast receivers should employ services rather than simply put time-consuming operations in a thread.

Cette dernière phrase me conduit à essayer avec un service ! Ca me permettra de me plonger pour la première fois là dedans au passage. Grosso ce que je vais essayer de faire c'est faire tourner un service en fond qui sera appelé par le BroadcastReceiver, et c'est dans le service que tournera l'AsyncTask. On verra bien si ça fonctionne.

Edit : et ce post me conforte dans cette voie : http://androiddev.orkitra.com/?p=283

Modifié par jedix
  • Like 1
Lien vers le commentaire
Partager sur d’autres sites

Grosso ce que je vais essayer de faire c'est faire tourner un service en fond qui sera appelé par le BroadcastReceiver, et c'est dans le service que tournera l'AsyncTask.

D'après tes articles cités, cela semble en effet une très bonne solution.

Tiens moi nous au courant :P

Lien vers le commentaire
Partager sur d’autres sites

J'ai essayé de migrer vers le sytème de service, mais ça a engendré pas mal de complications, notamment pour passer les données à traiter au service : il faut utiliser putExtra et getExtra de la classe Intent, or si on veut passer un objet il faut qu'il implémente l'interface Parcelable (dans le même esprit que Serializable). Bon, mais le problème n'est pas là, c'est après j'ai un pb de cast à l'éxécution, et comme les vacs sont finies et que j'ai repris les cours je n'ai pas trop eu le temps de me repencher sur le problème... il faudrait que je fasse des essais sur un projet test...

Lien vers le commentaire
Partager sur d’autres sites

Moi je ne dis rien mais ça m'intéresse beaucoup aussi

Cool on se sent moins seul :)

De mon coté j'ai jeté un coup d’œil sur le code de Romain Guy (Shelves) cité plus haut dans lequel il parle de persistance de tâches. La solution comporte la même difficulté que dans ton cas de service, c'est à dire la sérialisation des données. En fait sur le onSaveInstanceState il sérialise les taches en cours dans un Bundle. Dans son cas c'est assez simple puisque la tache consite au téléchargement d'un livre lié à son numéro isbn. Il stocke donc ce numéro pour pouvoir le télécharger de nouveau lors du onRestoreInstanceState.

Ton cas est beaucoup plus tricky étant donné que c'est un SMS. Je pense en plus que tu risque d'être confronté au fait qu'il s'agisse d'un evt que du veux capturer ou laisser passer et que dans ce cas la vitesse de traitement est assez critique.

Lien vers le commentaire
Partager sur d’autres sites

  • 2 weeks later...

Pourquoi ne pas simplement utiliser un Thread classique pour effectué ceci ?

final Handler handler = new Handler(){			
@Override
public void handleMessage(Message msg) {   
 super.handleMessage(msg);
 //Dans le thread de l'UI
 switch (msg.what) {
  case 1:

   break;
 }
}
};
Thread thread = new Thread(){
public void run() {
 //Dans le thread

 //envoi de données au thread de l'UI
 handler.sendEmptyMessage(1);
}
};
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();

Modifié par ChrOnOs
Lien vers le commentaire
Partager sur d’autres sites

  • 2 weeks later...

Rejoignez la conversation

Vous pouvez poster maintenant et vous enregistrez plus tard. Si vous avez un compte, connectez-vous maintenant pour poster.

Invité
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Répondre à ce sujet…

×   Collé en tant que texte enrichi.   Coller en tant que texte brut à la place

  Seulement 75 émoticônes maximum sont autorisées.

×   Votre lien a été automatiquement intégré.   Afficher plutôt comme un lien

×   Votre contenu précédent a été rétabli.   Vider l’éditeur

×   Vous ne pouvez pas directement coller des images. Envoyez-les depuis votre ordinateur ou insérez-les depuis une URL.

×
×
  • Créer...