L'API Task est la méthode standard pour gérer les opérations asynchrones dans les services Google Play. Elle offre un moyen puissant et flexible de gérer les appels asynchrones, en remplaçant l'ancien modèle PendingResult. Avec Task, vous pouvez enchaîner plusieurs appels, gérer des flux complexes et écrire des gestionnaires de réussite et d'échec clairs.
Gérer les résultats des tâches
De nombreuses API des services Google Play et de Firebase renvoient un objet Task pour représenter les opérations asynchrones. Par exemple,
FirebaseAuth.signInAnonymously()
renvoie un Task<AuthResult> qui représente le résultat de l'opération de connexion. Le Task<AuthResult> indique que lorsque la tâche se termine
correctement, elle renvoie un objet AuthResult.
Vous pouvez gérer le résultat d'une Task en associant des écouteurs qui répondent à la réussite, à l'échec ou aux deux :
Task<AuthResult> task = FirebaseAuth.getInstance().signInAnonymously();
Pour gérer la réussite d'une tâche, associez un OnSuccessListener :
task.addOnSuccessListener(new OnSuccessListener<AuthResult>() { @Override public void onSuccess(AuthResult authResult) { // Task completed successfully // ... } });
Pour gérer l'échec d'une tâche, associez un OnFailureListener :
task.addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
Pour gérer à la fois la réussite et l'échec dans le même écouteur, associez un OnCompleteListener :
task.addOnCompleteListener(new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (task.isSuccessful()) { // Task completed successfully AuthResult result = task.getResult(); } else { // Task failed with an exception Exception exception = task.getException(); } } });
Gérer les threads
Par défaut, les écouteurs associés à une Task sont exécutés sur le thread principal (UI) de l'application. Cela signifie que vous devez éviter d'effectuer des opérations de longue durée dans les écouteurs. Si vous devez effectuer une opération de longue durée, vous pouvez spécifier un Executor qui est utilisé pour planifier les écouteurs sur un thread en arrière-plan.
// Create a new ThreadPoolExecutor with 2 threads for each processor on the // device and a 60 second keep-alive time. int numCores = Runtime.getRuntime().availableProcessors(); ThreadPoolExecutor executor = new ThreadPoolExecutor(numCores * 2, numCores *2, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); task.addOnCompleteListener(executor, new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { // ... } });
Utiliser des écouteurs limités à l'activité
Lorsque vous devez gérer les résultats des tâches dans une Activity, il est important de gérer le cycle de vie des écouteurs pour éviter qu'ils ne soient appelés lorsque l'Activity n'est plus visible. Pour ce faire, vous pouvez utiliser des écouteurs limités à l'activité. Ces écouteurs sont automatiquement supprimés lorsque la méthode onStop de votre Activity est appelée, de sorte qu'ils ne sont pas exécutés après l'arrêt de l'Activity.
Activity activity = MainActivity.this; task.addOnCompleteListener(activity, new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { // ... } });
Enchaîner les tâches
Si vous utilisez un ensemble d'API qui renvoient des objets Task dans une fonction complexe, vous pouvez les enchaîner à l'aide de continuations. Cela vous permet d'éviter les rappels profondément imbriqués et de consolider la gestion des erreurs pour plusieurs tâches enchaînées.
Prenons l'exemple d'un scénario dans lequel vous disposez d'une méthode doSomething qui
renvoie un Task<String>, mais qui nécessite un AuthResult comme paramètre.
Vous pouvez obtenir cet AuthResult de manière asynchrone à partir d'une autre Task :
public Task<String> doSomething(AuthResult authResult) {
// ...
}À l'aide de la méthode Task.continueWithTask, vous pouvez enchaîner ces deux tâches :
Task<AuthResult> signInTask = FirebaseAuth.getInstance().signInAnonymously(); signInTask.continueWithTask(new Continuation<AuthResult, Task<String>>() { @Override public Task<String> then(@NonNull Task<AuthResult> task) throws Exception { // Take the result from the first task and start the second one AuthResult result = task.getResult(); return doSomething(result); } }).addOnSuccessListener(new OnSuccessListener<String>() { @Override public void onSuccess(String s) { // Chain of tasks completed successfully, got result from last task. // ... } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // One of the tasks in the chain failed with an exception. // ... } });
Bloquer une tâche
Si votre programme s'exécute déjà dans un thread en arrière-plan, vous pouvez bloquer le thread actuel et attendre la fin de la tâche au lieu d'utiliser un rappel :
try {
// Block on a task and get the result synchronously. This is generally done
// when executing a task inside a separately managed background thread. Doing this
// on the main (UI) thread can cause your application to become unresponsive.
AuthResult authResult = Tasks.await(task);
} catch (ExecutionException e) {
// The Task failed, this is the same exception you'd get in a non-blocking
// failure handler.
// ...
} catch (InterruptedException e) {
// An interrupt occurred while waiting for the task to complete.
// ...
}Vous pouvez également spécifier un délai avant expiration lorsque vous bloquez une tâche pour éviter que votre application ne se bloque indéfiniment si la tâche met trop de temps à se terminer :
try {
// Block on the task for a maximum of 500 milliseconds, otherwise time out.
AuthResult authResult = Tasks.await(task, 500, TimeUnit.MILLISECONDS);
} catch (ExecutionException e) {
// ...
} catch (InterruptedException e) {
// ...
} catch (TimeoutException e) {
// Task timed out before it could complete.
// ...
}Interopérabilité
Task est conçu pour fonctionner en synergie avec d'autres modèles de programmation asynchrone Android courants. Il peut être converti vers et depuis d'autres primitives telles que
ListenableFuture et les coroutines Kotlin, qui sont
recommandées par AndroidX,
ce qui vous permet d'utiliser l'approche la mieux adaptée à vos besoins.
Voici un exemple d'utilisation d'une Task :
// ... simpleTask.addOnCompleteListener(this) { completedTask -> textView.text = completedTask.result }
Coroutine Kotlin
Pour utiliser les coroutines Kotlin avec Task, ajoutez la dépendance suivante à votre projet, puis utilisez l'extrait de code pour effectuer la conversion à partir d'une Task.
Gradle (niveau module build.gradle, généralement app/build.gradle)
// Source: https://github.com/Kotlin/kotlinx.coroutines/tree/master/integration/kotlinx-coroutines-play-services implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3'
Extrait
import kotlinx.coroutines.tasks.await // ... textView.text = simpleTask.await() }
ListenableFuture Guava
Pour utiliser ListenableFuture Guava avec Task, ajoutez la dépendance suivante à votre projet, puis utilisez l'extrait de code pour effectuer la conversion à partir d'une Task.
Gradle (niveau module build.gradle, généralement app/build.gradle)
implementation "androidx.concurrent:concurrent-futures:1.2.0"
Extrait
import com.google.common.util.concurrent.ListenableFuture // ... /** Convert Task to ListenableFuture. */ fun <T> taskToListenableFuture(task: Task<T>): ListenableFuture<T> { return CallbackToFutureAdapter.getFuture { completer -> task.addOnCompleteListener { completedTask -> if (completedTask.isCanceled) { completer.setCancelled() } else if (completedTask.isSuccessful) { completer.set(completedTask.result) } else { val e = completedTask.exception if (e != null) { completer.setException(e) } else { throw IllegalStateException() } } } } } // ... this.listenableFuture = taskToListenableFuture(simpleTask) this.listenableFuture?.addListener( Runnable { textView.text = listenableFuture?.get() }, ContextCompat.getMainExecutor(this) )
RxJava2 Observable
Ajoutez la dépendance suivante, en plus de la bibliothèque asynchrone relative de votre choix, à votre projet, puis utilisez l'extrait de code pour effectuer la conversion à partir d'une Task.
Gradle (niveau module build.gradle, généralement app/build.gradle)
// Source: https://github.com/ashdavies/rx-tasks implementation 'io.ashdavies.rx.rxtasks:rx-tasks:2.2.0'
Extrait
import io.ashdavies.rx.rxtasks.toSingle import java.util.concurrent.TimeUnit // ... simpleTask.toSingle(this).subscribe { result -> textView.text = result }