PDM Avanzado Threads

De MediaWiki
Saltar a: navegación, buscar

Introdución

Máis información en: http://developer.android.com/guide/components/processes-and-threads.html


Os threads vannos permitir ter diferentes fíos de execución, separados do fío principal (chamado main) onde se atenden os eventos do usuario na interface.

É moi importante ter dúas consideracións cando estamos a manexar o thread principal:

  • Non deixar bloqueado o thread principal, por exemplo, cargando unha imaxe.
  • Non se pode acceder os elementos da interface (botóns,….) dende un fío diferente ao principal.


O primeiro é necesario xa que podemos provocar un ANR (Application Not Responding), no que aparece un dialogbox indicando que a aplicación deixou de funcionar.

PDM Avanzada Threads 1.jpg

Imaxinemos que queremos cargar unha imaxe dende Internet. Isto pode levar moito tempo, e a aplicación non pode esperar a cargar a imaxe.

Para isto, creamos un novo fío de execución.

Temos varias formas de facelo,con AsyncTask e con Threads.

Threads

Cando creamos un obxecto da clase Thread podemos sobrescribir o método run ou crealo mandando no construtor un obxecto de interface runable que implemente o método run.

Por exemplo:

  1. private void crearThread(){
  2.         Thread fio = new Thread(){
  3.                
  4.                 public void run(){
  5.                         for (int a=0;a<10;a++){
  6.                                 try {
  7.                                         Thread.sleep(1000);
  8.                                 Log.i("THREAD",String.valueOf(a));
  9.                                 } catch (InterruptedException e) {
  10.                                         // TODO Auto-generated catch block
  11.                                         e.printStackTrace();
  12.                                 }
  13.                                 }
  14.                         }       // FIN DO RUN
  15.                        };
  16.                 fio.start();
  17.  
  18.         }

Nota: A liña 7 indica que o Thread 'durma' durante 1 segundo (1000 mseg).

A outra forma sería:

  1. private void crearThreadRunable(){
  2.         Thread fio = new Thread(new Runnable() {
  3.                        
  4.                         public void run() {
  5.                                 // TODO Auto-generated method stub
  6.                         for (int a=0;a<10;a++){
  7.                                 try {
  8.                                         Thread.sleep(1000);
  9.                                         Log.i("THREAD",String.valueOf(a));
  10.                                 } catch (InterruptedException e) {
  11.                                         // TODO Auto-generated catch block
  12.                                         e.printStackTrace();
  13.                                 }
  14.                         }       // Fin do for                  
  15.                         } //Fin do run()
  16.                    } //Fin do runnable
  17.         ); // Fin no new Thread
  18.        
  19.         fio.start();
  20.     }   // FIN DO METODO

Nestes dous exemplos estamos 'parando' a execución do Thread un segundo (sleep(1000)) e imprimindo no log a valor do contador a.

Agora supoñamos que queremos modificar un texto (TextView) cos datos do contador (variable a).

Para iso temos que ter unha referencia da caixa de texto dentro do fío e iso non é posible.

O seguinte código da un erro xa que non podemos referenciar á caixa de texto dentro de fío.

  1.         private void crearThread(){
  2.                final TextView texto = (TextView)findViewById(R.id.txtContador);
  3.                Thread fio = new Thread(){
  4.                
  5.                 public void run(){
  6.                         for (int a=0;a<10;a++){
  7.                         try {
  8.                                         Thread.sleep(1000);
  9.                                         texto.setText(String.valueOf(a));
  10.                                 } catch (InterruptedException e) {
  11.                                         // TODO Auto-generated catch block
  12.                                         e.printStackTrace();
  13.                                 }
  14.                         }
  15.                 }
  16.                };
  17.                fio.start();
  18.  
  19.         }       // FIN DO crearThread


Para solucionalo, temos a opción de usar un Handler, que vén a ser coma unha ponte entre un fío e o fío principal.

Paso de mensaxes

Máis información en: http://developer.android.com/reference/android/os/Handler.html

Nota: A clase Handler pertence ao paquete android.os, cando importedes a clase escoller android.os.Handler.


Como indicamos antes non podemos referenciar ningún elemento da interface dentro dun fío.

Nos casos en que isto sexa necesario teremos que utilizar un AsyncTask ou un Thread con paso de mensaxes.

Un Handler é unha ponte para pasar información dende o Thread a un procedemento no que podemos acceder ao fío principal.

  1.         private Handler ponte = new Handler(){
  2.             @Override
  3.             public void handleMessage(Message msg) {
  4.                        
  5.                 }
  6.        
  7.         }; // Fin do Handler


O parámetro msg vai recibir información dende o fío, e dende o Handler si imos poder referenciar os elementos gráficos da UI.

  1.         private Handler ponte = new Handler(){
  2.             @Override
  3.             public void handleMessage(Message msg) {
  4.                 TextView texto = (TextView)findViewById(R.id.txtContador);
  5.                 texto.setText(String.valueOf(msg.arg1));
  6.                 }
  7.         }; // Fin do Handler
  1. private void crearThread(){
  2.                
  3.         Thread fio = new Thread(){
  4.                
  5.                 public void run(){
  6.                         for (int a=0;a<10;a++){
  7.                                 try {
  8.                                         Thread.sleep(1000);
  9.                                         Message msg = new Message();
  10.                                         msg.arg1=a;
  11.                                         ponte.sendMessage(msg);
  12.                                 } catch (InterruptedException e) {
  13.                                         // TODO Auto-generated catch block
  14.                                         e.printStackTrace();
  15.                                 } // fin do catch
  16.                         } // Fin do for
  17.                 } // Fin do run
  18.         }; // fin do new Thread
  19.         fio.start();
  20.  
  21. }       // Fin do crearThread


Fixarse como o msg pódese utilizar para enviar datos de moi diversas formas:

  • msg.setData(data): sendo data un obxecto da clase Bundle (almacena pares da datos cos métodos setTIPODATO e recupera con getTIPODATO).
  • msg.obj: un Object
  • msg.arg1: un enteiro
  • msg.arg2: outro enteiro.


Ata o de aquí non hai problema ou si....se o deixades así podedes ver coma vos da un aviso na clase Handle:

PDM Avanzada Threads 2.jpg


Para solucionar o problema temos que ter unha referencia débil da Activity na que queremos acceder ós elementos gráficos.

Debido a que o Handler manexa unha cola de mensaxes, é recomendable que sexa definido como Static, xa que dita cola é compartida por todos os obxectos handler que teñamos, é o procesar dita mensaxe non pode ser destruída polo proceso de garbagecollection se o handler non está definido como de clase.

Se cambiamos sen máis a clase a ‘Static’ imos atopar o problema de que cando queiramos referenciar un método ou un obxecto fora da clase Handler (que estea definido na clase Activity), non podemos facelo.

Podedes facer a proba:

  1.         private static Handler ponte = new Handler(){
  2.             @Override
  3.             public void handleMessage(Message msg) {
  4.                 TextView texto = (TextView)findViewById(R.id.txtContador);
  5.                 texto.setText(String.valueOf(msg.arg1));
  6.                 }
  7.         }; // Fin do Handler


Neste caso sería máis conveniente utilizar a clase Asynctask (a veremos a continuación), pero a modo de curiosidade imos ver como podemos solucionar o problema:

Para solucionalo, temos que facer que exista unha referencia ‘débil’ da Activity principal dentro do Handler.

Para facelo temos que crear un construtor no handler onde lle imos pasar "this" como parámetro e que será a referencia débil:

Cambiamos polo tanto a forma anterior e creamos un obxecto dunha clase Handler definida previamente:

Nota: A clase activity principal neste exemplo se chama "OutraActividade". Deberedes cambiala en función do voso nome.

Código a poñer dentro da activity de nome OutraActividade.java Pasamos de:

  1.         private Handler ponte = new Handler(){
  2.             @Override
  3.             public void handleMessage(Message msg) {
  4.                 TextView texto = (TextView)findViewById(R.id.txtContador);
  5.                 texto.setText(String.valueOf(msg.arg1));
  6.                 }
  7.        
  8.         }; // Fin do Handler

A

  1. private static class ClassPonte extends Handler {
  2.                
  3.         private WeakReference<OutraActividade> mTarget=null;
  4.                        
  5.         ClassPonte(OutraActividade target) {
  6.                 mTarget = new WeakReference<OutraActividade>(target);
  7.         }
  8.  
  9.         @Override
  10.         public void handleMessage(Message msg) {
  11.                        
  12.                 OutraActividade target = mTarget.get();
  13.                        
  14.                 TextView texto1 = (TextView)target.findViewById(R.id.txtContador);
  15.                 texto1.setText(String.valueOf(msg.arg1));
  16.         }
  17. }; // Fin do Handler
  18.  
  19. private ClassPonte ponte = new ClassPonte(this);


Fixarse coma dentro da clase ClassPonte, no método handleMessage podemos facer referencia a método non static grazas o obxecto target:

  1. OutraActividade target = mTarget.get();

Caso práctico

O obxectivo desta práctica e ver como funciona un Thread e como podemos usar un Handler para pasar mensaxes dende o fío a activity e poder acceder os elementos gráficos da mesma.

Consta dun botón cun TextView onde se vai a amosar un contador. Cando se preme sobre o botón o contador empeza a funcionar.

PDM Avanzada Threads 3.jpg


Creamos a activity

  • Nome do proxecto: UD7_01_Threads
  • Nome da activity: UD7_01_Threads.java


Código do layout xml

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.    xmlns:tools="http://schemas.android.com/tools"
  3.    android:layout_width="match_parent"
  4.    android:layout_height="match_parent"
  5.    tools:context="${relativePackage}.${activityClass}" >
  6.  
  7.     <TextView
  8.        android:id="@+id/UD7_01_txtContador"
  9.        android:layout_width="wrap_content"
  10.        android:layout_height="wrap_content"
  11.        android:layout_centerHorizontal="true"
  12.        android:layout_centerVertical="true"
  13.        android:text="0" />
  14.  
  15.     <Button
  16.        android:id="@+id/UD7_01_btnCrono"
  17.        android:layout_width="wrap_content"
  18.        android:layout_height="wrap_content"
  19.        android:layout_alignParentTop="true"
  20.        android:layout_centerHorizontal="true"
  21.        android:layout_marginTop="21dp"
  22.        android:text="INICIAR CRONO" />
  23.  
  24. </RelativeLayout>


Código da clase UD7_01_Threads
Obxectivo: Utilizar un Thread con paso de mensaxes mediante Handler.

NOTA: Neste exemplo o código da clase Hander e o código da clase Thread están definidos dentro da propia Activity. Nada impide que se definan en clases separadas.

  1. import java.lang.ref.WeakReference;
  2.  
  3. import android.app.Activity;
  4. import android.os.Bundle;
  5. import android.os.Handler;
  6. import android.os.Message;
  7. import android.view.View;
  8. import android.view.View.OnClickListener;
  9. import android.widget.Button;
  10. import android.widget.TextView;
  11. import android.widget.Toast;
  12.  
  13. public class UD7_01_Threads extends Activity {
  14.  
  15.         private final int TEMPO_CRONO=10;
  16.        
  17.         // INICO DA CLASE HANDLER
  18.         private static class ClassPonte extends Handler {
  19.  
  20.                 private WeakReference<UD7_01_Threads> mTarget = null;
  21.  
  22.                 ClassPonte(UD7_01_Threads target) {
  23.                         mTarget = new WeakReference<UD7_01_Threads>(target);
  24.                 }
  25.  
  26.                 @Override
  27.                 public void handleMessage(Message msg) {
  28.  
  29.                         UD7_01_Threads target = mTarget.get();
  30.                         TextView texto1 = (TextView) target
  31.                                         .findViewById(R.id.UD7_01_txtContador);
  32.  
  33.                         if (msg.arg2==1){
  34.                                 Toast.makeText(target.getApplicationContext(), "ACABOUSE O CRONO", Toast.LENGTH_LONG).show();
  35.                                 texto1.setText(String.valueOf(0));
  36.                         }
  37.                         else {
  38.                                 texto1.setText(String.valueOf(msg.arg1));
  39.                         }
  40.                 }
  41.         }; // Fin do Handler
  42.  
  43.         private ClassPonte ponte = new ClassPonte(this);
  44.  
  45.        
  46.         private class MeuFio extends Thread{
  47.                
  48.                 public void run(){
  49.                 for (int a=0;a<=TEMPO_CRONO;a++){
  50.                         try {
  51.                                         Thread.sleep(1000);
  52.                                         Message msg = new Message();
  53.                                         msg.arg1=a;
  54.                                 ponte.sendMessage(msg);
  55.                         } catch (InterruptedException e) {
  56.                                 // TODO Auto-generated catch block
  57.                                 e.printStackTrace();
  58.                         }
  59.                 }
  60.                 Message msgFin = new Message();
  61.                 msgFin.arg2=1;
  62.                         ponte.sendMessage(msgFin);
  63.                 }       // FIN DO RUN
  64.         };
  65.          
  66.         private Thread meuFio;
  67.        
  68.         private void xestionarEventos(){
  69.                 Button btnCrono = (Button)findViewById(R.id.UD7_01_btnCrono);
  70.                 btnCrono.setOnClickListener(new OnClickListener() {
  71.                        
  72.                         @Override
  73.                         public void onClick(View v) {
  74.                                 // TODO Auto-generated method stub
  75.                                
  76.                                 if ((meuFio==null) || (!meuFio.isAlive())){
  77.                                         Toast.makeText(getApplicationContext(), "INICIANDO FIO", Toast.LENGTH_LONG).show();
  78.                                         meuFio = new MeuFio();
  79.                                         meuFio.start();
  80.                                 }
  81.                                 else {
  82.                                         Toast.makeText(getApplicationContext(), "NON TE DEIXO INICIAR O FIO ATA QUE REMATE :)", Toast.LENGTH_LONG).show();
  83.                                 }
  84.                         }
  85.                 });
  86.         }
  87.        
  88.     @Override
  89.     protected void onCreate(Bundle savedInstanceState) {
  90.         super.onCreate(savedInstanceState);
  91.         setContentView(R.activity_ud7_01__threads);
  92.        
  93.         xestionarEventos();
  94.     }
  95. }
  • Liña 15: Definimos o tempo do crono. Cando chegue a ese valor para.
  • Liñas 18-41: Definimos a clase Handler que vai capturar as mensaxes enviadas dende o Thread.
  • Liñas 33-36: Utilizamos a propiedade arg2 do Message para saber cando acaba o contador (chega ao valor indicado na liña 15). Dende o Thread imos enviar un valor igual a 1 para indicalo.
  • Liña 38: Asinamos o valor enviado na propiedade arg1 do Message ao EditText da activity principal.
  • Liña 43: Definimos o obxecto da clase Handler que vai utilizar o Thread.
  • Liñas 46-64: Definimos a clase Thread que vai utilizar a activity principal para o contador.
  • Liña 53: Dentro do bucle enviamos en Message na propiedade arg1 o valor do contador.
  • Liñas 60-62: Cando sae do bucle enviamos na propiedade arg2 o valor 1 para indicar á ponte que xa rematou o fío.
  • Liña 66: Definimos o obxecto da clase Thread.
  • Liña 73: Xestionamos o evento Click do botón.
  • Liñas 76-80: En caso de que o fío non se iniciara nunca ou se xa rematou, instanciamos un novo fío.





-- Ángel D. Fernández González e Carlos Carrión Álvarez -- (2014).