Tip Android #03: realizar tareas asíncronas con Runnable y Handler

  1. 17/01/2013 (Primera publicación)
  2. 28/07/2015:Control de salida de la Activity

  3. android tip

    En el tip anterior vimos cómo realizar tareas asíncronas en segundo plano usando la clase AsyncTask de Android SDK. Ahora vamos a repetir el mismo ejemplo pero usando Java estándar y los Handler de Android.

    La estrategia consiste en crear un objeto de tipo Thread (implementación de Runnable), implementar el método run con lo que queramos realizar en segundo plano y ejecutar este nuevo hilo con start. Los datos obtenidos con este hilo los procesaremos con un Handler de Android desde el que tendremos acceso al UIThread, esto, es, podemos modificar la interfaz gráfica.

    package com.danielme.tipsandroid.runnable;
    
    import java.net.URL;
    
    import android.app.Activity;
    import android.app.AlertDialog;
    import android.app.AlertDialog.Builder;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.util.Log;
    import android.view.View;
    import android.widget.ImageView;
    import android.widget.ProgressBar;
    
    import com.danieme.tipsandroid.runnable.R;
    
    public class MainActivity extends Activity
    {
    	ProgressBar progressBar;
    	ImageView imageView;
    	
    	@Override
    	public void onCreate(Bundle savedInstanceState)
    	{
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		progressBar = (ProgressBar) findViewById(R.id.progresss);
    		imageView = (ImageView) findViewById(R.id.imageView1);
    		progressBar.setVisibility(View.VISIBLE);
    
    		new Thread()
    		{
    			public void run()
    			{
    				try
    				{
    					//espera de 3 segundos para que de tiempo a ver el progress
    					Thread.sleep(3000);
    			    	URL url = new URL("https://danielme.com/wp-content/uploads/2012/02/icon-danielme.png");
    			    	Bitmap bmp = BitmapFactory.decodeStream(url.openConnection().getInputStream());
    			    	Message message = new Message();
    					message.obj = bmp;
    					message.what = 1;
    					getHandler.sendMessage(message);
    					
    				}
    				catch (Exception ex)
    				{
    					Log.e(MainActivity.class.toString(), ex.getMessage(), ex);
    					Message message = new Message();
    					message.what = 2;
    					getHandler.sendMessage(message);
    				}
    			}
    
    		}.start();
    	}
    		
    		
    	private Handler getHandler = new Handler()
    	{
    		@Override
    		public void handleMessage(Message msg)
    		{
    			 //la tarea en segundo plano ya ha terminado. Ocultamos el progreso.
    			 progressBar.setVisibility(View.GONE);
    			 //si tenemos la imagen la mostramos
    			 if (msg.what == 1 && msg.obj != null)
    			 {
    				imageView.setImageBitmap((Bitmap) msg.obj);
    				imageView.setVisibility(View.VISIBLE);
    			 }
    			 //si no, informamos del error
    			 else
    			 {
    				 	Builder builder = new Builder(MainActivity.this);
    					builder.setTitle(R.string.title);
    					builder.setIcon(android.R.drawable.ic_dialog_info);
    					builder.setMessage(R.string.error);					
    					builder.setNeutralButton(getString(R.string.ok), null);			
    
    					AlertDialog alertDialog = builder.create();
    					alertDialog.show();
    					alertDialog.setCancelable(false);
    			 }
    			
    
    		}
    
    	};
    
    	
    
    }
    
    

    Este código es correcto y funciona perfectamente, pero si ejecutamos la herramienta Lint de ADT obtenemos el warning «This Handler class should be static or leaks might occur»:

    lint warning handler

    Esto es debido a que si la Activity es destruida pero se está ejecutando un hilo que la referencie el recolector de basura no podrá liberar la Activity de la memoria. Para evitar este tipo de problemas lo más recomendable es definir el handler como una clase estática y guardar dentro del handler una WeakReference a la propia Activity para acceder a sus métodos.

    Asimismo hay que tener en cuenta que el hilo se sigue ejecutando aunque la Activity pase a segundo plano o se destruya, por ejemplo porque el usuario navega hacia otra pantalla. Para controlar esta casuística se puede utilizar un flag para saber dentro del hilo y el handler si la operación que se está llevando a cabo debe ser abortada. El código con estos cambios queda como sigue:

    package com.danielme.tipsandroid.runnable;
    
    import java.lang.ref.WeakReference;
    import java.net.URL;
    
    import android.app.Activity;
    import android.app.AlertDialog;
    import android.app.AlertDialog.Builder;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.util.Log;
    import android.view.View;
    import android.widget.ImageView;
    import android.widget.ProgressBar;
    
    import com.danieme.tipsandroid.runnable.R;
    
    public class MainActivity extends Activity {
    	ProgressBar progressBar;
    	ImageView imageView;
            //permite saber dentro del hilo y handler si se ha salido de la Activity
    	volatile boolean endThread = false;
    	MyHandler myHandler = new MyHandler(MainActivity.this);
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		progressBar = (ProgressBar) findViewById(R.id.progresss);
    		imageView = (ImageView) findViewById(R.id.imageView1);
    		progressBar.setVisibility(View.VISIBLE);
    
    		new Thread() {
    			public void run() {
    				Message message = new Message();
    				try {
    					// espera de 3 segundos para que de tiempo a ver el progress
    					Thread.sleep(3000);
    					if (!endThread) {
    						URL url = new URL(
    								"https://danielme.com/wp-content/uploads/2013/01/android-tip.png");
    						Bitmap bmp = BitmapFactory.decodeStream(url
    								.openConnection().getInputStream());
    
    						message.obj = bmp;
    						message.what = 1;
    					}
    				} catch (Exception ex) {
    					Log.e(MainActivity.class.toString(), ex.getMessage(), ex);
    					message.what = 2;
    				} finally {
    					if (!endThread) {
    						myHandler.sendMessage(message);
    					}
    				}
    			}
    
    		}.start();
    	}
    
    	@Override
    	protected void onPause() {
    		super.onPause();
    		endThread = true;
    	}
    
    	private static class MyHandler extends Handler {
    		private final WeakReference<MainActivity> myActivity;
    
    		public MyHandler(MainActivity myActivity) {
    			this.myActivity = new WeakReference<MainActivity>(myActivity);
    		}
    
    		@Override
    		public void handleMessage(Message msg) {
    			// la tarea en segundo plano ya ha terminado. Ocultamos el progreso.
    			myActivity.get().progressBar.setVisibility(View.GONE);
    			// si tenemos la imagen la mostramos
    			if (msg.what == 1 && msg.obj != null) {
    				myActivity.get().imageView.setImageBitmap((Bitmap) msg.obj);
    				myActivity.get().imageView.setVisibility(View.VISIBLE);
    			}
    			// si no, informamos del error
    			else {
    				Builder builder = new Builder(myActivity.get());
    				builder.setTitle(R.string.title);
    				builder.setIcon(android.R.drawable.ic_dialog_info);
    				builder.setMessage(R.string.error);
    				builder.setNeutralButton(myActivity.get()
    						.getString(R.string.ok), null);
    
    				AlertDialog alertDialog = builder.create();
    				alertDialog.show();
    				alertDialog.setCancelable(false);
    			}
    		}
    	}
    
    }
    

    El proyecto completo para Eclipse ADT se encuentra en Github. Para más información sobre cómo utilizar GitHub, consultar este artículo.

    << TIPS ANDROID

2 comentarios sobre “Tip Android #03: realizar tareas asíncronas con Runnable y Handler

  1. Así me gusta, muy bien explicado y en español o castellano. Ojalá lo hubiera encontrado antes y me hubiera ahorrado mucho tiempo. Gracias.
    PD: compartimos el 2º apellido

  2. Amigo, disculpa la confianza, pero yo tengo un problema con un código muy similar, y es que cuando ya cierro la aplicación el hilo sigue corriendo no sé porqué? he probado llamar al método hilo.stop(); dendro del método onDestroy(); de la activity y nada me puedes ayudar
    if (t==null)
    {
    t = new Thread() {

    public void run()
    {
    Log.i(«Activity», «Hilo corriendo»);

    while (true)
    {
    Log.i(«Activity», «Estoy Escuchando»);
    try {
    bytesIn = MyInStream.read(bufferIn);
    strTempIn = new String(bufferIn,0,bytesIn);
    strBufferIn += strTempIn;
    Log.i(«Activity HILO», «mensaje recibido: «+ strTempIn);
    } catch (IOException e) {
    e.printStackTrace();
    }
    messageHandler.sendMessage(Message.obtain(messageHandler, 1,bufferIn)); }//fin while
    };/ Fin de void run
    };//Fin de newThread
    }//Fin de If
    t.start();
    textViewLog.append(«\n»+»Conectado con RN42»);

Deja un comentario

Este sitio utiliza Akismet para reducir el spam. Conoce cómo se procesan los datos de tus comentarios.