La API de Task
es la forma estándar de controlar las operaciones asíncronas en los Servicios de Google Play. Proporciona una forma potente y flexible de administrar las llamadas asíncronas, lo que reemplaza el patrón PendingResult
anterior. Con Task
, puedes encadenar varias llamadas, controlar flujos complejos y escribir controladores claros de éxito y error.
Cómo controlar los resultados de las tareas
Muchas APIs en los Servicios de Google Play y Firebase devuelven un objeto Task
para representar operaciones asíncronas. Por ejemplo, FirebaseAuth.signInAnonymously()
devuelve un Task<AuthResult>
que representa el resultado de la operación de acceso. El Task<AuthResult>
indica que, cuando la tarea se complete correctamente, devolverá un objeto AuthResult
.
Puedes controlar el resultado de un Task
adjuntando objetos de escucha que respondan a la finalización correcta, la falla o ambas:
Task<AuthResult> task = FirebaseAuth.getInstance().signInAnonymously();
Para controlar la finalización correcta de una tarea, adjunta un OnSuccessListener
:
task.addOnSuccessListener(new OnSuccessListener<AuthResult>() { @Override public void onSuccess(AuthResult authResult) { // Task completed successfully // ... } });
Para controlar una tarea fallida, adjunta un OnFailureListener
:
task.addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
Para controlar tanto el éxito como el error en el mismo objeto de escucha, adjunta 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(); } } });
Administra subprocesos
De forma predeterminada, los objetos de escucha adjuntos a un Task
se ejecutan en el subproceso principal (IU) de la aplicación. Esto significa que debes evitar realizar operaciones de larga duración en los objetos de escucha. Si necesitas realizar una operación de larga duración, puedes especificar un Executor
que se use para programar objetos de escucha en un subproceso en segundo plano.
// 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) { // ... } });
Usa objetos de escucha con alcance de actividad
Cuando necesitas controlar los resultados de las tareas dentro de un Activity
, es importante administrar el ciclo de vida de los objetos de escucha para evitar que se los llame cuando el Activity
ya no esté visible. Para ello, puedes usar objetos de escucha con alcance de actividad. Estos objetos de escucha se quitan automáticamente cuando se llama al método onStop
de tu Activity
, de modo que no se ejecuten después de que se detenga el Activity
.
Activity activity = MainActivity.this; task.addOnCompleteListener(activity, new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { // ... } });
Encadena tareas
Si usas un conjunto de APIs que devuelven objetos Task
en una función compleja, puedes encadenarlas con continuaciones. Esto te ayuda a evitar las devoluciones de llamada anidadas y consolida el control de errores para varias tareas encadenadas.
Por ejemplo, considera una situación en la que tienes un método doSomething
que devuelve un Task<String>
, pero requiere un AuthResult
como parámetro.
Puedes obtener este AuthResult
de forma asíncrona desde otro Task
:
public Task<String> doSomething(AuthResult authResult) { // ... }
Con el método Task.continueWithTask
, puedes encadenar estas dos tareas:
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. // ... } });
Cómo bloquear una tarea
Si tu programa ya se está ejecutando en un subproceso en segundo plano, puedes bloquear el subproceso actual y esperar a que se complete la tarea, en lugar de usar una devolución de llamada:
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. // ... }
También puedes especificar un tiempo de espera cuando bloqueas una tarea para evitar que tu aplicación se quede atascada indefinidamente si la tarea tarda demasiado en completarse:
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. // ... }
Interoperabilidad
Task
está diseñado para funcionar bien con otros patrones comunes de programación asíncrona de Android. Se puede convertir a otros tipos primitivos, como ListenableFuture
y corrutinas de Kotlin, que recomienda AndroidX, y viceversa, lo que te permite usar el enfoque que mejor se adapte a tus necesidades.
A continuación, se muestra un ejemplo con el uso de Task
:
// ... simpleTask.addOnCompleteListener(this) { completedTask -> textView.text = completedTask.result }
Corrutina de Kotlin
Para usar corrutinas de Kotlin con Task
, agrega la siguiente dependencia a tu proyecto y, luego, usa el fragmento de código para convertir desde un Task
.
Gradle (build.gradle
a nivel del módulo, generalmente 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'
Fragmento
import kotlinx.coroutines.tasks.await // ... textView.text = simpleTask.await() }
Guava ListenableFuture
Para usar Guava ListenableFuture
con Task
, agrega la siguiente dependencia a tu proyecto y, luego, usa el fragmento de código para convertir desde un Task
.
Gradle (build.gradle
a nivel del módulo, generalmente app/build.gradle
)
implementation "androidx.concurrent:concurrent-futures:1.2.0"
Fragmento
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
Agrega la siguiente dependencia, además de la biblioteca asíncrona relativa de tu elección, a tu proyecto y, luego, usa el fragmento de código para convertir desde un Task
.
Gradle (build.gradle
a nivel del módulo, generalmente app/build.gradle
)
// Source: https://github.com/ashdavies/rx-tasks implementation 'io.ashdavies.rx.rxtasks:rx-tasks:2.2.0'
Fragmento
import io.ashdavies.rx.rxtasks.toSingle import java.util.concurrent.TimeUnit // ... simpleTask.toSingle(this).subscribe { result -> textView.text = result }