Tutorial y ejemplos de Threading
de Android
Este es nuestro tutorial de threading en android. Exploramos lo que es un thread
y proporcionamos varias abstracciones para la clase Thread
.
¿Qué es un Thread
?
Un thread
es una ruta de ejecución que existe dentro de un proceso.
Un proceso puede tener uno o más threads
.
Un proceso Single-threaded
tiene un thread
mientras que un proceso multi-threaded
tiene más de un thread
.
En el proceso
tenemos múltiples conjuntos de instrucciones ejecutadas simultáneamente.single-threaded
, tenemos sólo un flujo de ejecución de instrucciones mientras que en el proceso
multi-threaded
Un proceso multi-threaded
tiene partes que se ejecutan simultáneamente y cada parte puede hacer una tarea determinada de forma independiente.
Así que el multi-threading
es un mecanismo que nos permite escribir programas de manera que múltiples actividades puedan proceder simultáneamente en el mismo programa.
Y especialmente en el entorno actual de dispositivos multinúcleo, los desarrolladores deben ser capaces de crear líneas de ejecución concurrentes que combinen y agreguen datos de múltiples recursos.
Pero también es importante señalar que, en realidad, un sistema que sólo tiene un núcleo de ejecución puede crear la ilusión de una ejecución concurrente. Lo hace ejecutando los distintos "hilos" de forma intercalada.
Pero lo hace de forma rápida, de tal manera que pensamos que realmente está realizando tareas de forma concurrente.
El "hilo" principal
Normalmente cuando ejecutas tu proyecto, tu proceso de aplicación se iniciará. Primero habrá threads
de mantenimiento para el Runtime de Android o la máquina virtual Dalvik.
Además, el sistema Android creará un thread
de ejecución llamado main
. Este será el thread
principal para su aplicación.
Este thread
también se llama a veces UI
thread``.
Tu aplicación puede tener muchos otros threads
, normalmente llamados threads
de fondo. Sin embargo, el thread
principal es el más importante. Es este thread
el responsable de interactuar con los componentes y vistas de Android. Los renderiza y también actualiza sus estados.
Este thread
es muy crucial, especialmente dado el hecho de que, como hemos dicho, es donde todos los componentes de Android (Activity
, Services
, BroadcastReceiver
) se ejecutan por defecto.
Este thread
es el responsable de manejar y escuchar los eventos de entrada del usuario. Debido a su importancia, siempre es recomendable mantenerlo activo:
-
- No hacer ningún tipo de tarea que pueda llevar mucho tiempo, como "entrada/salida" (
I/O
) en este "hilo". Estas tareas pueden bloquear elthread
principal durante un tiempo indefinido, por lo que deben ser transferidas a unthread
en segundo plano.
- No hacer ningún tipo de tarea que pueda llevar mucho tiempo, como "entrada/salida" (
- No hacer tareas intensivas de CPU en este
thread
. Si tienes algunos cálculos o tareas caras como la codificación de vídeo, necesitas descargarlas también a unthread
de fondo.
Este thread
normalmente tiene una instalación adjunta llamada Looper
. El Looper
mantendrá una Cola de Mensajes. Una MessageQueue es simplemente una cola de mensajes con alguna unidad de trabajo que deben ser ejecutados secuencialmente.
Así que cuando un mensaje está listo para ser procesado en la cola, el Looper Thread
sacará ese mensaje de la cola. Ese mensaje será reenviado sincrónicamente (secuencialmente) al manejador de destino. Ese manejador ya está especificado en el mensaje.
Entonces el Handler
comenzará su trabajo y lo hará. Cuando termina ese trabajo, el thread
de Looper` comienza a procesar el siguiente mensaje disponible en la cola y lo pasa para que también se ejecute.
Puedes ver que este proceso es secuencial. Así que supongamos que nuestro Handler
no termina su trabajo rápidamente, el Looper
se quedará ahí esperando para procesar otros mensajes pendientes en la cola.
En ese caso el sistema mostrará el diálogo Application Not Responding(ANR)
. Es posible que ya hayas visto esto en algunas aplicaciones. Significa que la aplicación no está respondiendo a las entradas del usuario. Sin embargo, está ocupada haciendo su trabajo.
El diálogo ANR se mostrará a los usuarios si una aplicación no responde a la entrada del usuario en cinco segundos. El sistema ofrecerá entonces a los usuarios la opción de salir de la aplicación.
Te encontrarás con este tipo de escenario cuando intentes realizar tareas intensivas en tu hilo
principal. Esto significa, por ejemplo, que cuando intentas hacer lo siguiente en tu thread
principal:
-
- Acceder a la red/servicios web/internet
- Acceder a los recursos del sistema de archivos.
- Tratar de procesar grandes cantidades de datos o hacer cálculos matemáticos complejos, etc.
La mayor parte del código que escribes como en tus actividades, fragmentos, servicios normalmente se ejecutan en el thread
principal por defecto, a menos que crees explícitamente un thread
de fondo.
El SDK de Android está basado en un subconjunto del SDK de Java. El SDK de Java se deriva del proyecto Apache Harmony
y proporciona acceso a construcciones de concurrencia de bajo nivel como:
java.lang.Thread
.java.lang.Runnable
.- Palabras clave
synchronized
yvolatile
.
Clase Thread
La clase java.lang.Thread
es la construcción más básica utilizada para crear threads
.
También es la más utilizada. Esta clase nos crea una nueva línea de ejecución independiente en un programa Java.
Una forma de crear un nuevo thread
es simplemente subclasificando o extendiendo la clase java.lang.Thread
.
public class MyThread extends Thread {
public void run() {
Log.d("Generic", "Our thread is running ...");
}
}
Entonces podemos hacer nuestra tarea en segundo plano dentro del método run()
.
Sin embargo ese thread
aún no está iniciado. Para que eso ocurra tenemos que instanciar esa clase y arrancar explícitamente nuestro thread
:
MyThread myThread = new MyThread();
myTread.start();
El método start()
reside en la clase Thread
. Al invocarlo le dice al sistema que cree un thread
dentro del proceso y ejecuta el método
método run()
. El método run()
se ejecutará automáticamente si invocamos el método start()
.
Métodos Threading
comunes
(a). Método Thread.currentThread()
Este método devolverá el Thread
del llamante, es decir, el Thread
actual.
(b). Thread.sleep(time)
Este método hará que el thread
que envió este mensaje duerma durante el intervalo de tiempo dado (en milisegundos y nanosegundos). La precisión no está garantizada - el Hilo
puede dormir más o menos de lo solicitado.
Básicamente, se detiene la ejecución del thread
actual durante el
período de tiempo dado.
(c). getContextClassLoader()
Este método devuelve el ClassLoader de contexto para este Thread
.
(d). Iniciar()
Este método iniciará el nuevo Thread
de ejecución. El método run()
del receptor será llamado por el propio Thread
del receptor (y no por el Thread
que llama a start()
).
(e). Tread.getName() y
Thread.getId()`
Obtienen el "nombre" y el "ID" respectivamente. Se utilizan principalmente para fines de depuración.
(f) Thread.isAlive()
La función isAlive()
comprueba si el thread
se está ejecutando actualmente o
ya ha terminado su trabajo.
g) Thread.join()
join()
bloqueará el thread
actual y esperará hasta que el thread
accedido
termine su ejecución o muera.
Cómo mantener la capacidad de respuesta de la aplicación
La mejor manera de mantener la capacidad de respuesta de la aplicación no es evitando hacer operaciones de larga duración. En su lugar, es descargándolas del hilo principal
para que puedan ser manejadas en segundo plano por otro hilo
.
El hilo principal puede entonces continuar procesando las actualizaciones de la interfaz de usuario sin problemas y responder de manera oportuna a las interacciones del usuario.
Normalmente hay un conjunto de operaciones típicas que son comunes en muchas aplicaciones y que consumen no sólo mucho tiempo sino también recursos del dispositivo.
Estas incluyen:
- Acceso y comunicación a través de la red, especialmente Internet.
- Operaciones de entrada y salida de archivos. Estas ocurren en el sistema de archivos local.
- Procesamiento de imágenes y vídeos.
- Cálculos matemáticos complejos.
- Procesamiento de texto - Tratar de procesar o analizar una gran cantidad de texto.
- Codificación y decodificación de datos.
Ejemplos Rápidos de Threading
.
1. Creando un simple Timer con la clase Thread
.
Esta es una clase para mostrar cómo implementar un simple temporizador. No imprime nada y es sólo una clase que muestra cómo implementar esta idea.
import java.lang.Thread;
public class TimerClass extends Thread{
boolean timeExpired;
double mTime;
/** Creates a new instance of TimerClass */
public TimerClass(double time){
mTime = time;
}
public void run(){
timeExpired = true;
mTime = mTime * 1000;
double startTime = System.currentTimeMillis();
double stopTime = startTime + mTime;
while(System.currentTimeMillis() < stopTime && timeExpired == false){
try {
Thread.sleep(10);
} catch (InterruptedException e){ }
}
timeExpired = true;
}
public boolean getTimeExpired(){
return true;
}
public void cancel(){
timeExpired = true;
}
}
2. Clase utilitaria Thread
completa y reutilizable
import android.os.Looper;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Threading tools
* </p>
*/
public class ThreadUtils {
private final static ExecutorService sThreadPool = Executors.newCachedThreadPool();
/**
* Current thread
*/
public static Thread currentThread() {
return Thread.currentThread();
}
/**
* Current process ID
*/
public static long currentThreadId() {
return Thread.currentThread().getId();
}
/**
* Current process name
*/
public static String currentThreadName() {
return Thread.currentThread().getName();
}
/**
* Determine if it is a UI thread
*/
public static boolean isUiThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
/**
* Runs on the UI thread
*/
public static void runOnUiThread(Runnable action) {
if (action == null) {
return;
}
if (Looper.myLooper() == Looper.getMainLooper()) {
action.run();
} else {
HandlerUtils.uiPost(action);
}
}
/**
* Runs in the background thread
*/
public static void runOnBackgroundThread(Runnable action) {
if(action==null){
return;
}
if (Looper.myLooper() != Looper.getMainLooper()) {
action.run();
}else{
sThreadPool.submit(action);
}
}
/**
* Run on asynchronous thread
*/
public static void runOnAsyncThread(Runnable action) {
if (action == null) {
return;
}
sThreadPool.submit(action);
}
/**
* Runs on the current thread
*/
public static void runOnPostThread(Runnable action) {
if(action==null){
return;
}
action.run();
}
public static void backgroundToUi ( final Runnable background , final Runnable ui ) {
runOnBackgroundThread(new Runnable() {
@Override
public void run() {
background.run();
runOnUiThread ( ui );
}
});
}
}