IMPORTANT: Per accedir als fitxer de subversion: http://acacha.org/svn (sense password). Poc a poc s'aniran migrant els enllaços. Encara però funciona el subversion de la farga però no se sap fins quan... (usuari: prova i la paraula de pas 123456)

En resum, el més important a tenir en compte és:

Cada aplicació s'executa en el seu propi procés i tots els components de l'aplicació s'executen en aquest procés per defecte.
Qualsevol operació lenta o que bloquegi (per exemple una operació que esperi la finalització d'una operació I/O) s'ha de fer en un Thread nou, per tal d'evitar que 
la interfície d'usuari es ralentitzi.

Quan un component d'una aplicació s'inicia i l'aplicació no té cap component actualment en execució, el sistema operatiu Android inicia un nou procés Linux que es dedica a l'aplicació. Aquest procés té un sol fil d'execució, anomenat el main thread. Per defecte, tots els components de la mateixa aplicació s'executen al mateix procés i fil d'execució, si el component d'una aplicació s'inicia i ja existeix el fil d'execució principal de l'aplicació (perquè un altre component ja s'està executant), aleshores el nou component s'executa dins d'aquest fil d'execució principal.

En tot cas, es pot (i en certs casos que veurem és recomana o fins i tot és obligatori) executar diferents components de la mateixa aplicació en fils d'execució diferents.

Android, com la majoria de sistemes operatius actuals és un sistema operatiu multiprocés, és a dir, es possible executar múltiples processos al mateix temps, ja sigui utilitzant múltiples CPU al mateix temps o multiplexant en temps l'ús de la CPU.

Processos i Threads (fils d'execució)

Es pot controlar a quin procés pertany cada component d'una aplicació Android modificant el fitxer manifest.xml.

Els elements XML de cada tipus de component Android del fitxer de manifest:

<activity>, <service>, <receiver>, <provider> ...

Suporten l'atribut XML:

android:process

Que permet especificar el procés en el que s'executa el component.

Es pot establir aquest atribut a cada components de forma que diversos components comparteixin el mateix procés (ja és el que es fa per defecte) o per que treballin en processos diferents.

NOTA: També es pot establir el mateix procés per a components de diferents aplicacions, de forma que els components compartiran el mateix user id i els mateixos certificats.

L'element:

<application>

també suporta l'atribut:

android:process

per tal d'establir un valor per defecte que s'aplicara a tots els components.

Android pot decidir en qualsevol moment que cal aturar un procés, especialment quan les condicions de memòria lliure són dolents i cal alliberar recursos per als processos que l'usuari requereixi de forma més immediata. Conseqüentment tots els components que s'executen al mateix procés són destruïdes.

Quan el sistema operatiu Android decideix quin procés matar, ho fa donant-li un pes relatiu respecte al usuari. Per exemple, s'atura abans un procés que contingui activitats que ja no són visibles a la pantalla en comparació amb aquelles que ho són. La decisió però depèn de l'estat dels components del procés i s'explica en més detall als següents apartats.

Cicle de vida dels processos

Per defecte, el sistema operatiu mira de mantenir el procés d'una aplicació tant de temps com sigui possible. Per determinar quins processos són més importants que altres el sistema els classifica en una jerarquia d'importància basant-se en els components del procés i el seu estat.

Hi ha 5 nivells, ordenats per importància:

Procés en primer terme (foreground process)

És el procés actiu, el que esta utilitzant en aquest moment l'usuari. Un procés es considera d'aquesta categoria si es compleix alguna de les següents condicions:

  • Conté una activitat amb la que l'usuari està interaccionant (the Activity's onResume() method has been called).
  • Si el procés conté un Servei que està lligat a l'activitat amb la que l'usuari està interaccionant.
  • Si el procés conté un Servei que s'està executant en "foreground" ( el servei ha cridat el mètode startForeground()).
  • Si el procés conté un Servei que està executant un dels seus mètodes (callbacks) del cicle de vida (onCreate(), onStart(), o onDestroy()).
  • Si el procés conté un BroadcastReceiver que està executant el mètode: onReceive().

En general només uns pocs processos estan en aquest estat en un moment donat. Només es mata aquests processos com a últim recurs, és a dir, que no hi ha prou memòria per a continuar.

Procés visible

Un procés que no té cap component en el mode anterior, però que encara pot afectar el que l'usuari veu per pantalla. S'han de complir les següents condicions:

  • Un procés que conté una activitat que no és troba en foreground però que encara és visible per a l'usuari (quan el mètode onPause() ha estat cridat). Això pot passar si l'activitat en primer terme és un diàleg que permet que l'activitat es vegi al fons.
  • Un procés que conté un servei que està lligat a una Activitat que sigui visible.

Els processos visibles són considerats extremadament importants i no seran matats a no ser que sigui necessari per tal de mantenir en execució tots els processos de tipus primer terme.

Procés de tipus servei

Un procés que executa un servei que ha estat iniciat pel mètode startService() i no cau en cap de les categories anteriors.

Encara que aquests tipus de processos no estan relacionats directament amb res que l'usuari vegi directament, si que realitzen normalment tasques que són importants per l'usuari (per exemple executant un àudio o baixant un fitxer). Per aquesta raó el sistema mira sempre de no matar aquestes aplicacions excepte que no hi hagi prou memòria per mantenir en execució tots els processos en primer terme i els processos visibles.

Procés de tipus segon terme

Un procés que conté un Activitats que actualment no és visible per a l'usuari (l'activitat ha cridat el mètode onStop()). Aquests processos no tenen impacta directe en l'experiència de l'usuari i per tant el sistema mata aquests processos en qualsevol moment que necessiti alliberar memòria per a qualsevol procés que estigui dins de les anteriors categories.

Normalment hi ha força processos en aquesta categoria i per aquesta raó es guarden a una estructura de dades de tipus LRU (least recently used) per tal de garantir que el procés de l'activitat que ha estat utilitzat de forma més recent per part de l'usuari sigui l'últim en ser eliminat. Si una activitat implement els seus mètodes de cicle de vida correctament, i guarda el seu estat actual, eliminar el procés no ha de tenir cap implicació en la "user experience", ja que l'usuari navega endarrere cap a les activitats i l'activitat restaura el seu estat.

Procés buit

Un procés que no conté cap component actiu. L'únic raó de mantenir aquest tipus de procés és per cache, per tal de millorar els temps d'inici de les aplicacions.

Threads

Quan s'inicia una aplicació, el sistema crea un fil d'execució o thread anomenat main o fil d'execució principal.

Aquest thread és molt importants perquè s'encarrega de gestionar els esdeveniments relacionats amb la interfície gràfica. Aquí s'interactua amb els components del Android UI toolkit (components dels paquets android.widget i android.view). Per aquesta raó també s'ha conegut com a UI thread o fil d'execució de la interfície gràfica.

Worker threads

El sistema no crea un fil d'execució diferent per a cada instància d'un component Android. Tots els components s'executen al mateix fil d'execució, el fil principal.

Si la nostra aplicació està realitzant operacions intensives en recursos, el sistema es pot veure afectat si tot s'executa a aquest procés principal. Per exemple realitzar operacions lentes com accessos de xarxa o consultes a bases de dades poden bloquejar la interfície gràfica. Quan el fil principal està bloquejat, aleshores no es poden executar les operacions de "re-dibuixar" widgets i la interfície gràfica no funciona de forma àgil.

Des de la perspectiva de l'usuari l'aplicació sembla penjada. A més. si la interfície es bloquejada durant més de 5 segons pot aparèixer el diàleg:

"application not responding" (ANR)

A més cal tenir en compte que la Android UI Toolkit (llibreria d'interfície gràfica) no és thread-safe. No es poden fer manipulacions de la interfície gràfica des de fora del fil d'execució principal.

Cal complir les següents dos normes:

  • No bloquegeu mai el fil d'execució principal.
  • No accediu al Android UI Toolkit des de fora del fil d'execució principal.

Worker threads

Quan tingueu que realitzar una operació lenta cal executar-les en segon terme o en el que es coneix com a Worker thread, un fil d'execució independent del fil d'execució principal.

Vegem un exemple que descarrega una imatge i la mostra en un ImageView:

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
            mImageView.setImageBitmap(b);
        }
    }).start();
}

Compte però que aquest codi és parcialment correcte! Amb aquest codi complim la primera norma, que es no bloquejar el fil d'execució principal executant una operació lenta en un fil d'execució a part. A la línia:

mImageView.setImageBitmap(b);

Cal tenir en compte però que estem violant la segona norma:

No accedir al Android UI Toolkit des de fils d'execució que no siguin el principal.

Això pot provocar un comportament indefinit o inesperat de l'aplicació i que pot ser molt difícil de detectar i/o depurar.

Per tal de solucionar aquest problema, tenim diferents formes d'accedir al UI thread des dels altres fils d'execució. Els següents mètodes poden ser d'utilitat:

Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)

Vegeu un exemple de com solucionar el codi anterior utilitzant el mètode View.post(Runnable):

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

Ara aquesta implementació és "thread-safe".

Aquesta solució però no sempre és la millor si la complexitat de l'operació a realitzar és més important que la de l'exemple. Hi ha dos recomanacions per aquests casos:

  • Utilitzar un Handler (gestor) al worker thread per tal de processar els missatges de comunicació entre el thread principal i el fil treballador.
  • Extendre la classe AsyncTask.

Utilitzant AsyncTask

AsyncTask us permet realitzar tasques de forma asíncrona a la vostra interfície d'usuari. Realitza les operacions que bloquegen el sistema en un worker thread independent del UI thread i aleshores publica els resultats al fil principal sense la necessitat de gestionar els threads o els handlers.

Per a implementar AsyncTask cal heretar la classe:

AsyncTask http://developer.android.com/reference/android/os/AsyncTask.html

I implementar el mètode:

doInBackground()

Que s'executa en un pool de threads en segon terme. Per actualitzar la interfície gràfica cal implementar el mètode:

onPostExecute()

que proporciona el resultat del mètode doInBackground() al UI thread. S'utilitza el mètode:

execute()

des de la UI thread per executar la AsyncTask. Vegem un exemple:

 public void onClick(View v) {
    new DownloadImageTask().execute("http://example.com/image.png");
 }
 
 private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
    /** The system calls this to perform work in a worker thread and
      * delivers it the parameters given to AsyncTask.execute() */
    protected Bitmap doInBackground(String... urls) {
        return loadImageFromNetwork(urls[0]);
    }
    
    /** The system calls this to perform work in the UI thread and delivers
      * the result from doInBackground() */
    protected void onPostExecute(Bitmap result) {
        mImageView.setImageBitmap(result);
    }
 }

Ara el UI és thread-safe (segur) i el codi és més simple ja que se separa la part que s'ha de fer al worker thread de la part que s'ha de fer al UI thread.

Per a més informació podem llegir:

http://developer.android.com/reference/android/os/AsyncTask.html

Es poden especificar el tipus de paràmetres, els valors de progress i el valor final utilitzant genèrics:

  • El mètode doInBackground() s'executa automàticament en un worker thread.
  • Els mètodes onPreExecute(), onPostExecute(), i onProgressUpdate() s'invoken tots als UI thread.
  • El valor de retorn de doInBackground() s'envia a onPostExecute().
  • Es pot cridar el mètode publishProgress() en qualsevol moment al mètode doInBackground() per tal d'executar onProgressUpdate()al UI thread.
  • Es pot cancelar la tasca en qualsevol moment, des de qualsevol thread.

NOTA: Oco! Un problema que ens podem trobar en qualsevol moment al worker thread és un reinicii de l'activitat per exemple per un canvi en la configuració en temps d'execució (per exemple, l'orientació de pantalla) i pot destruir el worker thread Per veure com es poden persistit les tasques i com cancel·lar adequadament les tasques quan l'activitat és destruïda, podeu veure l'aplicació exemple Shelves

Vegeu també

Enllaços externs