128 votes

Retourner une valeur depuis AsyncTask dans Android

Une simple question : est-il possible de renvoyer une valeur en AsyncTask ?

//AsyncTask is a member class
private class MyTask extends AsyncTask<Void, Void, Void>{

    protected Void doInBackground(Void... params) {
         //do stuff
         return null;
    }

    @Override
    protected void onPostExecute(Void result) {
        //do stuff
        //how to return a value to the calling method?
    }
}

Et puis dans mon Activity / Fragment :

// The task is started from activity
myTask.execute()
// something like this?
myvalue = myTask.getvalue() 

EDIT : Cette question a été posée il y a longtemps et je n'étais pas familier avec Java. Maintenant que je m'y connais mieux, je vais faire un résumé rapide :

L'intérêt d'une tâche asynchrone est que la tâche est asynchronous ce qui signifie qu'après avoir appelé execute() sur la tâche, la tâche commence à s'exécuter sur un thread qui lui est propre. renvoyer une valeur de asynctask serait inutile parce que le thread appelant original a déjà continué à faire d'autres choses (donc la tâche est asynchrone).

Pensez au temps : À un moment donné, vous avez lancé une tâche qui s'exécutera en parallèle avec le fil principal. Lorsque la tâche parallèle s'est terminée, le temps s'est également écoulé sur le fil principal. La tâche parallèle ne peut pas remonter le temps pour renvoyer une valeur au thread principal.

Je venais du C, donc je n'y connaissais pas grand-chose. Mais il semble que beaucoup de personnes se posent la même question, alors j'ai pensé que je pourrais clarifier un peu les choses.

3 votes

En ce qui concerne vos modifications, un ensemble étroitement lié d'objets basés sur les threads a le concept de retour de quelque chose : les threadpools et les futures. Lorsque vous soumettez une tâche à un pool de threads, vous obtenez un futur. À un moment donné (espérons-le après que le runnable ait terminé), vous demandez au future sa valeur de retour. Si le runnable est terminé, vous obtenez immédiatement la valeur de retour, sinon le thread attend qu'il soit terminé. J'utilise souvent cette méthode pour diviser quelque chose en plusieurs parties, les exécuter simultanément et obtenir les résultats (souvent quelques lignes plus tard).

0 votes

@RichardTingle pouvez-vous me fournir un lien sur ce dont vous parlez... un exemple de code serait préférable. Merci d'avance et n'oubliez pas de me mentionner dans vos commentaires.

0 votes

Puis-je obtenir un résultat avec quelque chose comme un écouteur ?

46voto

Ted Hopp Points 122617

C'est ce que onPostExecute() est destiné. Il s'exécute sur le thread de l'interface utilisateur et vous pouvez transmettre votre résultat à l'écran (ou à tout autre endroit où vous le souhaitez). Il ne sera pas appelé avant que le résultat final ne soit disponible. Si vous voulez fournir des résultats intermédiaires, jetez un coup d'oeil à onProgressUpdate()

19 votes

Oui je sais que doInBackground() renvoie des données et les place là, mais comment transférer les données à mon activité principale sous forme de variable ?

5 votes

@Tom91136 - Il suffit de passer outre. onPostExecute() pour stocker le résultat dans une variable de votre choix. Notez que votre code original n'a pas de sens car la méthode (hypothétique) myTask.getValue() s'appellerait antes de un résultat était disponible. Vous pouvez également appeler la méthode AsyncTask get() pour obtenir le résultat, mais vous ne devriez pas le faire à partir du thread de l'interface utilisateur avant d'être sûr que le résultat est disponible. Sinon, le thread de l'interface utilisateur sera bloqué, ce qui va à l'encontre de l'objectif de l'utilisation d'AsyncTask.

11 votes

@TedHopp vous dites de stocker le résultat dans une variable de votre choix en onPostExecute() mais comment ramener cette variable dans votre activité ? Par exemple, si vous voulez que votre tâche se connecte à Internet et télécharge des informations, puis que vous fassiez quelque chose avec ces informations... Comment faire pour .execute() le site AsyncTask et ensuite faire quelque chose avec cette information si la ligne de code suivante s'exécute avant la AsyncTask est fait ?

38voto

ababzy Points 328

Pourquoi ne pas appeler une méthode qui gère la valeur ?

public class MyClass extends Activity {

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

        //initiate vars
        public myTask() {
            super();
            //my params here
        }

        protected Void doInBackground(Void... params) {
            //do stuff
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            //do stuff
            myMethod(myValue);
        }
    }

    private myHandledValueType myMethod(Value myValue) {
        //handle value 
        return myHandledValueType;
    }
}

60 votes

Et comment puis-je accéder à maMéthode ?

1 votes

Cela ne fonctionne pas, par exemple : myTask mTask = new myTask() ; mTask.execute() ; ne retournera pas le myHandledValueType.

7 votes

La mise en forme est mauvaise, mais en gros, l'AsyncTask appelle la méthode de l'activité : myMethod avec myValue (qui est censée être une variable de classe qui est définie dans doInBackground). Dans myMethod, vous traitez le résultat de l'AsyncTask (il n'y a aucune raison d'avoir une valeur de retour ici, il suffit de faire le travail dans cette méthode pour obtenir le résultat). Une meilleure implémentation serait d'étendre AsyncTask<Void, Void, Value> et de faire en sorte que doInBackground renvoie Value et que OnPostExecute prenne la Value et la passe à myMethod - cela éviterait toute variable de classe désordonnée et c'est ainsi que AsyncTask est conçu pour fonctionner.

27voto

Le plus simple est de passer l'objet appelant dans la tâche asynchrone (lors de sa construction si vous le souhaitez) :

public class AsyncGetUserImagesTask extends AsyncTask<Void, Void, Void> {

    private MyImagesPagerFragment mimagesPagerFragment;
    private ArrayList<ImageData> mImages = new ArrayList<ImageData>();

    public AsyncGetUserImagesTask(MyImagesPagerFragment imagesPagerFragment) {
        this.mimagesPagerFragment = imagesPagerFragment;
    }

    @Override
    public Void doInBackground(Void... records) {
        // do work here
        return null;
    }

    @Override
    protected void onPostExecute(Void result) {
        mimagesPagerFragment.updateAdapter(mImages);
    }
}

Et la classe appelante (votre activité ou fragment) exécute la tâche :

public class MyImagesPagerFragment extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        AsyncGetUserImagesTask mGetImagesTask = new AsyncGetUserImagesTask(this);
        mGetImagesTask.execute();
    }

Et ensuite, le onPostExecuteMethod appellera n'importe quelle méthode de votre classe d'origine, par ex :

    public void updateAdapter(List<ImageData> images) {
        mImageAdapter.setImages(images);
        mImageAdapter.notifyDataSetChanged();
    }
}

2 votes

Il s'agit essentiellement d'une injection de dépendance

3 votes

Cette solution fonctionne. À moins que cela ne soit réutilisable par d'autres fragments, il serait plus simple de faire de la tâche asynchrone une classe interne privée (ou anonyme) et de ne pas faire circuler le fragment. De plus, au lieu d'utiliser Void pour les résultats, vous pourriez spécifier le tableau d'images. doInBackground renverrait le tableau d'images et, lors de l'exécution, le prendrait comme paramètre, ce qui éliminerait le besoin d'une variable de classe pour faire circuler les données.

0 votes

@jt-gilkeson c'est correct mais cela ne serait pas non plus réutilisable car vous devriez inclure cette classe interne dans chaque classe Activity que vous avez. Comment cela peut-il être réalisé ? Pour faire une classe AsyncTask réutilisable ?

15voto

RedLenses Points 388

Exemple de code : L'activité utilise AsyncTask pour obtenir une valeur dans un thread d'arrière-plan, puis AsyncTask renvoie le résultat à l'activité en appelant processValue :

public class MyClass extends Activity {
  private void getValue() {
      new MyTask().execute();
  }

  void processValue(Value myValue) {
     //handle value 
     //Update GUI, show toast, etc..
  }

  private class MyTask extends AsyncTask<Void, Void, Value> {
    @Override
    protected Value doInBackground(Void... params) {
      //do stuff and return the value you want 
      return Value;
    }

    @Override
    protected void onPostExecute(Value result) {
      // Call activity method with results
      processValue(result);
    }
  }
}

11voto

kernelheart Points 103

Vous pouvez essayer celui-ci : myvalue = new myTask().execute().get(); L'inconvénient est que le processus sera gelé jusqu'à ce que l'asyncron ne soit pas terminé ;

7 votes

Un autre moyen d'éviter ce gel de l'interface utilisateur ?

1 votes

Il est déconseillé d'effectuer des tâches lourdes dans le fil d'exécution de l'interface utilisateur, ce qui peut entraîner un dysfonctionnement du système. De plus, l'exécution d'une opération de mise en réseau sur le fil d'exécution principal provoque une exception NetworkOnMainThreadException et un plantage immédiat par conception. Consultez ce bel article : developer.Android.com/training/articles/perf-anr#java

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