Sei sulla pagina 1di 10

CURSO DE DESARROLLO DE APLICACIONES ANDROID

Tema 11

Procesos e Hilos
TEMA 11. PROCESOS E HILOS

Introducción

Como ya se ha comentado en temas anteriores, por defecto cada aplicación Android ejecuta
todos sus componentes en un único proceso propio. Por otro lado, para evitar que la interfaz
de usuario se ralentice o incluso se bloquee, es necesario realizar las tareas pesadas en nuevos
hilos o threads.

Cuando se inicia el primer componente de una aplicación (en general una actividad), el sistema
inicia un nuevo proceso Linux con un único hilo de ejecución. A no ser que se especifique lo
contrario, el resto de componentes que se inicien a continuación también lo harán sobre el
mismo proceso y sobre dicho hilo, denominado main thread, por ser el hilo principal de la
aplicación. No obstante, existe la posibilidad de gestionar los componentes de la aplicación
para que se ejecuten en procesos separados, y además se pueden crear múltiples hilos en cada
proceso.

Procesos

La mayoría de las aplicaciones utilizarán un único proceso para todos sus componentes. En
caso de querer separar ciertos componentes de la aplicación en diferentes procesos, se tendrá
que editar el manifiesto (AndroidManifest.xml) de la aplicación.

Todos los elementos que declaran los diferentes componentes de una aplicación en el
manifiesto (<activity>, <service>, <receiver>, <provider>) soportan el atributo
android:process, que permite especificar el proceso en el que el componente será
ejecutado, de forma que se podrá compartir el mismo proceso entre distintos componentes.
Además, se podrá compartir el mismo proceso entre componentes de diferentes aplicaciones
(en caso de que compartan el mismo ID de usuario Linux y estén firmadas con los mismos
certificados). Para asignar el mismo proceso a todos los componentes de una aplicación, se
podrá declarar el atributo android:process en el elemento <application>.

Debido a que Android gestiona automáticamente el ciclo de vida de los procesos, en casos de
que la memoria disponible sea escasa y otro proceso con mayor prioridad deba realizar tareas,
el sistema podrá finalizar procesos menos prioritarios, destruyendo por lo tanto los
componentes de la aplicación que estuvieran ejecutándose en dichos procesos. La decisión de
qué proceso se finalizará se realiza en función de la importancia relativa de dicho proceso para
el usuario, la cual se determina en función del estado de los componentes que estén activos en
cada proceso.

CURSO DE DESARROLLO DE APLICACIONES ANDROID 2


TEMA 11. PROCESOS E HILOS

Ciclo de vida de los procesos

El sistema Android intentará mantener el proceso de cada aplicación tanto tiempo como sea
posible, hasta que llegue el momento de eliminar procesos antiguos para liberar memoria que
necesitan nuevos procesos o procesos que ya se están ejecutando y que son más importantes.
Para decidir qué proceso ha de mantenerse y cuál puede ser finalizado, el sistema ordena los
mismos en una jerarquía de importancia, que se basa en los componentes que se están
ejecutando en dicho proceso, así como en el estado de los mismos. Los procesos que tengan
una importancia menor serán los primeros que se finalicen, y se seguirá eliminando procesos
en la jerarquía (de menor a mayor importancia) hasta que se satisfagan las necesidades de
recursos del sistema exigidas en cada momento.

Existen cinco niveles en la jerarquía de importancia:

1. Procesos en primer plano (más importantes). Son aquellos procesos requeridos para
completar las operaciones que realiza el usuario. Estos procesos se identifican porque:

• albergan una actividad con la cual el usuario está interactuando, por lo que se
ha invocado su método onResume(), o
• albergan un servicio unido a una actividad con la cual está interactuando el
usuario, o
• albergan un servicio que se está ejecutando en primer plano, al haber
invocado a su método starForeground(), o
• albergan un servicio que está ejecutando uno de sus métodos callback
(onCreate(), onStart(), u onDestroy())
• albergan un receptor de Broadcast que está ejecutando su método
onReceive().

En general existirán pocos procesos en primer plano, por lo que serán finalizados
únicamente en casos extremos en los cuales la memoria disponible sea tan baja que
sea físicamente imposible mantenerlos activos. En estos casos, el dispositivo
comenzará a utilizar la memoria de paginación 1 por lo que será necesario eliminar
algún proceso en primer plano para mantener la interfaz de usuario fluida.

2. Procesos visibles. Son aquellos procesos que, pese a no tener ningún componente en
primer plano, afectan a lo que el usuario ve en la pantalla. Por lo tanto, serán
actividades cuyo método onPause() ha sido invocado (están en segundo plano porque
están mostrando, por ejemplo, un diálogo) o bien servicios unidos a actividades que
están en primer plano y que requieren que el servicio esté activo (ya que están
unidos).

1
La información que gestione el proceso deberá escribirse en la tarjeta de memoria en vez de en la memoria RAM,
disminuyendo así la velocidad de lectura y escritura.

CURSO DE DESARROLLO DE APLICACIONES ANDROID 3


TEMA 11. PROCESOS E HILOS

3. Procesos servicio. Son procesos que están ejecutando un servicio invocado a través
del método de contexto startService() y que no cumplen ninguno de los casos
anteriores. Aunque estos servicios no estén ligados a ningún componente en primer
plano, sí que están realizando tareas en las que el usuario, en general, está poniendo
su atención (como, por ejemplo, la reproducción de música en segundo plano, o la
descarga de algún archivo), por lo que el sistema los mantendrá salvo que no haya
suficiente memoria como para que se ejecuten junto con los procesos con mayor
preferencia.

4. Procesos en background. Aquellos procesos que alberguen una actividad que ya no


sea visible al usuario (por lo que se habrá invocado a su método onStop() 2) podrán
ser eliminados por el sistema en cualquier momento. El sistema eliminará primero
aquellos procesos en background más antiguos, e intentará mantener los más
recientes.

5. Procesos vacíos. Son procesos que no albergan ningún componente pero que se
mantienen vivos por cuestiones de eficiencia, ya que se agiliza la inicialización de
nuevos componentes sobre dicho proceso al estar el proceso ya creado.

Existen otras condiciones que rigen la importancia de los procesos activos. Si un proceso
alberga un servicio y una actividad visible, dicho proceso siempre se categorizará como un
proceso visible. Además, la importancia de un proceso puede ser incrementada si un segundo
proceso más importante depende de él, de forma que el primero esté realizando tareas para el
segundo.

En cuanto a las actividades que realicen tareas de larga duración las cuales permanezcan
activas incluso cuando la actividad pase a background, será conveniente que inicien servicios
para dichas tareas en vez de simples hilos, puesto que se garantizará que, al menos, las tareas
tendrán prioridad de proceso servicio.

2
Por eso es importante implementar los métodos del ciclo de vida de las actividades correctamente, y guardar el
estado de las mismas. De esta forma, aunque dicha actividad pase a segundo plano y sea eliminada, cuando el
usuario navegue a ella de nuevo, recordará su estado anterior.

CURSO DE DESARROLLO DE APLICACIONES ANDROID 4


TEMA 11. PROCESOS E HILOS

Hilos

Cada aplicación es lanzada en un hilo de ejecución principal llamado main. Este hilo está
encargado de enviar los eventos a la interfaz de usuario 3, incluyendo los eventos de
renderizado (encargados de dibujar los diferentes elementos de la pantalla). Además, es el hilo
sobre el cual la aplicación interacciona con los componentes visuales de Android (widgets y
views). Todas las instancias de un mismo componente son lanzadas sobre este hilo por lo que
las llamadas a estos componentes son enviadas sobre dicho hilo. De esta forma, los métodos
callback también se ejecutan siempre en el hilo main.

Si una aplicación realiza tareas de larga duración en el hilo principal, debido al modelo “single
thread” que implementa Android, la interfaz de usuario se bloqueará y no se podrán enviar
eventos (ni siquiera los de renderizado). Si dicho bloqueo dura más de 5 segundos, el sistema
mostrará el mensaje “La aplicación no responde” (mensaje ANR: Application Not Responding),
lo cual es siempre indeseable.

Por otro lado, los widgets y las views de Android no son “thread-safe”, por lo que no se deberá
nunca manipular la interfaz de usuario desde un hilo tipo worker.

Resumiendo, nunca se deberá bloquear el hilo main y nunca se accederá a un componente


visual desde otro hilo que no sea el main.

Hilos Worker

Tal y como se ha mencionado, las tareas de larga duración deberán realizarse en hilos en
segundo plano o workers.

Debido a que no se puede acceder a un componente visual desde un hilo que no sea el hilo
principal (hilo UI), no es posible implementar un hilo worker para que realice una tarea de
larga duración así:

new Thread(new Runnable() {


public void run() {
String resultado = tareaLargaDuracion(5000);
// No se puede acceder desde este hilo al EditText (UI) ya que
// el sistema lanzará la excepción CalledFromWrongThreadException
// Es decir, no se permite interactuar con un componente del
// hilo principal desde otro hilo.
((EditText) findViewById(R.id.editText1)).setText(resultado);
}
}).start();

3
De ahí que también se llame hilo UI.

CURSO DE DESARROLLO DE APLICACIONES ANDROID 5


TEMA 11. PROCESOS E HILOS

Para posibilitar el acceso desde otros hilos worker al hilo main, Android provee tres métodos:

• Activity.runOnUiThread(Runnable)
• View.post(Runnable)
• View.postDelayed(Runnable, long)

Utilizando estos métodos, el código anterior es thread-safe, ya que la tarea de larga duración
se realiza en un hilo separado, y la actualización del TextView con el resultado de dicha tarea,
se realiza en el hilo principal, gracias al método post().

TextView resultadoTextView;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
resultadoTextView = ((TextView) findViewById(R.id.textViewResultado));
}

public void metodoQueNoBloquea(View v) {


new Thread(new Runnable() {
public void run() {
final String resultado = tareaLargaDuracion(5000);
// Gracias al método post de la vista a actualizar, sí que
// se puede acceder desde un worker al hilo principal.
// Además, la UI no se bloquea
resultadoTextView.post(new Runnable() {
@Override
public void run() {
resultadoTextView.setText(resultado);
}
});
}
}).start();
}

No obstante, estos métodos acaban generando un código complicado y difícil de mantener,


por lo que, para gestionar interacciones más complejas con workers desde el hilo principal de
la aplicación, es conveniente utilizar un Handler en el hilo worker que encapsule los distintos
mensajes enviados desde el hilo principal (destinados a gestionar las operaciones que realiza
dicho worker).

Uso de AsyncTask

Para simplificar el uso de los hilos worker, Android proporciona otra solución que consiste en
extender la clase AsyncTask. Esta clase permite realizar tareas asíncronas en la interfaz de

CURSO DE DESARROLLO DE APLICACIONES ANDROID 6


TEMA 11. PROCESOS E HILOS

usuario. Para ello, utiliza un hilo worker que se encarga de realizar las tareas de larga duración
cuyos resultados publica en el hilo principal, sin que sea necesario gestionar directamente ni
hilos ni handlers.

Para utilizar esta solución, se deberá extender la clase AsyncTask y al menos implementar el
método doInBackground(), que se ejecuta en un pool de hilos en segundo plano. En general,
también se implementará el método onPostExecute() que se ejecuta en el hilo main y que
recibe el resultado del método doInBackground(). Como este segundo método se ejecuta en
el hilo main, se puede actualizar de forma segura la interfaz de usuario.

Esta clase ayuda a que el código creado sea legible y fácil de mantener, ya que separa el código
correspondiente a la tarea de larga duración (método doInBackground()) del código que
actualiza la interfaz de usuario (método onPostExecute()).

Para ejecutar la tarea asíncrona, bastará invocar a su método execute(Params…) desde el hilo
main (desde la actividad o servicio).

Al declarar una tarea asíncrona, se podrán especificar los tres tipos genéricos de parámetros
que se utilizarán en los diferentes métodos que se implementarán. Además, se puede ampliar
el control de la tarea asíncrona implementando cuatro métodos: onPreExecute(),
doInBackground(), onProgressUpdate() y onPostExecute().

Los tres tipos genéricos son:

• Params: tipo de parámetros enviados a la tarea en ejecución.


• Progress: tipo de unidades para publicar el progreso de la tarea que se está
ejecutando en background.
• Result: tipo de resultado obtenido de la tarea en background.

Si no se desea declarar uno de los tipos genéricos, se utilizará Void. Por ejemplo,

class OperacionLargaTask extends AsyncTask<Long, Void, String> {…}

En la declaración anterior, se definen los parámetros tipo de los métodos, al usar la notación
de Java de tipos genéricos 4 en AsyncTask<…>. En dicha declaración se establece que el
método doInBackground() recibirá parámetros de tipo Long, que no se utilizará el método
onProgressUpdate() ya que se define su tipo como Void, y se establece, por último un tipo
String como el tipo de resultado obtenido de la tarea en background.

Existe una característica adicional de los tipos genéricos Params y Progress: los métodos de la
tarea asíncrona que los utilizan pueden recibir múltiples parámetros del tipo que definan los

4
Se puede consultar más información en este enlace:
http://docs.oracle.com/javase/tutorial/java/generics/types.html

CURSO DE DESARROLLO DE APLICACIONES ANDROID 7


TEMA 11. PROCESOS E HILOS

tipos genéricos 5. Por ejemplo, según se ha definido OperacionLargaTask previamente, el


método doInBackground(Long… miliseconds) podría ser invocado a través del método
execute(Long… miliseconds), desde el hilo main, con múltiples parámetros Long:

new OperacionLargaTask().execute(1000, 2000, 500);

Cuando una tarea asíncrona se inicia, su ciclo de vida evoluciona a través de los siguientes
métodos:

1. onPreExecute(). Método invocado en el hilo main inmediatamente después de que la


tarea sea iniciada. Es utilizado normalmente para inicializar la tarea, por ejemplo
mostrando una barra de progreso en la interfaz de usuario.
2. doInBackground(Params…). Método invocado en el hilo background inmediatamente
después de que onPreExecute() finalice su ejecución. Es donde se realizarán las
tareas de larga duración. Recibe los N parámetros de tipo genérico Params que sean
pasados al método execute(Params…). Devolverá como resultado un objeto cuyo tipo
genérico será Results y que será recibido por el método onPostExecute(Results).
Además, este método puede invocar al método publishProgress(Progress…) para
publicar una o más unidades de progreso que serán mostradas en el hilo main.
3. onProgressUpdate(Progress…). Método invocado en el hilo main cada vez que se
invoque a publishProgress(Progress…) y que será utilizado para mostrar cualquier
tipo de elemento de progreso visual en la interfaz de usuario mientras la tarea en
background se siga ejecutando. Por ejemplo, se puede utilizar para mostrar una barra
de progreso o para actualizar mensajes tipo log en una TextView.
4. onPostExecute(Result). Método invocado en el hilo main al finalizar la tarea en
background. Recibe como parámetro el resultado que devuelva el método
doInBackground(Params…) y que será un objeto de tipo genérico Results.

Las tareas asíncronas pueden ser canceladas en cualquier momento invocando el método
cancel(boolean). Al invocar a cancel(boolean), el método callback isCancelled()
devolverá true, por lo que el método doInBackground(Params…) deberá invocar
frecuentemente a este método para finalizar cuanto antes la tarea iniciada una vez se haya
solicitado su cancelación. Además, en caso de ser cancelada la tarea, una vez finalice el
método doInBackground(Params…), se invocará al método onCancelled(Result) en vez de
a onPostExecute(Result).

5
Esto se indica con puntos suspensivos a continuación del tipo de parámetro, en la declaración del método.

CURSO DE DESARROLLO DE APLICACIONES ANDROID 8


TEMA 11. PROCESOS E HILOS

Existen cuatro importantes reglas que se deberán tener en cuenta:

• La tarea asíncrona deberá ser creada en el hilo main.


• El método execute(Params…) deberá ser invocado en el hilo main.
• No se deberá invocar manualmente a ninguno de los métodos onPreExecute(),
onPostExecute(Results), doInBackground(Params…),
onProgressUpdate(Progress…).
• La tarea solo se puede ejecutar simultáneamente una vez. Se lanzará una excepción si
se intenta volver a lanzar la tarea mientras esta se encuentra en ejecución.

Por último, se ha de mencionar que AsyncTask garantiza que las llamadas a sus métodos
callback están sincronizadas, de forma que las siguientes operaciones son seguras (sin
necesidad de establecerlas explícitamente como synchronized):

• Se pueden establecer valores de atributos miembro de la clase tanto en el constructor


como dentro del método onPreExecute() y referirlos en el método
doInBackground(Params…)
• Se pueden establecer valores de atributos miembro de la clase dentro del método
doInBackground(Params…) y referirlos en los métodos
onProgressUpdate(Progress…) y en el método onPostExecute(Result).

El próximo ejemplo ilustra el uso de AsyncTask.

public class OperacionLargaTask extends AsyncTask<Long, Integer, Long> {

// Se almacena una referencia al contexto para utilizar sus métodos


Activity procesosEHilosActivity;
ProgressDialog dialogoBarraProgreso;
// Se pasa de parámetro el Contexto de la aplicación para después
// poder utilizar sus métodos getString(), findViewById(id)...
public OperacionLargaTask(Activity procesosEHilosActivity) {
this.procesosEHilosActivity = procesosEHilosActivity;
((TextView) procesosEHilosActivity.
findViewById(R.id.textViewResultado)).setText("");
}

@Override
public void onPreExecute() {
// Se crea el diálogo, pasando el contexto
dialogoBarraProgreso = new ProgressDialog(procesosEHilosActivity);
// Se establece el estilo a barra horizontal (por defecto es el
// círculo)
dialogoBarraProgreso.setProgressStyle(
ProgressDialog.STYLE_HORIZONTAL);
// Si no se hace cancelable, la aplicación se bloqueará hasta
// que concluya la hipotética tarea

CURSO DE DESARROLLO DE APLICACIONES ANDROID 9


TEMA 11. PROCESOS E HILOS

dialogoBarraProgreso.setCancelable(true);
dialogoBarraProgreso.setIndeterminate(false);
dialogoBarraProgreso.setMessage(
procesosEHilosActivity.getString(R.string.progreso));
dialogoBarraProgreso.setTitle(
procesosEHilosActivity.getString(
R.string.tarea_asincrona_ejecucion));
dialogoBarraProgreso.show();
}

// Este método se ejecuta en un worker en background


@Override
protected Long doInBackground(Long... miliseconds) {
// Cada Long recibido equivale a una tareaLargaDuracion
int count = miliseconds.length;
long milisecondsTotal = 0;
for (int i = 0; i < count; i++) {
try {
publishProgress((int) ((i / (float) count) * 100));
synchronized (this) {
wait(miliseconds[i]);
}
milisecondsTotal += miliseconds[i];
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return milisecondsTotal;
}

// Método ejecutado en el hilo principal, por lo que se podrá


// acceder a la barra de progreso para actualizarla.
protected void onProgressUpdate(Integer... progress) {
dialogoBarraProgreso.setProgress(progress[0]);
}

// Método ejecutado en el hilo principal, por lo que se podrá


// acceder a la TextView, para publicar el resultado final.
protected void onPostExecute(Long milisegundosTotales) {
dialogoBarraProgreso.cancel();
showResults(milisegundosTotales);
}

private void showResults(Long milisegundosTotales) {

Resources res = procesosEHilosActivity.getResources();


String resultadoFinal =
String .format(res.getString(R.string.tiempo_total_tarea),
milisegundosTotales);

((TextView) procesosEHilosActivity.
findViewById(R.id.textViewResultado)).setText(resultadoFinal);
}
}

CURSO DE DESARROLLO DE APLICACIONES ANDROID 10

Potrebbero piacerti anche