web-dev-qa-db-fra.com

Android: passer la référence de la fonction à AsyncTask

Je suis nouveau sur Android et très habitué au développement Web. en javascript, lorsque vous souhaitez effectuer une tâche asynchrone, vous transmettez une fonction sous forme d'argument (un rappel):

http.get('www.example.com' , function(response){
   //some code to handle response
});

Je me demandais si nous pouvions faire la même chose avec la variable AsyncTask d'Android, passer une référence de fonction à la méthode onPostExecute() et il l'exécutera.

aucune suggestion ?

15
user47376

Oui, le concept de rappel existe aussi très bien en Java. En Java, vous définissez un rappel comme ceci:

public interface TaskListener {
    public void onFinished(String result);
}

On aurait souvent imbriqué ces types de définitions d'écouteur dans la AsyncTask comme ceci:

public class ExampleTask extends AsyncTask<Void, Void, String> {

    public interface TaskListener {
        public void onFinished(String result);
    }

    ...
}

Et une implémentation complète du rappel dans la AsyncTask ressemblerait à ceci:

public class ExampleTask extends AsyncTask<Void, Void, String> {

    public interface TaskListener {
        public void onFinished(String result);
    }

    // This is the reference to the associated listener
    private final TaskListener taskListener;

    public ExampleTask(TaskListener listener) {
        // The listener reference is passed in through the constructor
        this.taskListener = listener;
    }

    @Override
    protected String doInBackground(Void... params) {
        return doSomething();
    }

    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);

        // In onPostExecute we check if the listener is valid
        if(this.taskListener != null) {

            // And if it is we call the callback function on it.
            this.taskListener.onFinished(result);
        }
    }
}

onPostExecute() est appelé dès que la tâche en arrière-plan est terminée. Vous pouvez utiliser le tout comme ceci:

ExampleTask task = new ExampleTask(new ExampleTask.TaskListener() {
    @Override
    public void onFinished(String result) {
        // Do Something after the task has finished
    }
});

task.execute();

Ou vous pouvez définir la TaskListener complètement séparément comme ceci:

ExampleTask.TaskListener listener = new ExampleTask.TaskListener() {
    @Override
    public void onFinished(String result) {
        // Do Something after the task has finished
    }
};

ExampleTask task = new ExampleTask(listener);    
task.execute();

Ou vous pouvez sous-classe TaskListener comme ceci:

public class ExampleTaskListener implements TaskListener {

    @Override
    public void onFinished(String result) {

    }
}

Et puis utilisez-le comme ceci:

ExampleTask task = new ExampleTask(new ExampleTaskListener());    
task.execute();

Vous pouvez bien sûr simplement remplacer la méthode onPostExecute() de la AsyncTask, mais ce n’est pas recommandé et dans la plupart des cas, c’est en fait une très mauvaise pratique. Par exemple, vous pouvez faire ceci:

ExampleTask task = new ExampleTask() {
    @Override
    public void onPostExecute(String result) {
        super.onPostExecute(result);

        // Your code goes here
    }
};

Cela fonctionnera aussi bien que l'implémentation ci-dessus avec une interface d'écoute distincte, mais cela pose quelques problèmes:

Tout d’abord, vous pouvez réellement briser la ExampleTask dans son ensemble. Tout se résume à l'appel super.onPostExecute() ci-dessus. Si vous, en tant que développeur, remplacez onPostExecute() comme ci-dessus et oubliez d'inclure le super appel ou supprimez-le simplement pour une raison quelconque, si la méthode onPostExecute() d'origine dans ExampleTask ne sera plus appelée. Par exemple, l'implémentation complète de l'écouteur avec la variable TaskListener ne fonctionnerait plus du tout puisque l'appel au rappel est implémenté dans onPostExecute(). Vous pouvez également diviser la TaskListener de nombreuses autres manières en influençant inconsciemment ou involontairement l'état de la ExampleTask pour que cela ne fonctionne plus.

Si vous regardez ce qui se passe réellement lorsque vous substituez une méthode comme celle-ci, il devient beaucoup plus clair ce qui se passe. En remplaçant onPostExecute(), vous créez une nouvelle sous-classe de ExampleTask. Ce serait exactement la même chose que faire ceci:

public class AnotherExampleTask extends ExampleTask {

    @Override
    public void onPostExecute(String result) {
        super.onPostExecute(result);

        // Your code goes here
    }
}

Tout cela est juste caché derrière une fonctionnalité de langage appelée classes anonymes. Annuler soudainement une méthode comme celle-ci ne semble plus si simple et rapide, n'est-ce pas?

Pour résumer: 

  • Remplacer une telle méthode crée en fait une nouvelle sous-classe. Vous n’ajoutez pas simplement un rappel, vous modifiez le fonctionnement de cette classe et pouvez, sans le savoir, casser oh tant de choses.
  • Les erreurs de débogage comme celle-ci peuvent être beaucoup plus qu'une simple douleur dans le **. Parce que soudainement ExampleTask pourrait lancer Exceptions ou tout simplement ne plus fonctionner sans raison apparente, car vous n’aviez jamais réellement modifié son code.
  • Chaque classe doit fournir des implémentations d'auditeur aux endroits appropriés et prévus. Bien sûr, vous pouvez simplement les ajouter plus tard en surchargeant onPostExecute() mais c'est toujours très dangereux. Même @flup avec sa réputation de 13k a oublié d'inclure l'appel super.onPostExecute() dans sa réponse, imaginez ce qu'un autre développeur non expérimenté pourrait faire! 
  • Un peu d'abstraction n'a jamais fait de mal à personne. Écrire des auditeurs spécifiques peut représenter un peu plus de code, mais c'est une bien meilleure solution. Le code sera plus propre, plus lisible et beaucoup plus facile à gérer. L'utilisation de raccourcis tels que le remplacement de onPostExecute() sacrifie essentiellement la qualité du code pour un peu de commodité. Ce n’est jamais une bonne idée, mais une volonté de causer des problèmes à long terme.
44
Xaver Kapeller

En Java, les fonctions sont moins un citoyen de première classe qu'en JavaScript. AsyncTask fournit le rappel en tant que méthode de la classe, que vous devez remplacer.

Voir Effectuer une requête HTTP avec Android pour une sous-classe de AsyncTask avec une implémentation de doInBackground qui effectue une requête Web.

Si vous souhaitez effectuer plusieurs demandes HTTP avec différents rappels, vous pouvez remplacer RequestTask et implémenter onPostExecute avec les différentes implémentations de rappel. Vous pouvez utiliser une classe anonyme pour simuler la fermeture d’un rappel JavaScript qui utilise couramment:

new RequestTask(){
    @Override
    public void onPostExecute(String result) {
        // Implementation has read only access to 
        // final variables in calling scope. 
    }
}.execute("http://stackoverflow.com");

Comme le montre Xaver, vous pouvez également créer une interface complète pour l'auditeur. Cela ne me semble utile que si vous souhaitez implémenter quelques fonctions onPostExecute par défaut et choisir l’une de ces implémentations par défaut pour un appel particulier.

2
flup

à Kotlin

Tout d'abord, créez la classe AsyncTaskHelper comme ci-dessous.

class AsyncTaskHelper() : AsyncTask<Callable<Void>, Void, Boolean>() {

    var taskListener: AsyncListener? = null

    override fun doInBackground(vararg params: Callable<Void>?): Boolean {
        params.forEach {
            it?.call()
        }
        return true
    }

    override fun onPreExecute() {
        super.onPreExecute()
    }

    override fun onPostExecute(result: Boolean?) {
        super.onPostExecute(result)
        taskListener?.onFinished(result as Any)
    }

}

interface AsyncListener {
    fun onFinished(obj: Any)
}

le code ci-dessous, vous pouvez utiliser lorsque vous souhaitez utiliser une tâche async.

    AsyncTaskHelper().let {
        it.execute(Callable<Void> {
                //this area is the process do you want to do it in background
                // dosomething()
                }
            }
            null
        })
        it.taskListener = object : AsyncListener{
            override fun onFinished(obj: Any) {
                // this area is for the process will want do after finish dosomething() from Callable<Void> callback


            }

        }

Du code ci-dessus. si vous souhaitez séparer votre processus en plusieurs tâches . vous pouvez faire comme ce code ci-dessous.

AsyncTaskHelper().let {
            it.execute(Callable<Void> {
                // task 1 do in background
                null
            },Callable<Void>{
                // task 2 do in background
                null
            },Callable<Void>{
                // task 3 do in background
                null
            })

            it.taskListener = object : AsyncListener {
                override fun onFinished(obj: Any) {
                    // when task1,task2,task3 has been finished . it will do in this area
                }

            }
        }
0
Sup.Ia