Beruflich Dokumente
Kultur Dokumente
com/2014/12/android-asynctask-hilos/
Esta valoración mantendrá la sensación multitarea, evitará bloqueos y optimizará el hilo principal de tus
proyectos.
Aunque los hilos se benefician de las tecnologías multinucleos y multiprocesamiento, no significa que una
arquitectura simplista no se beneficie de la creación de hilos.
Cuando se construye una aplicación Android, todos los componentes y tareas son introducidos en el hilo principal
o hilo de UI (UI Thread). Hasta el momento hemos trabajado de esta forma, ya que las operaciones que hemos
realizado toman poco tiempo y no muestran problemas significativos de rendimiento visual en nuestros ejemplos.
Pero en tus proyectos reales no puedes pretender que todas las acciones que lleva a cabo tu aplicación sean
simples y concisas.
En ocasiones hay instrucciones que toman unos segundos en terminarse, esta es una de las mayores causas de
la terminación abrupta de las aplicaciones. Android está diseñado para ofrecerle al usuario la opción de terminar
aquellas aplicaciones que demoran más de 5 segundos en responder a los eventos del usuario.
Cuando esto pasa, podemos ver el famoso Diálogo ANR (Application not respond).
El camino correcto es renderizar la interfaz de la aplicación y al mismo tiempo ejecutar en segundo plano la otra
actividad para continuar con la armonía de la aplicación y evitar paradas inesperadas. Es aquí donde entran los
hilos, porque son los únicos que tienen la habilidad especial de permitir al programador generar concurrencia en
sus aplicaciones y la sensación de multitareas ante el usuario.
Se ha creado un nuevo hilo donde se ejecuta la tarea en el mismo intervalo de tiempo [t1, t2], pero esta vez el
tiempo de ejecución de la tercera tarea UI se extendió debido a que se realizarán pequeños incrementos entre al
segundo y tercera tarea. Aunque el tiempo empleado es el mismo, la respuesta ante el usuario simula una
aplicación limpia.
Recuerda que existen dos tipos de procesamientos de tareas, Concurrencia y Paralelismo. La concurrencia se
refiere a la existencia de múltiples tareas que se realizan simultáneamente compartiendo recursos de
procesamiento.
El paralelismo es la ejecución de varias tareas al tiempo en distintas unidades de procesamiento, por lo que es
mucho mas rápido que la concurrencia.
Ejemplo de Hilos: Ordenar Números con el Algoritmo Burbuja
Este tutorial no tendría gran valor si no encontrases un buen ejemplo explicativo. Por esta razón verás la
construcción de una aplicación llamada AsyncLab. Dicha aplicación tiene como fin mostrar algunos experimentos
de ejecución del Algoritmo de Ordenamiento Burbuja Simple, con 4 diferentes caminos y así evaluar la mejor
opción.
Descargar Código
Apóyanos con una señal en tu red social favorita y consigue el código completo.
Me gusta
Tweet
+1 Google
En cuanto a diseño, AsyncLab consiste en una actividad Main que despliega un menú construido a través de una
lista. Cada uno de los ítems representa un experimento aislado que muestra al usuario el comportamiento que se
produce. Precisamente ese comportamiento es mostrado en una segunda actividad hija llamada ABTest, donde
existe el botón sortButton para iniciar el ordenamiento de los números y cancelButton para cancelar la operación.
Veamos:
switch (position){
case 0:
// Experimento #1
break;
case 1:
// Experimento #2
break;
case 2:
// Experimento #3
break;
case 3:
// Experimento #4
break;
int aux;
Este enfoque es completamente válido y funcional hasta cierto punto. Recuerda que el algoritmo de
ordenamiento burbuja puede llegar a tener un orden de complejidad de O(N2) dependiendo de la dispersión de
los números.
Esto significa que si en algún momento la cantidad de números requiere una cantidad de segundos considerable,
el usuario debe esperar a que se termine la ejecución del algoritmo antes de poder interactuar de nuevo con la
aplicación.
Al ejecutar este experimento el botón “Ordenar” queda
seleccionado y la interfaz se congela. Si das taps
prolongadamente, para intentar que la aplicación responda,
obtendrás un diálogo ANR.
switch (position){
...
case 1:
execWithThread();
break;
...
}
new Thread(
new Runnable() {
@Override
public void run() {
bubbleSort(numbers);
Toast.makeText(
getBaseContext(),
"¡Números Ordenados!",
Toast.LENGTH_LONG).show();
}
}
).start();
Aunque el código anterior parece correcto, al iniciar este experimento obtendrás un error debido a que no es
aceptada la creación de instancias de la clase Toast dentro de un Hilo.
La documentación de Android recomienda no acceder directamente a los objetos del hilo de UI desde los hilos
creados manualmente. Advierten que pueden llegar a producirse anomalías debido a la ausencia de
sincronización.
Para evitar acceder directamente sobre los elementos de la UI, puedes usar algunos de los siguientes métodos:
Activity.runOnUiThread(), View.post(Runnable) y View.postDelayed(Runnable, long). Estos nos permitirán
presentar la información necesaria que se ha procesada en UI Thread.
runOnUiThread() es ideal para presentar resultados en el UI Thread cuando se ejecutan sentencias generales
asociadas a una actividad. El método post() se usa para relacionar un hilo al contenido de un View específico.
postDelayed() realiza exactamente lo mismo que post(), solo que retrasa n milisegundos el inicio del hilo.
El ejemplo anterior quedaría asegurado con la siguiente definición:
new Thread(
new Runnable() {
@Override
public void run() {
bubbleSort(numbers);
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(
getBaseContext(),
"¡Números Ordenados!",
Toast.LENGTH_LONG).show();
}
});
}
}
).start();
Hacemos exactamente lo mismo pero esta vez asignamos ejecutamos el método makeText() del de
runOnUiThread() junto a una nueva instancia Runnable que comunique las acciones al hilo principal.
Internamente esta operación entra a un proceso de cola de peticiones, donde el main Thread gestionará el
momento adecuado para iniciarla.
Por esta razón ha sido creada la interfaz AsyncTask, cuyo objetivo es liberar al programador del uso de hilos, la
sincronización entre ellos y la presentación de resultados en el hilo primario. Esta clase unifica los aspectos
relacionados que se realizarán en segundo plano y además gestiona de forma asíncrona la ejecución de las
tareas.
Para implementarla debes extender una nueva clase con las características de AsyncTask e implementar los
métodos correspondientes para la ejecución en segundo plano y la publicación de resultados en el UI Thread.
Adaptemos el ejemplo anterior a esta filosofía:
/*
Se hace visible el botón "Cancelar" y se desactiva
el botón "Ordenar"
*/
@Override
protected void onPreExecute() {
cancelButton.setVisibility(View.VISIBLE);
sortButton.setEnabled(false);
}
/*
Ejecución del ordenamiento y transmision de progreso
*/
@Override
protected Void doInBackground(Void... params) {
int aux;
/*
Se informa en progressLabel que se canceló la tarea y
se hace invisile el botón "Cancelar"
*/
@Override
protected void onCancelled() {
super.onCancelled();
progressLabel.setText("En la Espera");
cancelButton.setVisibility(View.INVISIBLE);
sortButton.setEnabled(true);
}
/*
Impresión del progreso en tiempo real
*/
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
progressLabel.setText(values[0] + "%");
}
/*
Se notifica que se completó el ordenamiento y se habilita
de nuevo el botón "Ordenar"
*/
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
progressLabel.setText("Completado");
sortButton.setEnabled(true);
}
SimpleTask se extiende de AsyncTask que además de ser abstracta es genérica. Las tres variables de entrada
que posee se refieren a los Parámetros, Unidades de Progreso y Resultados respectivamente.
La clase AsyncTask posee métodos te permitirán coordinar la ejecución de las tareas que deseas ubicar en
segundo plano. Estos métodos tienen los siguientes propósitos:
onPreExecute(): En este método van todas aquellas instrucciones que se ejecutarán antes de iniciar la
tarea en segundo plano. Normalmente es la inicialización de variables, objetos y la preparación de
componentes de la interfaz.
doInBackground(Parámetros…): Recibe los parámetros de entrada para ejecutar las instrucciones
especificas que irán en segundo plano, luego de que ha terminado onPreExecute(). Dentro de él podemos
invocar un método auxiliar llamado publishProgress(), el cual transmitirá unidades de progreso al hilo
principal. Estas unidades miden cuanto tiempo falta para terminar la tarea, de acuerdo a la velocidad y
prioridad que se está ejecutando.
onProgressUpdate(Progreso…): Este método se ejecuta en el hilo de UI luego de que publishProgress()
ha sido llamado. Su ejecución se prolongará lo necesario hasta que la tarea en segundo plano haya sido
terminada. Recibe las unidades de progreso, así que podemos usar algún View para mostrarlas al usuario
para que este sea consciente de la cantidad de tiempo que debe esperar.
onPostExecute(Resultados…): Aquí puedes publicar todos los resultados retornados por
doInBackground() hacia el hilo principal.
onCancelled(): Ejecuta las instrucciones que desees que se realicen al cancelar la tarea asíncrona.
Comprendiendo estas propiedades, la clase SimpleTask queda fácil de asimilar. Si observas onPreExecute(), se
comienza por hacer visible el botón cancelButton ( ya que solo aparece cuando la tarea asíncrona está en
ejecución) y luego se desactiva el botón sortButton para evitar la ejecución la actividad un sinnúmero de
ocasiones.
@Override
protected void onPreExecute() {
cancelButton.setVisibility(View.VISIBLE);
sortButton.setEnabled(false);
}
En el caso de doInBackground() se han puesto parámetros de tipo Void, ya que no se recibe valores de entrada y
solo se ejecutarán las instrucciones del ordenamiento burbuja. Si eres buen observador, el método
publishProgress() aparece al finalizar el primer bucle for. Esto con el fin de obtener una medida relativa en tiempo
real del progreso actual. Matemáticas básicas!
El tipo de dato para las unidades de progreso es Integer, así se obtienen números enteros que muestren un
porcentaje entre el intervalo [0, 100]. Dicha medida se muestra en un TextView llamado progressLabel.
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
progressLabel.setText(values[0] + "%");
}
En onPostExecute() se recibe un tipo Void como resultado, debido a que no se recibe retorno de
doInBackground(). Aquí aprovecharás para restablecer sortButton y cancelButton a su estado inicial. También
puedes avisar a través de progressLabel que se ha completado el trabajo.
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
progressLabel.setText("Completado");
cancelButton.setVisibility(View.INVISIBLE);
sortButton.setEnabled(true);
}
Ahora solo queda usar el método execute() para comenzar a ejecutar la tarea asíncrona en el método onClick():
Las Tareas Asíncronas deben ser creadas, cargadas y ejecutadas dentro del UI Thread para su correcto
funcionamiento.
Cancelar una tarea Asíncrona
@Override
protected void onCancelled() {
super.onCancelled();
progressLabel.setText("En la Espera");
cancelButton.setVisibility(View.INVISIBLE);
sortButton.setEnabled(true);
}
Complementariamente se coordina la detención del ordenamiento burbuja con un break si en algún momento la
actividad se ha cancelado:
if(!isCancelled())
publishProgress((int)(((i+1)/(float)(numbers.length-1))*100));
else break;
Lo que quiere decir que comienza un nuevo ciclo de vida para la actividad y por ende el hilo principal toma otro
rumbo.
Para solucionar este pequeño inconveniente se usará el método setRetainInstace(), el cual permite retener las
características de un fragmento ante un cambio de configuración. En consecuencia crearemos un fragmento
personalizado, donde se añada una instancia de la tarea asíncrona ProgressBarTask con el fin de que el
fragmento la proteja ante el cambio de configuración.
/*
Interfaz para la comunicación con la actividad ABTest.
*/
static interface TaskCallbacks {
void onPreExecute();
void onProgressUpdate(int progress);
void onCancelled();
void onPostExecute();
}
public HiddenFragment() {}
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
//Obtener la instancia de ABTest
mCallbacks = (TaskCallbacks) activity;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@Override
public void onDetach(){
super.onDetach();
mCallbacks = null;
}
@Override
protected void onPreExecute() {
if (mCallbacks != null) {
mCallbacks.onPreExecute();
}
}
@Override
protected Long doInBackground(Void... params) {
long t0 = System.currentTimeMillis();
int aux;
int numbers[] = ABTest.numbers;
return t0;
}
@Override
protected void onProgressUpdate(Integer... values) {
if (mCallbacks != null) {
mCallbacks.onProgressUpdate(values[0]);
}
}
@Override
protected void onPostExecute(Long aLong) {
if (mCallbacks != null) {
mCallbacks.onPostExecute();
}
}
Se le ha llamado HiddenFragment porque no posee interfaz de usuario( por eso su método onCreateView() no
está sobrescrito). Su estructura está formada por una interfaz llamada TaskCallbacks que le permitirá
comunicarse con la actividad ABTest, donde se han añadido 4 métodos de comunicación para sobrescribir
respectivamente los métodos callback de la clase ProgressBarTask.
Recuerda que para que la comunicación se dé, es necesario obtener la instancia de la actividad ABTest en el
método onAttach(). Una vez realizado esto, es posible comenzar a llamar todas las implementaciones de los
métodos de la interfaz que han sido definidos en la actividad. Es de vital importancia que ejecutes la tarea en el
método onCreate() del fragmento para arraigarla con setRetainInstance().
Al implementar la interfaz TaskCallbacks en ABTest es necesario sobrescribir los métodos de la siguiente forma:
@Override
public void onPreExecute() {
progressBar.setVisibility(View.VISIBLE);
cancelButton.setVisibility(View.VISIBLE);
sortButton.setEnabled(false);
}
@Override
public void onProgressUpdate(int progress) {
progressBar.setProgress(progress);
progressLabel.setText(progress+"%");
}
@Override
public void onCancelled() {
progressBar.setVisibility(View.INVISIBLE);
cancelButton.setVisibility(View.INVISIBLE);
progressLabel.setText("En la Espera");
sortButton.setEnabled(true);
}
@Override
public void onPostExecute() {
progressBar.setVisibility(View.INVISIBLE);
cancelButton.setVisibility(View.INVISIBLE);
progressLabel.setText("Completado");
sortButton.setEnabled(true);
Básicamente se oculta la ProgressBar al igual que se hacía con la progressLabel y luego configuras los botones,
para que aparezcan en la situación adecuada. También se usa el método setProgress() para actualizar el estado
de realización de la tarea.
A continuación crea el fragmento justo cuando sea presionado sortButton para que la tarea se inicie.
Finalmente asegura que los botones y la barra se restablezcan correctamente en el método onCreate() de
ABTest cuando surja la rotación de la pantalla:
fragment = (HiddenFragment)getFragmentManager().
findFragmentByTag(HIDDEN_FRAGMENT_TAG);
En castellano, el código anterior se traduce a “Si se ha elegido la opción 3 y el fragmento ya ha sido creado,
entonces compruebe si la tarea está en ejecución. Si es así, entonces mantener visible la barra de progreso,
mantener visible el botón de cancelar y además conservar el estado de inactividad de sortButton”.
Supongo que ya deduces que getStatus() obtiene el estado actual de la tarea y que RUNNING es un tipo
enumerado que representa el estado de ejecución.
Ahora prueba el experimento y verás como al cambiar a landscape, la ProgressBar y la tarea asíncrona siguen
intactas.
Conclusiones
No hace falta justificar ampliamente el porqué es mejor usar la clase AsyncTask para gestionar hilos y trabajos en
segundo plano. Este mecanismo permite optimizar tanto el flujo de tus aplicaciones como el orden de
codificación de las Actividades.
Así que la próxima vez que debas poner en ejecución una operación que requiere algunos segundos
considerables, no dudes en acudir a las tareas asíncronas.