Tareas en segundo plano AsyncTask Thread Programació De Dispositius Mòbils PRDM Cristobal Raya Giner 2015
Hilos de ejecución En una aplicación Android, todas las actividades, los servicios y los broadcast receivers se ejecutan en el mismo hilo de ejecución, el llamado hilo principal, main thread, o GUI thread. Un hilo de ejecución se puede definir de forma simple como un conjunto de procesos con diferentes tareas que se ejecutan consecutivamente. Con un solo hilo, los procesos han de ejecutarse secuencialmente, y en caso de que uno de ellos ocupara un tiempo excesivamente largo, podría llegar hasta bloquear la ejecución. Hilo principal Ejecución de los Procesos 5 s 30 s 15 s 20 s 5 + 30 + 15 + 20 = 70 s Para reducir el tiempo de ejecución y principalmente reducir la posibilidad del bloqueo de la ejecución, se utiliza la multitarea que permite ejecutar varios hilos simultáneamente y ejecutar simultáneamente varios procesos. El sistema Android monitoriza las operaciones realizados en el hilo principal y detecta aquellas que superen los 5 segundos, y avisa con el mensaje Application Not Responding que permite decidir al usuario si esperar o forzar el cierre de la aplicación. Hilos multitarea Los hilos multitarea, también se conocen como tareas en segundo plano o hilos secundarios. De esta forma se ejecutan varios procesos a la vez y en caso de que uno de ellos pueda llegar a bloquearse, no tiene por qué afectar a los demás procesos. tiempo proceso más largo 30 s Hilo principal Hilo 1 Hilo 2 Hilo 3 Hilo 4 5 s 30 s 15 s 20 s
Para realizar tareas en segundo plano, el sistema Android proporciona principalmente dos alternativas: Crear directamente un nuevo hilo para ejecutar las tareas mediante las clases Thread y Handler que proporciona java (java.lang.thread) Utilizar la clase auxiliar AsyncTask de Android (android.os.asynctask<params, Progress, Result>) El problema que aparece al trabajar con hilos secundarios es que no se puede hacer referencia directa a componentes del hilo principal desde los hilos secundarios. Por ejemplo no podríamos actualizar el valor de un TextView del hilo principal desde el hilo secundario. Pero existen varias alternativas para solucionarlo en función del tipo de hilo, entre ellas el uso de Handler o runonuithread() en Threads, o el uso publishprogress() y onprogressupdate() en AsynTask. Las recomendaciones de uso de Threads o AsynTask, son entre otras: Thread: Operaciones de red que requieran de moderada a gran cantidad de datos, como descargas y subidas de datos Tareas que requieran un alto uso de la CPU Tareas en que sea necesario controlar el uso de la CPU respecto al hilo principal Necesita el uso de Handlers para comunicarse con el hilo principal (UI Thread) AsyncTask: Operaciones simples de red que no requieran gran cantidad de datos Tareas sobre disco que requieran pocos milisegundos Facilita la comunicación con el hilo principal y no es necesario el uso de Handlers. Utiliza más tiempo de CPU que Thread Thread La clase Thread es la clase que proporciona java (java.lang.thread) que nos permite ejecutar hilos secundarios. Los hilos no comparten información con la GUI principal directamente, y es necesario utilizar intermediarios. Para ello podemos utilizar varias alternativas, como el uso de objetos Handler o el método runonuithread(), que se encargarán de compartir la información con el hilo principal. Una de las métodos más rápidos es utilizar la interfície runnable(), que implementará el método run(), y dentro del método se ejecutará el código correspondiente a la comunicación entre el Thread secundario y el hilo principal. Como alternativa se puede utilizar el objeto Handler, y utilizar su método post() donde mediante la interfície Runnable() también puede comunicarse con el hilo principal.
Una vez creado el Thread, se ha de activar mediante el método start(). El siguiente ejemplo crea un Thread en el método oncreate() de la actividad, y modifica el texto de dos TextView, uno mediante Handler.post() y otro mediante runonuithread(). public class MainActivity extends AppCompatActivity { TextView mensajepost, mensajeonui; Handler mensajehandler=new Handler(); String msgstring; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mensajepost=(textview) findviewbyid(r.id.textview); mensajeonui=(textview) findviewbyid(r.id.textview2); new Thread(new Runnable() { msgstring="mensaje del Thread a través de "; mensajehandler.post(new Runnable() { mensajepost.settext(msgstring+"post"); ); runonuithread(new Runnable() { mensajeonui.settext(msgstring+"runonuithread"); ); ).start(); Para una mejor organización disminuyendo el código insertado en oncreate(), es aconsejable crear el Thread como una nueva clase extensión de Thread y después en oncreate() crear un objeto de dicha clase. public class MainActivity extends AppCompatActivity { TextView mensajepost, mensajeonui; String msgstring; Handler mensajehandler=new Handler(); Hilosecundario hilo; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate);
setcontentview(r.layout.activity_main); mensajepost=(textview) findviewbyid(r.id.textview); mensajeonui=(textview) findviewbyid(r.id.textview2); hilo=new Hilosecundario(); hilo.start(); private class Hilosecundario extends Thread{ msgstring="mensaje del Thread a través de "; mensajehandler.post(new Runnable() { mensajepost.settext(msgstring+"post"); ); runonuithread(new Runnable() { mensajeonui.settext(msgstring+"runonuithread"); ); Otro ejemplo en que continuamente está ejecutándose de cálculo de rotación y este se aplica a dos TextView public class MainActivity extends AppCompatActivity { TextView mensajepost, mensajeonui; float rotacion=0; Handler mensajehandler=new Handler(); Hilorotacion hilo; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mensajepost=(textview) findviewbyid(r.id.textview); mensajeonui=(textview) findviewbyid(r.id.textview2); hilo=new Hilorotacion(); hilo.start(); private class Hilorotacion extends Thread{ while(true){ try{ Thread.sleep(1000); catch (InterruptedException e){ rotacion=rotacion+45; if (rotacion==360) {rotacion=0; mensajehandler.post(new Runnable() { mensajepost.settext("post Rotacion:" +Float.toString(rotacion)); mensajepost.setrotation(rotacion); ); runonuithread(new Runnable() {
); mensajeonui.settext("runonuithread Rotacion: " +Float.toString(360-rotacion)); mensajeonui.setrotation(360-rotacion); Para no tener que utilizar la interfície Runnable() se pueden utilizar los mensajes del objeto Handler, mediante los cuales el hilo principal puede decidir qué hacer con ellos. Habiendo creado un Handler, en el Thread se envía un mensaje mediante el método obtainmessage() y sendtotarget() del Handler. En él se envía quien ha enviado el mensaje y el mensaje: Handler.obtainMessage(int what, Object obj).sendtotarget(); Aunque también se pueden enviar dos argumentos más de forma opcional Handler.obtainMessage(int what, int arg1, int arg2, Object obj).sendtotarget(); En el hilo principal se inicia el Thread, y se crea el Handler. En el Handle se añade el método handlemessage(msg) con el que se gestionarán los mensajes recibidos del Thread, utilizando los argumentos enviados por el Thread. import android.os.message; public class MainActivity extends AppCompatActivity { TextView txt1,txt2; Handler intermediario; private Hilosecundario Hilomsg; final int mensaje1=1; final int mensaje2=2; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); txt1=(textview) findviewbyid(r.id.textview); txt2=(textview) findviewbyid(r.id.textview2); Hilomsg = new Hilosecundario(); Hilomsg.start(); intermediario = new Handler(){ public void handlemessage (Message msg){ if (msg.what==mensaje1){ String leemsg1 = (String) msg.obj; txt1.settext(leemsg1); if (msg.what==mensaje2){ String leemsg2 = (String) msg.obj; txt2.settext(leemsg2); ;
private class Hilosecundario extends Thread{ int contador=0; String msg1="mensaje 1: "; String msg2="mensaje 2: "; while (true){ try{ intermediario.obtainmessage(mensaje1,msg1+integer.tostring(contador)).send ToTarget(); Thread.sleep(500); contador++; intermediario.obtainmessage(mensaje2,msg2+integer.tostring(contador)).send ToTarget(); Thread.sleep(500); contador++; catch (InterruptedException e){ break; Utilizar la clase Thread de Java puede ser un poco complejo de interpretar, aunque es el método recomendado para tratar gran cantidad de datos y para tareas complejas. En caso de necesitar tareas sencillas y con pocos datos, en Android podemos utilizar la clase AsyncTask. AsyncTask La clase AsyncTask que proporciona Android, permite realizar tareas e interactuar con el hilo principal igual que con Thread, pero de una forma más organizada y sin el uso de Handlers ni runonuithread(). Al crear una instancia de la clase AsyncTask se necesitan definir tres tipos de datos genéricos: Los tres tipos necesarios son: android.os.asynctask<params, Progress, Result> Parámetros (Params). El tipo de los parámetros que recibirá como entrada tarea en segundo plano en ejecución (doinbackground()) Progreso (Progress). El tipo utilizado durante la tarea en segundo plano para indicar el progreso que se recibirá como parámetro en el método onprogressupdate() y que se incluirá como parámetro en el método publishprogress() Resultado (Result). El tipo que se devolverá como resultado al finalizar la tarea, que será el tipo retornado por doinbackground() y recibido por onpostexecute() Si alguno de los tipos no son necesarios, se pueden definir del tipo Void: private class MyTask extends AsyncTask<Void, Void, Void> {...
La clase está organizada en diferentes métodos en función del estado de la tarea: onpreexecute(): Se ejecutará antes del código principal de la tarea y se utilizará normalmente para inicializar y preparar la ejecución de la tarea doinbackground(): Ejecuta el código principal de la tarea onprogressupdate(): Se ejecuta cada vez que llamemos al método publishprogress() desde el método doinbackground() onpostexecute(): Se ejecuta al finalizar la tarea, al finalizar el método doinbackground() oncancelled(): Se ejecutará al cancelar la ejecución de la tarea antes de la finalización normal private class TareaAsyncTask extends AsyncTask <Byte,Integer,Long> { protected void onpreexecute() { super.onpreexecute(); protected Long doinbackground(byte... params) { return null; protected void onprogressupdate(integer... values) { super.onprogressupdate(values); protected void onpostexecute(long along) { super.onpostexecute(along); protected void oncancelled(long along) { super.oncancelled(along); La siguiente figura expresa de forma gráfica la ejecución de una tarea con AsyncTask onpreexecute() onprogressupdate() onpostexecute() publishprogress() doinbackground() oncancelled() La tarea se inicia con el método AsyncTask.execute (), y se puede cancelar con AsyncTask.cancel ()
En el siguiente ejemplo se utilizará un AsynTask para incrementar un contador en el intervalo de tiempo indicado. En el layout se utilizarán un TextView para mostrar el valor, un EditText para introducir el valor del intervalo, un botón para iniciar la tarea y otro para cancelarla. Al pulsar el botón de inicio, si la tarea no está activa, se creará la tarea enviando el intervalo de tiempo cuando se ejecute mediante Tarea.execute(intervalo). Este intervalo lo recibirá la tarea AsyncTask mediante el tipo params. Al pulsar el botón de cancelar se cancela la ejecución y elimina la tarea, pero al indicar false en la cancelación (Tarea.cancel (false)), deja que finalice la ejecución del código. En la creación de la tarea se utilizarán todos los tipo (Params, Progress, Result). En el método onpreexecute() de inicializaciones se creará un String con la parte inicial del mensaje. En el código principal de la tarea de doinbackground(), realizará un retardo con el intervalo que envió el hilo principal al executar la tarea, recibido como parámetro. Creará el mensaje y enviará al método onprogressupdate() mediante publishprogress(). Al poder cancelarse la tarea, es necesario comprobar si se ha cancelado para no continuar la ejecución, ya que en caso se cancelar y continuar la ejecución aparecería un error. Finalmente envía el valor del contador al método oncancelled(). En onprogressupdate() recibe el valor del contador enviado por doinbackground(), mediante publishprogress(), y modifica el texto del hilo principal. En oncancelled() recibe el último valor del contador al finalizar doinbackground(), y muestra un Toast. El método onpostexecute() no realiza nada ya que al haber un bucle infinito en el código de doinbackground(), nunca dejará este de ejecutarse y onpostexecute() no ejecutará nunca ningún código. public class MainActivity extends AppCompatActivity { Button inicio,cancela; TextView texto; EditText retardo; Boolean tareaactiva=false; Integer contador=0; Integer espera; String mensaje; TareaSincrona Tarea; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate);
setcontentview(r.layout.activity_main); inicio=(button) findviewbyid(r.id.iniciar); cancela=(button)findviewbyid(r.id.cancela); texto=(textview)findviewbyid(r.id.textview); retardo=(edittext)findviewbyid(r.id.retardoms); inicio.setonclicklistener(new View.OnClickListener() { public void onclick(view v) { if (tareaactiva==false){ espera=integer.parseint(retardo.gettext().tostring()); Tarea=new TareaSincrona(); Tarea.execute(espera); tareaactiva=true; ); cancela.setonclicklistener(new View.OnClickListener() { public void onclick(view v) { if (tareaactiva==true){ Tarea.cancel(false); //false deja finalizar la tarea Tarea=null; tareaactiva=false; ); private class TareaSincrona extends AsyncTask <Integer,String, Integer>{ protected void onpreexecute() { mensaje="contador: "; protected Integer doinbackground(integer... params) { while (true){ SystemClock.sleep(params[0]); contador++; String msg=mensaje+integer.tostring(contador); publishprogress(msg); //Envia a onprogressupdate() if(iscancelled()) break; // NECESARIO si se cancela!!! return contador; //Envia a oncancelled() y onpostexecute() protected void onprogressupdate(string... values) { texto.settext(values[0]); //Recibe de doinbackground() protected void onpostexecute(integer integer) { //Como el código del método doinbackground contiene un bucle //infinito, no dejará de ejecutarse nunca de forma normal protected void oncancelled(integer integer) { String ultimocnt=integer.tostring(integer); //Recibe de doinbackground() Toast.makeText(MainActivity.this, "Tarea cancelada! Contador: "+ultimocnt,toast.length_long).show();