Hilos en Android con Threads java

Toda aplicación de Android tiene al menos un hilo o thread de ejecución. Ese thread es el encargado de la pantalla que tienes ante ti y es imprescindible por la manera en la que Android gestiona los cambios en el interfaz: a través de una cola de mensajes. Es decir, cada vez que aplicamos algún cambio en algún View no se aplica directamente sino que se deja la petición en una cola. Y ese hilo, el UI thread o el hilo principal de la aplicación es quien precisamente se encarga de procesar esa cola y aplicar los cambios solicitados.

También desde ese hilo es de donde se llama a los callbacks de los eventos y es por eso por lo que hay que tener ojo con determinadas acciones si no queremos que la aplicación se ralentice y deje de responder. El ejemplo más típico es un acceso a la red. Para que la aplicación siga respondiendo resulta imprescindible por tanto dejar determinadas tareas a nuevos hilos. Android soporta los hilos básicos de java más todo el API de concurrencia, y aparte trae sus propios superhilos llamados AsyncTask. Entre uno y otro quizá habrá que decantarse por el último porque a Android me da la sensación que siempre le gusta más que se hagan las cosas a su peculiar manera. ¿Tú también Android? joder, esto es la fiesta de los callbacks y el asincronísmo generalizado.

En este post veremos los hilos tradicionales utilizados desde un Activity de Android. Es una App de android que genera nombres aleatorios. Para generar y visualizar los nombres utilizará dos clases Thread:

  1. NameGenerator: genera nombres para dar ideas a nombres de webs. Cada nombre lo guarda en una estructura BlockingQueue que comparte con la otra clase. Este hilo es totalmente ajeno a Android
  2. NameFetcher: a través de la cola va recibiendo cada uno de los nombres generados y se encarga de notificárselo a la activity de Android
Este último punto es interesante. ¿Cómo podemos hacer que desde un hilo Java normal se actualice el interfaz del Activity? OJO: NO podemos pasar un view o la activity entera al Thread para que este meta mano: //08-12 19:54:46.882: E/AndroidRuntime(1372): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. Android NO permite que se altere el interfaz más que desde la propia Activity. Así que establecemos un mecanismo de comunicación (propio de android) a través de una instancia de la clase Handler. Esta nos permite mandar mensajes del hilo al Activity y esta ya puede tocar lo que quiera y como en este caso, actualizar una ProgressBar que siempre queda muy aparentón.

NameGenerator

Empezamos por el hilo feliz, ajeno al entorno que le rodea. Genera un número concreto de nombres y los va metiendo en una estructura compartida con un consumidor. Él sabrá para que lo quiere, esta clase es una machaca en modo mononeurona, hace su tarea y punto.

package info.pello.android.threads;

import java.util.Random;
import java.util.concurrent.BlockingQueue;

/**
 * NameGenerator
 * generates random names alternating vowels/consonants and sometimes
 * duplicating them. It makes use of a BlockingQueue to share names
 * with a consumer that will take care of notifying to our Android Activity.
 * @author Pello Xabier Altadill Izura
 * @greetz OOoooh I'm using threads and queues again oooo uuuu aaa
 *
 */
public class NameGenerator extends Thread {
	private int size = 2;
	private String [] vowels = {"a","e","i","o","u","y","ee","oo"}; 
	private String [] consonants = {"b","c","d","f","g","h","j","k","l",
									"m","n","p","q","r","s","t","v","w","x","y","z"};
	private String [][] alternate = {vowels,consonants};
	private int [] sizes = {vowels.length, consonants.length};
	
	private BlockingQueue generatedNames;
	
	private int isVowel = 0;
	private Random random = new Random();
	private int total = 100;
	private static final int DUPLICATION_POSSIBILITY = 20;
	private static final int SLEEP_TIME = 2000;
	
	
	
	/**
	 * default constructor
	 * @param thread name
	 */
	public NameGenerator (String name) {
		super(name);
	}
	
	/**
	 * Constructor with size parameter
	 * @param size
	 */
	public NameGenerator (String name, int size, BlockingQueue generatedNames, int total) {
		super(name);
		this.size = size;
		this.generatedNames = generatedNames;
		this.total  = total;
	}

	/**
	 * method executed when thread is started
	 */
	public void run () {
		String name = "";
		while (true) {
			if (total == 0) {
				System.err.println(this.getName() + " > Our work is done here. Bye!");
				return; 
			}
			name = generate();
			System.out.println("Generated name: " + name);
			try {
				generatedNames.put(name);
				total--;
				sleep(random.nextInt(SLEEP_TIME) + SLEEP_TIME);
			} catch (InterruptedException ie) {
				System.err.println("Exception in generation thread: " + ie.getMessage());
			}
		}
	}
	
	/**
	 * generates a name
	 * @return
	 */
	private String generate () {
		String result = "";
		int counter = 0;
		String lastChar = "";
		isVowel = counter = random.nextInt(2);
		
		for (int i = 0; i < this.size; i++) {
			lastChar = generateChar();
			lastChar = duplicateOrNot(lastChar);
			result += lastChar;
			
			// We prepare next round
			counter++;
			isVowel = counter % 2;
		}
		
		return result;
	}

	/**
	 * we generate a random char from vowels or consonants
	 * @return
	 */
	private String generateChar() {
		String lastChar;
		lastChar = alternate[isVowel][random.nextInt(sizes[isVowel])];
		return lastChar;
	}

	/**
	 * We apply 20% possibilities to duplicate vowel or consonant
	 * @param lastChar
	 * @return
	 */
	private String duplicateOrNot(String lastChar) {
		lastChar += (random.nextInt(100)< DUPLICATION_POSSIBILITY)?lastChar:"";
		return lastChar;
	}
	
	/**
	 * @return the size
	 */
	public int getSize() {
		return size;
	}

	/**
	 * @param size the size to set
	 */
	public void setSize(int size) {
		this.size = size;
	}

}

//... OOooo uuuuu aaaaa ... xDDDD
NameFetcher

Este es otro hilo que está pendiente de los nombres generados por NameGenerator. Los va recibiendo a través de la cola y se encarga de notificárselo a la activity de android a través de un mensaje por un Handler compartido. Se podría haber usado ese handler en el propio generador, es cierto pero quería un generador desacoplado de android.

package info.pello.android.threads;

import java.util.Random;
import java.util.concurrent.BlockingQueue;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;


/**
 * NameFetcher shares a queue with a name producer.
 * When a new name is generated it send a message to Activity through a Handler
 * @author Pello Xabier Altadill Izura
 */
public class NameFetcher extends Thread {
	private BlockingQueue generatedNames;	
	private static final int SLEEP_TIME = 2000;
	private Handler handler;
	private int total = 100;
	
	/**
	 * default constructor
	 * @param thread name
	 */
	public NameFetcher (String name) {
		super(name);
	}
	
	/**
	 * Constructor with size parameter
	 * @param size
	 */
	public NameFetcher (String name, BlockingQueue generatedNames, int total) {
		super(name);
		this.generatedNames = generatedNames;
		this.total = total;
	}

	/**
	 * method executed when thread is started
	 */
	public void run () {
		String name = "";
		Message msg = null;
		Bundle bundle = null;
		
		while (true) {
			try {
				if (total == 0) {
					System.err.println(this.getName() + " > My work is done here. Bye!");
					return; 
				}
				name = generatedNames.take().toString();
				System.out.println("Fetching name: " + name);
				// We prepare message, and we send it.
				msg = new Message();
				bundle= new Bundle();
				bundle.putString("name",name);
				msg.setData(bundle);
				handler.sendMessage(msg);
				total--;
				sleep(SLEEP_TIME);
			} catch (InterruptedException ie) {
				System.err.println("Exception in generation thread: " + ie.getMessage());
			}
		}
	}


	/**
	 * we need to set this handler to send messages to
	 * Main app thread
	 * @param handler
	 */
	public void setHandler(Handler handler) {
		// TODO Auto-generated method stub
		this.handler = handler;
		
	}


	
}

MainActivity

Bueno, y aquí es donde ponemos todo a funcionar. Aparte de unos controles simples, la activity instancia los hilos y los pone en marcha. Con el NameFetcher establece un puente de comunicación a través del handler, que dispone de un callback para cada nuevo mensaje recibido. ahí es donde nuestra activity actualizará los views: meterá el nuevo nombre en un TextView y incrementará el ProgressBar.

package info.pello.android.threads;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;

/**
 * This activity uses java Threads to generate Random names.
 * To communicate with them it makes us of a Handler
 * @author Pello Xabier Altadill Izura
 * @greetz bizgen project
 */
public class MainActivity extends Activity {
	
	private TextView generatedResult;
	private NameGenerator nameGenerator;
	private NameFetcher nameFetcher;
    BlockingQueue generatedNames = new SynchronousQueue();
    private Handler handler;
    private static final int NAMES_TO_GENERATE = 10;
    ProgressBar progressBar;

	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		generatedResult = (TextView) findViewById(R.id.generatedResult);
		progressBar = (ProgressBar) findViewById(R.id.progressBarThreads);
		progressBar.setMax(NAMES_TO_GENERATE);
		
		// The handler used to communicate between Activity and a Thread
		handler =new Handler() {
	    				@Override
	    				public void handleMessage(Message msg) {
	    					Log.d("Received message from thread","PELLODEBUG");
	    					Bundle bundle;
	    					bundle = msg.getData();
	    					generatedResult.append(bundle.getString("name") + "\n");
	    					// We update progress bar:
	    					progressBar.incrementProgressBy(1);
	    				}
	    		};
	    		
		nameGenerator = new NameGenerator("Generator", 3, generatedNames,NAMES_TO_GENERATE);
		nameFetcher = new NameFetcher("Fetcher", generatedNames, NAMES_TO_GENERATE);
		// We set a handler to comunicate with android activity
		nameFetcher.setHandler(handler);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	/**
	 * invoked when button is pressed, this method start
	 * some java threads.
	 * NOTE: this thread will keep on working even if we click home
	 * or back buttons in the application
	 * @param v
	 */
	public void startThreads (View v) {
		progressBar.setProgress(0);
		Log.d("Starting threads","PELLODEBUG");
		// This thread generates names and puts them
		// in a queue
		nameGenerator.start();
		
		// This one will get freshly created names from
		// the queue
		nameFetcher.start();
	}
	
}

Aquí puedes ver un poco el aspecto que tiene la cosa en el emulador y en el DDMS

Ejemplo de hilos en Android

by Pello Altadill 08/17/2013 10:50:52 - 1319 hits

Servidor web de Node.js refactorizado

Sí, seguro que el primer código que has visto de Node.js es uno de esos milagrosos servidores web hechos con pocas líneas. Olvídese de aparatosos sockets, de punteros o de BufferedStreamsWriters, un módulo, unos callbacks, eliges un puerto y a dar servicio. Bueno, uno trata de ser un good Node.js citizen y este código se entiende y mola, pero a la larga, si lo queremos ir mejorando y añadiendo otras funcionalidades cada vez va a ser más complicado mantenerlo. Lo que propongo en ...

by Pello Altadill 08/16/2013 23:18:31 - 706 hits

More »

Hibernate self-join con anotaciones

Vamos a dejar por unos instantes tanto Javascript sin tipos y sin estructura y pongámonos otra vez la corbata para volver al mainstream de aplicaciones empresariales J2EE. Quedaba pendiente ver cómo montar un self-join pero con anotaciones, es decir una clase que se hace referencia a sí misma. En su momento ya puse aquí la versión con el mapeo por XML, vamos a ver cómo quedaría la cosa pero usando las anotaciones en la ...

by Pello Altadill 08/16/2013 17:06:58 - 452 hits

More »

Un vistazo a Backbone

Backbone es un framework MVC de javascript para clientes web que facilita el desarrollo de aplicaciones de una sola página. El hecho de aplicar MVC permite separar los datos de la presentación y Backbone lo hace sin invadir la página y con una serie de funciones que permiten la persistencia de datos en el servidor. Dicho de otro modo, podemos hacer páginas web que man...

by Pello Altadill 08/16/2013 01:37:50 - 620 hits

More »

El ciclo de vida de una Activity en Android

Android tiene una forma peculiar de gestionar las aplicaciones que choca un poco con los sistemas operativos tradicionales. Pese a estar montado en un linux con el kernel 2.6, Android no tiene por costumbre matar las aplicaciones cuando las cerramos. La clave está en que igual lo hace o igual no, según las necesidades o lo que Android considere oportuno. La cosa es que no hay que empeñarse en tener un botón para matar la aplicación, oficialmente no se recomienda y se todo se resume a un tranqui...

by Pello Altadill 08/15/2013 13:55:06 - 1529 hits

More »

Callbacks y funciones asíncronas, el estilo Node.js

El estilo nodejs Yo pensaba que sabía -algo- de javascript hasta que descubrí Node.js :P. En fin, espero ser siempre un aprendiz. Conforme te vas asomando a nuevas tecnologías tratas de hacer las cosas correctamente y como recién llegado aplicas lo de donde fueres haz lo que vieres. ¿Existe algún documento con las convenciones de Node.js? Sí, Node.js tiene una guía de estilo aunque no e...

by Pello Altadill 08/14/2013 22:08:41 - 2328 hits

More »

Intents con parámetros y startActivityForResult

Cuando en Android queremos abrir una nueva pantalla o Activity se hace de una manera muy curiosa: se crea un Intent. Los Intent son eso, intentos. No garantizan que el resultado vaya a ser el correcto ni si quiera que alguien vaya a responder, y es que los intents se pueden usar tanto para abrir Activities como para solicitar cualquier cosa (iniciar una llamada, mandar un mensaje, etc... ). Con los intents en definitiva se llama a componentes de cualquier tipo. Pueden ser internos de la propia a...

by Pello Altadill 08/14/2013 12:57:24 - 940 hits

More »

Ejemplo de IndexedDB la BD de HTML5

HTML5 trae un montón de nuevas posibilidades al navegador. Se ha hablado mucho del formato de vídeo, del canvas, los websockets, de los nuevos controles de formulario pero a mí en particular me ha interesado especialmente aquello de una BD en el cliente. Es un tema que no está tan documentado como el resto y que en muchos libros de HTML5 o ni se menciona o se hace de pasada. Y lo que es peor, hay información en blogs pero a nada que esté desfasada los ejemplos no funcionan como la llamada a...

by Pello Altadill 08/13/2013 22:35:53 - 2244 hits

More »

Eventos en nodejs

NodeJS está orientado a eventos por tanto disponer de mecanismos para definir nuestros propios eventos es algo fundamental. Como vamos a ver no tenemos más que importar el módulo events y a partir de ahí ya podremos trabajar con instancias de EventEmitter. Y como disponemos de herencia, podemos agregar la emisión de eventos a nuestras clases. Un ejemplo sencillo de eventos Este programa lee datos por consola y puede generar tres eventos Si el usuario escribe algo S...

by Pello Altadill 08/13/2013 10:37:21 - 472 hits

More »