miércoles, 30 de mayo de 2012

Android: Thread (Hilo) y Handler, proceso en segundo plano

Buenos días!
En este tutorial explicaremos el funcionamiento de los threads y los handlers. A simple vista parece algo complicado pero lo explicaremos lo más ameno posible. Primero hay que saber que és cada cosa:

Thread (Hilo):
        Es la función encargada de crear algún proceso en segundo plano. Su funcionamiento es bastan te   simple aunque como todo siempre se puede complicar. Se pueden crear tantos hilos (Thread) como se quiera, teniendo en cuenta que el hilo deja de formar parte de la aplicación y funciona de manera independiente. A simple visto no hay ningún problema pero el hilo no puede modificar ni insertar datos en el hilo principal (aplicación) esto causaría error, ¿Entonces?, ¿como podemos utilizar los hilos para volcar información a nuestra aplicación? La solución se llama Handler.



Handler:
        Para que lo entendamos todos un handler es el puente que hay entre un hilo secundario (thread) y el hilo principal (aplicación). No hay mucho más que explicar, para que se entienda bien:


Para ver el código y el funcionamiento vamos hacer un ejemplo muy práctico que seguro que a muchos le gusta.

Crear una barra de progreso en Android:

El ejemplo que vamos a utilizar es una barra de progreso (ProgressBar) que mostraremos inicialmente, a continuación crearemos un thread y en su interior la función que queramos, en este ejemplo simplemente será un contador (tendremos que simular algún proceso que tarde algo para poder ver funcionando el thread y no sea simplemente un parpadeo) y utilizaremos un handlet para ir actualizando la barra de progreso (situada en el hilo principal) como hemos comentado para actualizar datos de pantalla desde un hilo secundario (en nuestro caso el contador) hace falta un puente, es decir, nuestro handler.

Como vamos a ver el Handler hay que declararlo al inicio:

private Handler puente = new Handler() {
  @Override
  public void handleMessage(Message msg) {
   
  }
 };

Como vemos hay que pasarle un Message, en nuestro caso sera el contador hacia la progressbar.
Ahora mostramos el progressbar:
        progressDialog = new ProgressDialog(EjemplotutoActivity.this);
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        progressDialog.setMessage("Loading...");
        progressDialog.setMax(10000);
        progressDialog.setProgress(0);
        progressDialog.setCancelable(false);
        progressDialog.show();

La barra de progreso la hemos puesto a 0 y su máximo será 10000 así nos dará tiempo a verlo.

Creamos el Thread y lo ponemos en marcha con la funcion start() :

Thread th1 = new Thread(new Runnable() {
       @Override
       public void run() {
        
       }
      }); 
th1.start();

Ahora crearemos nuestro contrador:

Thread th1 = new Thread(new Runnable() {
       @Override
       public void run() {
        for(int a=1;a<9999;a++){
         int contador = a;
        }
       }
      });
      
      th1.start();

Ahora vamos hacer el paso importante pasar nuestro contador por un 'puente' handler hacia el progressBar, lo haremos através de un Message :

Thread th1 = new Thread(new Runnable() {
       @Override
       public void run() {
        for(int a=1;a<9999;a++){
         int contador = a;
         
         Message msg = new Message();
      msg.obj = a;
      puente.sendMessage(msg);
        }
       }
      });
Como vemos enviamos un Message a nuestro Handler 'puente' y lo hará 9998 veces ya que esta dentro de un túnel, la velocidad como veréis va un poco saturada al menos en el emulador. Ya solo queda recivir el contador en el Handler e insertarlo en el ProgressBar:

private Handler puente = new Handler() {
  @Override
  public void handleMessage(Message msg) {
   progressDialog.setProgress((Integer)msg.obj);
  }
 };
Como ya véis hay que hacer un casteo '(Integer)msg.obj' para convertir el mensaje en su forma original, un Integer y añadirlo al progressBar através del .setProgress.

Espero que haya quedado bastante claro y aunque parezca un poco largo, al ponerlo en práctica se reduce bastante.

Resumen: Pasar datos de un hilo secundario (Thread) al principal (Aplicación).

Muchas Gracias a todos y espero vuestros comentarios!

51 comentarios:

  1. Hola, he estado viendo el código y creo que puede servirme de ayuda pero tengo una duda.
    ¿Que sucede cuando la operativa del hilo forma parte de otra clase?

    Es decir, yo tengo mi clase lanzadora que muestra una tabla con datos. Esta misma clase crea un hilo de una clase, pero claro, cuando hago referencia al handler puente desde la otra clase (la del hilo) no lo reconoce, puesto que la definición está en la clase principal (la lanzadora).
    No se si me explico

    ResponderEliminar
    Respuestas
    1. Estuve pensando en eso justamente el otro día y la verdad es que se me plantean algunas alternativas (no tienen porque funcionar xD) pero aún no he podido ponerlas en practica.

      Me gustaría que si encontraras la respuesta la comentaras aquí también, para ayudar a otras personas que lo necesiten. Por mi parte aré lo mismo.

      Muchas gracias por comentar!

      Eliminar
    2. Ha pasado mucho tiempo desde que hicieron este comentario... pero la solucion ideal, seria para estos casos usar un broadcast (http://developer.android.com/reference/android/content/BroadcastReceiver.html).

      Eliminar
  2. Victor, agradezco que hayas hecho esta publicación. Estuve leyendo varias pero de todas la más fácil de comprender ha sido esta.

    Saludos y gracias.

    José Enrique Zúñiga Marín

    ResponderEliminar
    Respuestas
    1. Muchas gracias por tu comentario, cualquier cosa quí estamos :D

      Eliminar
  3. mil gracias a todas estas personas que comparten sus conocimientos, pues sin ellas, jamás lograríamos entender los entresijos de la programación android

    ResponderEliminar
    Respuestas
    1. Muchas gracias, seguiré ampliando el blog con más tutoriales.

      Eliminar
    2. Pues que Dios te lo pague, porque desde luego...os ganais el cielo

      Eliminar
  4. Saludos, Se ve perfectamente claro sin embargo a mi me sigue sin funcionar
    De nuevo aparece el mismo error CalledFromWrongViewException cuando el mensaje llega.
    Estoy usando este handler dentro de un Fragment, tiene algo que ver?
    gracias

    ResponderEliminar
    Respuestas
    1. Buenos días!
      Primero deberías probarlo sin Fragment a ver que resultado tienes. Y si que tendrá algo que ver ya que en el error CalledFromWrongViewException te está ablando del View en este caso Fragment. ¿Utilizas también el Progress Dialog? habría que tener en cuenta donde lo creas si dentro del Fragment o no.

      Espero a verte ayudado. cualquier cosa intentaremos ayudarte entre todos. Gracias!

      Eliminar
  5. Si en lugar de poner la orden (en el Thread)
    puente.sendMessage(msg);
    pongo directamente
    progressDialog.setProgress(msg);
    me funciona.
    ¿Lo que me pregunto es para qué quiero el Handler?

    ResponderEliminar
    Respuestas
    1. En este caso progressDialog es un ejemplo, el Handler se utiliza para sacar datos del Thread a la aplicaión en tiempo de ejecución. Si no utilizas Handler la barra de progreso no te medirá en tiempo real ya que no utilizas una apliación en segundo plano. Seguramente estás reando todo el progress dialog dentro del Thread. Gracias!

      Eliminar
    2. intenta cambiar textos de un textview o imagenes con un hilo y me cuentas si el handler, es solo por molestar que lo ponemos.
      a y muyyy util el tuto muchas gracias

      Eliminar
    3. intenta cambiar textos de un textview o imagenes con un hilo y me cuentas si el handler, es solo por molestar que lo ponemos.
      a y muyyy util el tuto muchas gracias

      Eliminar
  6. Gracias por tu aporte, se entiende perfectamente!
    Saludos!

    ResponderEliminar
  7. Muy buen tuto, Gracias :)

    ResponderEliminar
  8. Porque no puedo usar hilo.stop o hilo.resume... en android

    ResponderEliminar
    Respuestas
    1. Supongo que abra que utilizarlo en condiciones especiales, no lo he mirado.

      Eliminar
  9. 1+
    Buen tuto, gracias :)

    ResponderEliminar
  10. Muy pero muy bueno, corto y concreto :)

    ResponderEliminar
  11. una pregunta, en el ejemplo se utiliza un solo hilo y si fueran mas hilos digamos 4, tenemos que usar 4 puentes? como se maneja en este caso para varios hilos,

    gracias

    ResponderEliminar
    Respuestas
    1. Si serían más de un puente, aunque hay una manera más óptima que se llama asynctask. Esta semana aré un tutorial sobre eso :D muchas gracias por comentar!

      +1

      Eliminar
  12. hola victor, tendrias el codigo del proyecto; porque no se en que sector va cada trozo de codigo!!

    Gracias!!!

    ResponderEliminar
    Respuestas
    1. Los proyectos no los guardo, pero igualmente deberías empezar por tutoriales más sencillos. Gracias!

      Eliminar
  13. Saludos a todos, bueno realicé esta forma con AsyncTask
    La primera clase es esta la de AsyncTask
    package com.ex.proyecto;

    import android.app.ProgressDialog;
    import android.os.AsyncTask;
    import android.os.SystemClock;
    import android.widget.Button;
    import android.widget.ProgressBar;
    import android.app.Activity;


    public class UpdateProgress extends AsyncTask{
    int progress;
    Button button;
    ProgressBar progressBar;
    ProgressDialog pDialog;
    Activity ok;


    public UpdateProgress(Button button,ProgressBar progressBar,ProgressDialog pDialog, Activity ok) {
    this.button = button;
    this.progressBar = progressBar;
    this.pDialog = pDialog;
    this.ok = ok;
    }
    @Override
    protected void onPostExecute(Void result){
    this.button.setClickable(true);
    this.button.setText(R.string.bnt_text);
    this.progressBar.setProgress(0);
    //pDialog.dismiss();//ocultamos progess dialog.
    }


    @Override
    protected void onPreExecute(){
    this.progress = 0;
    /* para el progress dialog
    pDialog = new ProgressDialog(ok);
    pDialog.setTitle("Procesando");
    pDialog.setMessage("Espere por favor ....");
    pDialog.setIndeterminate(false);
    pDialog.setCancelable(false);
    pDialog.show(); */
    }

    @Override
    protected void onProgressUpdate(Integer... values){
    this.progressBar.setProgress(values[0]);
    }

    @Override
    protected Void doInBackground(Void... arg0){
    while(progress<100){
    this.progress++;
    publishProgress(this.progress);
    SystemClock.sleep(50);
    }
    return null;
    }
    }

    ResponderEliminar
  14. Luego tenemos la Otra Clase del Activity

    package com.ex.proyecto;


    import android.app.Activity;
    import android.app.ProgressDialog;
    import android.os.Bundle;
    import android.widget.EditText;
    import android.widget.ProgressBar;
    import android.widget.TextView;
    import android.view.Menu;
    import android.view.View;
    import android.widget.Button;

    public class MainActivity extends Activity {

    Button button, butonSumar;
    ProgressBar progressBar;
    EditText text1, text2;
    TextView respuesta2;
    int result = 0;
    private ProgressDialog pDialog;

    /** Called when the activity is first created. */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    button = (Button)findViewById(R.id.task);
    butonSumar = (Button)findViewById(R.id.button1);
    progressBar = (ProgressBar)findViewById(R.id.progressbar);
    respuesta2 = (TextView)findViewById(R.id.respuesta);
    /* El Proceso de AsynTask se Incia una Vez se le da click al Boton
    * Pulse para Inicar la Carga*/
    button.setOnClickListener(new Button.OnClickListener(){
    @Override
    public void onClick(View arg0){
    //Se cambia el Boton a Proceso corriendo Espere y
    //Se coloca deshabilita el Boton para que si lo pisan no haga nada
    button.setText(R.string.Corriendo);
    button.setClickable(false);
    /* Por último se llama al Metodo onPreExecute con la palabra execute de la
    * Sub-Clase UpdateProgress para que esta realice las Tareas de Forma Automaticas,
    * hay que recordar que solo se llama al Metodo execute y este realiza los trabajo
    * solos hasta Finalizae con el Metodo onPostExecute, bajo ningun concepto
    * se debe llamar a los metodos de la clase AsyncTask*/
    new UpdateProgress(button,progressBar,pDialog,MainActivity.this).execute();
    }
    });


    butonSumar.setOnClickListener(new Button.OnClickListener(){
    @Override
    public void onClick(View arg0){
    result = result + 1;
    String IntStr = String.valueOf(result);
    respuesta2.setText(IntStr);
    }
    });
    }

    @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;
    }
    }

    ResponderEliminar

  15. y por último tenemos el Archivo String en la Carpeta Values




    Proyecto
    Settings
    Hello world!
    Pulse para Iniciar la Carga
    Proceso Corriendo espere !!!!
    El Proceso Finalizó !!
    %


    Puedes correr un proceso primario y un hilo al mismo tiempo y el hilo modifica al primario.

    saludos a todo.



    ResponderEliminar
    Respuestas
    1. Muchas graciias por compartir, sobre el tema del tutorial.
      Animaros y dar +1 nos ayuda a todos!

      Eliminar
  16. No me dejo publicar el archivo xml de la pantalla
    pero lo pueden sacar por los archivos clases

    ResponderEliminar
    Respuestas
    1. Intentaré publicar el proyecto en cuanto tenga un momento. Muchas gracias!

      Eliminar
  17. Como mando una aplicación entera a segundo plano.

    ResponderEliminar
  18. Hola tengo una pregunta, es que quiero realizar un clientsocket que comunica con una aplicacion externa dentro del hilo thread y enviar los datos que recibo en ella y mostrarlos en la aplicacion como hago esto usando un handler ¿Uso tambien setprogress?

    ResponderEliminar
  19. genial gracias, espero poder seguir leyendo tus entradas..y otra vez, muchas gracias.

    ResponderEliminar
  20. una pregunta, como puedo suscribirme, para poder seguir el blog.

    ResponderEliminar
    Respuestas
    1. He añadido un botón de seguir bajo el menú.
      Muchas gracias por tu interés!

      Eliminar
  21. Muchas gracias por ejemplo, estoy haciendo un listado de ejercicios para un nuevo post en mi blog,ya que estoy empezando a manejar los threats, el ejercicio que has puesto en bruto sera el primero. Muchas gracias

    http://thebestandroide.blogspot.com.es/

    ResponderEliminar
    Respuestas
    1. Perfecto, pero deberías poner el link de este blog. Recuerda el CC creative Commons

      Eliminar
  22. Jope, que bien explicado. Así da gusto buscar información en Internet.

    ResponderEliminar
  23. Hola, me quedo claro en su mayoría lo que explicaste.. Pero tengo una duda, yo hice una pagina web (mapa) con php y base de datos, quiero que esta pag aparezca en mi webView en Android Studio y que cuando le de clic a un marcador me muestre su información (que se almacena en la BD ¿Como le hago para hacer que funcione en si?, es decir, ¿Que la app muestre un toast al dar clic en un marcador y esta muestre información desde la BD?

    ResponderEliminar
  24. Victor Muchisimas Gracias!!!
    Atte: Renato

    ResponderEliminar
  25. Excelente!!!
    Gracias!!

    ResponderEliminar