Cargando



Programar Android: Procesos, hilos y servicios

Cómo actúa Android en las aplicaciones al momento de ejecutar un servicio, describiremos en qué consisten los hilos de ejecución y procesos con ejemplos prácticos.


may 19 2016 17:23
Profesional
may 19 2016 17:30

android-developer.jpg

 

En este tutorial explicaremos la forma en que actúa Android al momento de ejecutar un servicio, describiremos en qué consisten los hilos de ejecución y de que tratan los procesos. Esto nos permitirá entender la manera en que se ejecutan nuestras aplicaciones, dándonos un mayor control y estabilidad en los dispositivos móviles donde serán instalados.

 

Thread


Cuando el usuario ejecuta una aplicación, Android crea un hilo de ejecución que se denomina principal (main). Este hilo es muy importante porque es el encargado de gestionar los eventos que dispara el usuario a los componentes adecuados e incluye también los eventos que dibujan la pantalla. Un hilo de ejecución, la parte más pequeña que puede ser procesada por un planificador (scheduler) en un sistema operativo, en este caso Android (con núcleo Linux).

 

La implementación de varios threads que se procesan al mismo tiempo en una misma aplicación, (llámese concurrencia que refiere a la simultaneidad de ejecución), se conoce como multithreading. El multithreading se aplica de modo que estos hilos comparten recursos y esto es lo que comprende un proceso, recordemos que esto lo podemos aplicar programáticamente dentro del código de una misma aplicación, la implementación del multithreading a nivel del sistema operativo no depende de nosotros.

 

El inicio del ciclo de vida de una aplicación comprende la generación de un nuevo proceso de Linux al que se le asigna un Main thread o UI thread (el hilo de ejecución que se encarga del procesamiento grafico de la aplicación, user interface thread en inglés)

 

Nota
El ciclo de vida comprende la ejecución de los métodos: onCreate(), onStart() y onResume(); en su inicio, y a su cierre: onPause(), onStop() y onDestroy().

 

Un proceso puede ser forzado al cierre por Android por falta de memoria, este tipo de casos son poco comunes por el avance tecnológico pero aun sucede.

 

La pregunta es: ¿Cuáles procesos Android decide cerrar?

 

Estos se cierran comparando su nivel de importancia, se resume así:

 

El más importante: Procesos de primer plano
El usuario está interactuando con dicho proceso (el método onResume() de dicho proceso se encuentra actualmente corriendo). Hay un servicio que está corriendo los métodos de su ciclo de vida. O hay un BroadcastReceiver ejecutando su método onReceive().

 

El segundo más importante: Procesos visibles
Actividad con llamado al metodo onPause(). Servicio ligado a una actividad visible (bound service).

 

El tercero más importante: Proceso con un servicio
El usuario no se encuentra interactuando directamente con el proceso.El proceso tiene un servicio corriendo en segundo plano (background).

 

El segundo con menos importancia: Proceso de segundo plano
No existe ningún tipo de interacción con el usuario. El proceso visto más recientemente por el usuario será el último en ser destruido.

 

El menos importante: Proceso vacío
No tiene componentes activos. El proceso sigue vivo para propósito de caching (memoria caché), previniendo que el usuario retorne al uso de ese proceso.

 

Éste último, el proceso vacío, es el primero en ser terminado en caso de falta de memoria. Así, una aplicación que implemente un servicio en donde se cree un thread para descargar contenido de internet, será más importante que una aplicación que cree el thread sin implementar un servicio, de modo que éste es más propenso a ser terminado antes de concluir la descarga, debido a que son procesos de larga duración.

 

Para entender el multhreading veamos cómo Android maneja su main thread.

 

 

multithread-android-1.jpg

 

 

El PROCESO A posee un UI o MAIN thread, este thread maneja un message queue o cola de mensajes, el cual se va ejecutando a medida que el thread se va desocupando, ¿quién maneja esto? El Looper.

 

Looper es una clase de la interfaz de usuario de Java de Android que, junto con la clase Handler, procesa los eventos de interfaz de usuario, tales como pulsaciones de botón, pantallas nuevamente dibujadas e interruptores de orientación. Los eventos también pueden ser usados para cargar contenido en un servicio HTTP, cambiar el tamaño de las imágenes y ejecutar solicitudes remotas. La característica clave de estas clases es que sean capaces de poner en práctica un patrón de concurrencia.

 

La clase Android Looper contiene un MessageQueue (cola de mensajes) y se asocia sólo con el tema desde el cual fue creado. Ten en cuenta que esta conexión no se puede romper y que el lLooper no puede estar unido a cualquier otro hilo. Además, el Looper se encuentra en el almacenamiento local y sólo se puede llamar desde un método estático. Un método de preparación comprueba si un Looper ya está asociado con un hilo y, a continuación, el método estático crea el Looper. Después, un bucle puede ser utilizado para comprobar los mensajes en la cola.

 

Hasta ahora vamos entiendo varios conceptos: proceso, hilo de ejecucion (thread), UI thread, looper, pero aún no sabemos por qué se implementa el multithreading.

 

Operaciones de larga duración


Se considera larga duración a cualquier método cuya ejecución supere los 5 segundos, el cual dispara el típico mensaje “la aplicación no responde. ¿Desea cerrarla?”.

 

¿Cuáles pueden ser estas operaciones?: Acesso a internet, consultas de SQL, XML/HTML/JSON parsing, procesamientos gráficos complejos. Cualquiera de estas operaciones que sean corridas en el main thread van a bloquearlo, y siendo éste el que maneja la interfaz gráfica de usuario se interpreta como un congelamiento, el cual android decide cerrar.

 

Tan solo imaginemos que cualquiera de estas operaciones dura 7 segundos y el usuario decide escribir algo en algún text input así que mientras estos 7 segundos no han transcurrido el UI thread no puede actualizar la vista para que el usuario aprecie que está escribiendo, y así se genera un freeze, se dispara el mensaje de “no hay respuesta” con el cual tienes dos opciones, esperar o destruir, aunque nunca se podrá saber cuánto se debe esperar, podrían ser unos pocos segundos o incluso minutos dependiendo de la cola de mensajes que tenga el Main thread.

 

¿Cómo evitamos el congelamiento?


Usando threads o servicios, dependiendo si la tarea requiere modificar la vista, en este caso se implementa un servicio debido a que no se puede modificar la vista de una aplicación fuera del UI thread. La manera más óptima de evitar el congelamiento es usar Tareas Asíncronas con la clase AsyncTask, en este tutorial haremos la implementación de multiples threads para entender el comportamiento de la arquitectura de Android.

 

Código y desarrollo


El proyecto que crearemos a continuación se basará en una descarga de imágenes con las cuales debemos crear un thread que permita manejar el acceso y descarga por internet debido a que el MAIN o UI Thread no permite realizar esta acción.

 

Comenzaremos creando un proyecto nuevo con una actividad vacía, hemos titulado a este proyecto “MultiThreadEjemplo”, con una sola actividad sencilla crearemos la estructura del archivo XML que pertenece a esta actividad.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:background="#12adb2"
    tools:context="com.omglabs.multithreadejemplo.MainActivity">
    <EditText
        android:id="@+id/descargaURL"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:paddingBottom="5dp"
        android:focusable="false"
        android:textColor="#000000"/>
    <Button
        android:id="@+id/descargaBot"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="descargar"
        android:onClick="descarga"
        android:background="#ffffff"
        android:layout_below="@id/descargaURL"/>
    <LinearLayout
        android:id="@+id/progresslayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/descargaBot"
        android:orientation="vertical"
        android:visibility="gone">
<TextView
    android:id="@+id/progresstag"
    style="?android:textAppearanceSmall"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="cargando..."/>
        <ProgressBar
            android:id="@+id/progressbar"
            style="?android:progressBarStyleSmall"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:indeterminate="true"/>
    </LinearLayout>
<ListView
    android:id="@+id/listurls"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:entries="@array/URLs">
</ListView>
</RelativeLayout>
Contamos con un campo de texto, un botón, un Linear layout que corresponde a una barra de carga indeterminada que usaremos más adelante, y una list view que contiene un arreglo de URLs de imágenes alojadas en internet. En el archivo que contiene la clase de Java para nuestra actividad (única) está escrito con el siguiente código:
package com.omglabs.multithreadejemplo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
    private EditText editText;
    private ListView listView;
    private String[] urls;
    private ProgressBar progressBar;
    private LinearLayout progressLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
	    super.onCreate(savedInstanceState);
	    setContentView(R.layout.activity_main);
	    editText = (EditText) findViewById(R.id.descargaURL);
	    listView = (ListView) findViewById(R.id.listurls);
	    listView.setOnItemClickListener(this);
	    urls = getResources().getStringArray(R.array.URLs);
	    progressBar = (ProgressBar) findViewById(R.id.progressbar);
progressLayout = (LinearLayout) findViewById(R.id.progresslayout);

    }
    public void descarga(View view) {
    }
    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
	    editText.setText(urls[i]);
    }
}
Hasta este momento la aplicación puede ser compilada sin ningún problema, en esta clase, declaramos las variables:
  • editText
  • listView
  • urls
  • progressBar
  • progressLayout

 

Un campo de texto, una lista, un arreglo de cadena de caracteres, una barra de progreso y un Linear Layout.

 

En el método onCreate asignamos a éstas el respectivo view que les pertenece y que fueron creados en el archivo XML de la actividad, a excepción del urls que se asigna sus valores desde la carpeta values en el archivo string y cuyo arreglo se encuentra declarado de la siguiente forma:

<string-array name="URLs">
    <item>http://www.fmdos.cl/wp-content/uploads/2016/03/1.jpg</item>
    <item>http://vignette3.wikia.nocookie.net/teenwolf/images/9/90/Crystal_Reed_003.jpeg</item>
    <item>https://pbs.twimg.com/profile_images/699667844129107968/EvhTFBHN.jpg</item>
    <item>http://vignette1.wikia.nocookie.net/teen-wolf-pack/images/0/0b/Holland-holland-roden-31699868-500-600.png</item>
</string-array>
El método vacío descarga (View view) será llenado con el código que hará la descarga y que está enlazado al botón descargaBot a través del atributo onclick. Por último el método onitemclick que pertenece al listview, éste llena el campo de texto al clicar cualquiera de los urls contenidos en la lista. Una vez compilado este código lucirá así:

 

 

multithread-android.jpg

 

 

En el siguiente paso crearemos los métodos que procederán a la descarga, siguiendo estos pasos:

  • Crear un objeto de la clase URL (java.net) que representará el url a descargar.
  • Abrir la conexión usando dicho objeto.
  • Leer los datos (vía web) usando la clase input stream en un arreglo byte.
  • Abrir/crear un archivo output stream en donde se guardarán los datos de las urls dentro de la tarjeta SD.
  • Escribir los datos en dicho archivo.
  • Y por último cerrar la conexión.

 

Por ahora se verá así:

public boolean descargarusandoThreads(String link){
    boolean confirmacion = false;
    URL descargarEnlace=null;
    HttpURLConnection conex = null;
    InputStream inputStream=null;
    try {
	    descargarEnlace = new URL(link);
	    conex = (HttpURLConnection)descargarEnlace.openConnection();
	    inputStream=conex.getInputStream();
    } catch (MalformedURLException e) {
	    e.printStackTrace();
    } catch (IOException e) {
	    e.printStackTrace();
    }finally {
	    if (conex!=null){
	    conex.disconnect();
	    }
	    if(inputStream!=null){
		    try {
			    inputStream.close();
		    } catch (IOException e) {
			    e.printStackTrace();
		    }
	    }
    }
    return confirmacion;
}
Este método que hemos construido solo necesitará un String el cual será la URL a descargar, es booleano para efecto de confirmación de descarga, descargarEnlace es el objeto URL, conex es la conexión que se realizará para acceder al objeto e inputStream es la que procederá a leer los datos, si intentáramos usar este método en el botón de descargaBot la aplicación se detendría por no poder ejecutarse en el main thread.

 

Aquí vamos con el uso de los threads, existen dos formas de hacer esto con una clase y es extendiendo esa clase a Thread o implementado la clase Runnable, esta clase no es un thread simplemente te permite crear un método el cual puedes correr en un momento específico y si creas un thread aparte correrlo en el mismo.

 

Dentro del botón de descarga escribiremos este código, y lucirá así:

public void descarga(View view) {
    Thread mThread = new Thread(new mRunn());
    mThread.start();
}
Aquí estamos creando un nuevo thread el cual necesita un objeto Runnable que creamos en una clase privada así:
private class mRunn implements Runnable{
    @Override
    public void run() {
	    descargarusandoThreads(urls[0]);
    }
}
Crear clase privada

 

Nota
Recuerda que todo esto está en la clase de Java de nuestra única actividad.

 

Con la línea:

descargarusandoThreads(urls[0]);
Estamos llamando a la función que creamos en donde abríamos la conexión, se le pasa un ítem del arreglo URL para que este pueda, leer los datos de dicha dirección. Más adelante será modificado.

 

Si intentáramos correr esta aplicación al presionar el botón, la aplicación se detendría, debido a que necesitamos un permiso especial para acceder a internet, el cual es pedido a través del manifest de nuestra aplicación. Agregando la línea, antes de la etiqueta <application />:

<uses-permission android:name="android.permission.INTERNET" />
Ahora para verificar que la aplicación realmente realice la descarga agregaremos unas líneas de código al método descargarusandoThreads, quedará así:
public boolean descargarusandoThreads(String link){
    boolean confirmacion = false;
    URL descargarEnlace=null;
    HttpURLConnection conex = null;
    InputStream inputStream=null;
    FileOutputStream archOutputStream =null;
    File archivo=null;
    try {
	    descargarEnlace = new URL(link);
	    conex = (HttpURLConnection)descargarEnlace.openConnection();
	    inputStream=conex.getInputStream();
	    archivo=new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS  )+"/"+Uri.parse(link).getLastPathSegment());
	    archOutputStream= new FileOutputStream(archivo);
	    int Lectura=-1;
	    byte[] buffer = new byte[1024];
	    while ((Lectura = inputStream.read(buffer))!= -1){
	    archOutputStream.write(buffer, 0, Lectura);
	    }
	    confirmacion=true;
    } catch (MalformedURLException e) {
	    e.printStackTrace();
    } catch (IOException e) {
	    e.printStackTrace();
    }finally {
	    if (conex!=null){
	    conex.disconnect();
	    }
	    if(inputStream!=null){
		    try {
			    inputStream.close();
		    } catch (IOException e) {
			    e.printStackTrace();
		    }
	    }
	    if(archOutputStream!=null){
		    try {
			    archOutputStream.close();
		    } catch (IOException e) {
			    e.printStackTrace();
		    }
	    }
    }
    return confirmacion;
}

FileOutputStream archOutputStream =null;
    File archivo=null;
Las declaraciones de estos objetos representan la escritura del archivo que se está leyendo, y el archivo vacío donde se guardará la lectura.
archivo=new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+"/"+Uri.parse(urls[0]).getLastPathSegment());
archOutputStream= new FileOutputStream(archivo);
int Lectura=-1;
byte[] buffer = new byte[1024];
while ((Lectura = inputStream.read(buffer))!= -1){
archOutputStream.write(buffer, 0, Lectura);
}
confirmacion=true;
“archivo” es el objeto File vacío cuya dirección es construida accediendo a la tarjeta SD “Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)” y agregándole un slash “/” y el último segmento de la URL que generalmente representa el nombre del archivo a descargar, esto lo conseguimos con el método getLastPathSegment().

 

Antes de probar la aplicación agregaremos un último permiso en el manifest:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Después de correr la aplicación en el emulador o dispositivo Android, al presionar el botón veremos que aparentemente no ocurre nada, pero si revisamos la carpeta Descarga con un explorador de archivos nos daremos cuenta de que el primer ítem de la lista, ha sido descargado; una foto que se llama 1.jpg.

 

Para hacer la aplicación dinámica e implementar las URL´s del listview, actualizaremos el método descarga (View view) y agregaremos ésta, como la primera línea:

String enlace = editText.getText().toString();
Y en la clase mRunn agregaremos esto, antes del método run():
private class mRunn implements Runnable{
private String enlace;
    public mRunn(String enlace){
	    this.enlace = enlace;
    }
    @Override
    public void run() {
	    descargarusandoThreads(enlace);
    }
}
Y en la clase mRunn agregaremos esto, antes del método run():

 

Así podremos pasar la variable enlace desde el campo de texto hasta el método que realiza la descarga. La aplicación en este punto es completamente funcional, aunque falta un poco de amigabilidad con el usuario, así que intentaremos arreglar esto, usando la barra de progreso que declaramos al principio.

 

En la clase mRunn en el método run() incluiremos:

MainActivity.this.runOnUiThread(new Runnable() {
    @Override
    public void run() {
	    progressLayout.setVisibility(View.VISIBLE);
    }
});
Antes del llamado al método descargarusandoThreads. Esto hará que aparezca la barra de carga cuando presionamos el botón, en la cláusula finally del método descargarusandoThreads.

 

Agregaremos:

this.runOnUiThread(new Runnable() {
    @Override
    public void run() {
	    progressLayout.setVisibility(View.GONE);
    }
});
De modo que al finalizar la descarga, la barra desaparezca otra vez. Esto ocurrirá sea o no exitosa la descarga.
Y esto ha sido todo, una breve implementación de múltiples threads, esto resulta un poco tedioso y trae ciertas complicaciones para aplicaciones más complejas. El modo más efectivo de lograr esta tarea, en nuestro caso descargar unas imágenes, es usando los AsyncTasks.


¿Te ayudó este Tutorial?


1 Comentarios


Sofia Delgado
ago 01 2016 15:02

Muy bueno¡¡¡ 

No esperes más y entra en Solvetic
Deja tus comentarios y aprovecha las ventajas de la cuenta de usuario ¡Únete!

X