¡Hola! Acabas de unirte a nosotros para la segunda parte de una serie de blogs sobre la API Play Services Task, usada por algunas características de Firebase para responder al trabajo realizado por sus API de forma asincrónica.
La última vez, nos familiarizamos con una Task usada por la API Almacenamiento de Firebase, y aprendimos un poco sobre cómo funcionan en general las Tasks. Si aún nos has visto la publicación, ahora es el momento para volver a la publicación antes de continuar. En esta publicación, daremos un vistazo a algunos de los matices sobre el comportamiento entre las diferentes variaciones para agregar a un receptor a una Task para capturar su resultado.
La última vez vimos cómo un receptor se adiciona a una Task como esta, usando la API de Almacenamiento de Firebase:
Task task = forestRef.getMetadata();
task.addOnSuccessListener(new OnSuccessListener() {
@Override
public void onSuccess(StorageMetadata storageMetadata) {
// Metadata now contains the metadata for 'images/forest.jpg'
}
});
En este código, addOnSuccessListener() se llama con un solo argumento, que es un receptor anónimo que se invoca una vez completado. Con esta forma, el receptor se invoca en el hilo principal, lo que significa que podemos hacer cosas que solo se pueden hacer en el hilo principal, como actualizar una Vista. Es genial que la Task ayude a poner devuelta el trabajo en el hilo principal, sin embargo existe una advertencia. Si un receptor se registra así en una Actividad y no es removido antes de que la Actividad se destruya, existe la posibilidad de una fuga de Actividad.
¡Pero yo no quiero Actividades con fugas!
De acuerdo, ¡nadie quiere Actividades con fugas! Entonces, ¿qué es una fuga de Actividad? Brevemente, una fuga de Actividad ocurre cuando un objeto mantiene una referencia de objeto de una Actividad más allá de su método de ciclo de vida de onDestroy(), reteniendo la Actividad más allá del tiempo útil. Cuando se llama onDestroy() en un Actividad, puedes estar seguro de que la instancia nunca se usará de nuevo por Android. Después de onDestroy(), queremos que el recolector de basura de tiempo de ejecución de Android limpie la Actividad, todas sus Vistas y otros objetos muertos. ¡Pero el recolector de basura no limpiará la Actividad y todas sus vistas si otro objeto mantiene una referencia fuerte hacia ésta!
Las fugas de Actividades pueden ser un problema con las Tasks, a menos de las evites. En el código de arriba (si estuviera dentro de una Actividad), el receptor anónimo mantiene una referencia fuerte e implícita hacia la Actividad contenida. Así es como el código dentro del receptor puede hacer cambios a la Actividad y a sus miembros. El compilador resuelve silenciosamente los detalles de esto. Una fuga de Actividad ocurre cuando una Tarea en progreso se mantiene en el receptor previo a onDestroy() de la Actividad. En verdad no tenemos ninguna garantía sobre cuánto tiempo tomará la Task, por lo tanto el receptor se puede mantener indefinidamente. Dado que el receptor mantiene implícitamente una referencia de la Actividad, la Actividad se puede fugar si la Task no se completa antes de onDestroy(). Si muchas de las Tasks que mantienen referencias a Actividades crean una copia de seguridad a medida que pasa el tiempo (por ejemplo, debido a una caída de la red), esto puede causar que tu aplicación se quede sin memoria y falle. Tú. Puedes obtener más información en
este video.
Devuelta a la Task a mano
Si estás interesado en Actividades con fugas (¡espero que lo estés!), debes saber que la versión simple del argumento addOnSuccessListener() tiene la advertencia de una posible fuga de la Actividad si no eres cuidadoso al remover el receptor en el momento adecuado.
Existe una mejor manera de hacer esto automáticamente con la API Task. Tomemos como ejemplo el código de arriba en una Actividad y modifiquemos ligeramente su llamada addOnSuccessListener():
Task task = forestRef.getMetadata();
task.addOnSuccessListener(this, new OnSuccessListener() {
@Override
public void onSuccess(StorageMetadata storageMetadata) {
// Metadata now contains the metadata for 'images/forest.jpg'
}
});
Esto es exactamente como la versión anterior, excepto que ahora hay dos argumentos para
addOnSuccessListener(). El primer argumento es `this`, de tal modo que cuando este código esté en una Actividad, hará que `this` se refiera a esa instancia de Actividad encerrada. Cuando el primer parámetro es una referencia de Actividad, esto le dice a la API Task que este receptor debe ser "enfocado" al ciclo de vida de la Actividad. Esto significa que el receptor será
automáticamente removido de la tarea cuando la Actividad pase a través de su método del ciclo de vida onStop() Esto es muy útil porque no necesitas recordar que debes hacerlo para todas las Tasks que puedas crear mientras un Actividad está activa. Sin embargo, necesitas estar seguro de que onStop() es el lugar correcto para detener la recepción. onStop() se activa cuando una Actividad no es visible, lo cual comúnmente está bien. Sin embargo, si quieres realizar un seguimiento de la Task en la siguiente Actividad (como cuando un cambio de orientación cambia la Actividad actual por una nueva), necesitarás crear una manera de retener ese conocimiento en la siguiente Actividad. Para más información sobre esto, lee
guardar el estado de la Actividad.
Omitir el tráfico en Main St.
Existen algunos casos en donde simplemente no quieres reaccionar para completar la Task en el hilo principal. Tal vez quieres trabajar en bloque en tu receptor, o quieres ser capaz de manejar diferentes resultados de Tasks simultáneamente (en lugar de secuencialmente). Por lo tanto, te gustaría evitar el hilo principal en conjunto y por el contrario procesar el resultado en otro hilo que tú controles. Existe otra forma de addOnSuccessListener() que puede ayudar a tu aplicación con tus hilos. Así es como se ve (con el receptor abreviado):
Executor executor = ...; // obtain some Executor instance
Task task = RemoteConfig.getInstance().fetch();
task.addOnSuccessListener(executor, new OnSuccessListener() { ... });
Acá estamos llamando a la API
Configuración remota de Firebase para obtener nuevos valores de configuración. Luego, la Task que muestra fetch() obtiene una llamada a
addOnSuccessListener() y recibe un
Ejecutor como primer argumento. Este ejecutor determina el hilo que se usará para invocar al receptor. Para aquellos que no están familiarizados con el Ejecutor, es una utilidad núcleo de Java que acepta unidades de trabajo y las encamina para ser ejecutadas en hilos bajo su control. Esto puede ser un solo hilo, o un conjunto de hilos, todos esperando hacer el trabajo. Para las aplicaciones no es muy común usar el Ejecutor directamente, y puede ser visto como una técnica avanzada para administrar el comportamiento del hilo de tu aplicación. Lo que debes recordar es el hecho de que no necesitas recibir a tus receptores en el hilo principal si no te conviene. Si decides usar un Ejecutor, asegúrate de administrarlos como individuos compartidos, o asegúrate de que sus ciclos de vida también estén administrados para que sus hilos no se fuguen.
Otra cosa interesante para tener en cuenta sobre este código es el hecho de que la Task que muestra la Configuración remota es parametrizada por Void. Esta es la manera en que una Task puede decir que no necesita generar un objeto indirecto. Void es el tipo de datos en Java que indica la ausencia de datos tipo. La API de Configuración remota simplemente usa la Task como un indicador de la terminación de la tarea, y se espera que el emisor use la API Configuración remota para descubrir los nuevos valores que fueron obtenidos.
¡Elige sabiamente!
Existen tres variedades de addOnSuccessListener():
Task addOnSuccessListener(OnCompleteListener listener)
Task addOnSuccessListener(Activity activity, OnSuccessListener listener)
Task addOnSuccessListener(Executor executor, OnSuccessListener listener)
Además de eso, tenemos las mismas variedades para receptores de fallo y terminación:
Task addOnFailureListener(OnFailureListener listener)
Task addOnFailureListener(Activity activity, OnFailureListener listener)
Task addOnFailureListener(Executor executor, OnFailureListener listener)
Task addOnCompleteListener(OnCompleteListener listener)
Task addOnCompleteListener(Activity activity, OnCompleteListener listener)
Task addOnCompleteListener(Executor executor, OnCompleteListener listener)
Espera, ¿qué es un OnCompleteListener?
No hay nada especial sobre
OnCompleteListener. Simplemente es un receptor que es capaz de recibir el éxito y el fallo, y debes revisar el estado dentro de la llamada de retorno. El archivo de metadatos de llamada de retorno de la última publicación podría ser reescrito así, en lugar de darle a la aplicación receptores separados de éxito o fallo:
Task task = forestRef.getMetadata();
task.addOnCompleteListener(new OnCompleteListener() {
@Override
public void onComplete (Task task) {
if (task.isSuccessful()) {
StorageMetadata meta = task.getResult();
// Do something with metadata...
} else {
Exception e = task.getException();
// Handle the failure...
}
}
});
Con OnCompleteListener, puedes tener un receptor independiente que maneja el éxito y el fallo, y puedes descubrir algunos de los dos llamando a isSuccessful() en el objeto de la Task que se pasa a la llamada de retorno. Esto es funcionalmente equivalente a registrar en ambos un OnSuccessListener y un OnFailureListener. El estilo que escojas es principalmente una cuestión de gustos.
Para concluir (la parte 2 de esta serie)
Has aprendido que las Tasks pueden recibir tres tipos de receptores: éxito, fracaso, y terminación total. Para cada uno de estos tipos de receptores, existen tres formas de recibir la llamada de retorno: en el hilo principal, en el hilo principal transferido a una Actividad, y en un hilo determinado por un Ejecutor. Aquí tienes algunas alternativas, y depende de ti escoger la opción que más te convenga. Sin embargo, estas no son las únicas maneras de manejar los resultados de tus Tasks. Puedes crear tuberías de resultados de Task para procesamientos más complejos. Para más detalles, acompáñame en la siguiente sesión, donde podrás continuar el viaje para convertirte en un Taskmaster de Firebase.
Si tienes alguna pregunta, puedes usar Twitter con el hashtag #AskFirebase o el
Grupo de Google firebase-talk. También tenemos un canal dedicado a Firebase Slack. Y puedes seguirme en
@CodingDoug en Twitter.
Lee la
parte 1 de esta serie de blogs.