La API de Task
es la forma estándar de controlar operaciones asíncronas en los Servicios de Google Play. Proporciona una forma potente y flexible de administrar llamadas asíncronas, lo que reemplaza el patrón PendingResult
anterior. Con Task
, puedes encadenar
varias llamadas, controlar flujos complejos y escribir controladores de éxito y error
claros.
Controla los resultados de las tareas
Muchas APIs de los Servicios de Google Play y Firebase muestran un objeto Task
para representar operaciones asíncronas. Por ejemplo, FirebaseAuth.signInAnonymously()
muestra un Task<AuthResult>
que representa el resultado de la operación de acceso. El Task<AuthResult>
indica que, cuando la tarea se complete correctamente, mostrará un objeto AuthResult
.
Para controlar el resultado de una Task
, adjunta 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 que falló, adjunta un OnFailureListener
:
task.addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Task failed with an exception // ... } });
Para controlar el éxito y la falla 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 los subprocesos
De forma predeterminada, los objetos de escucha conectados 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 usa 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 centrados en la actividad
Cuando necesites controlar los resultados de las tareas dentro de un Activity
, es importante que administres el ciclo de vida de los objetos de escucha para evitar que se los llame cuando el Activity
ya no sea visible. Para ello, puedes usar objetos de escucha centrados en la actividad. Estos objetos de escucha se quitan automáticamente cuando se llama al método onStop
de tu Activity
para que no se ejecuten después de que se detenga Activity
.
Activity activity = MainActivity.this; task.addOnCompleteListener(activity, new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { // ... } });
Cómo encadenar tareas
Si usas un conjunto de APIs que muestran objetos Task
en una función compleja, puedes encadenarlos con continuaciones. Esto te ayuda a evitar las devoluciones de llamada anidadas en profundidad y consolida la administración de errores para varias tareas encadenadas.
Por ejemplo, considera una situación en la que tienes un método doSomething
que muestra 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 la aplicación se bloquee 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 de programación asíncronos comunes de Android. Se puede convertir a otras primitivas, como ListenableFuture
y corrutinas de Kotlin, que recomienda AndroidX, lo que te permite usar el enfoque que mejor se adapte a tus necesidades.
A continuación, se muestra un ejemplo con un 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 que elijas, a tu proyecto y, luego, usa el fragmento de código para convertir desde un Task
.
Gradle (build.gradle
a nivel del módulo, por lo general, 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 }