Programació de comunicacions

Cada vegada és més habitual que les aplicacions facin ús de la comunicació per Internet, integrant-se en les xarxes socials o utilitzant serveis web. En aquest apartat veureu com fer ús de les comunicacions del dispositiu per tal de comunicar-vos amb l’exterior.

Comunicacions en Android

El primer pas per treballar amb l’API de comunicacions en Android és demanar els permisos per a la vostra aplicació al document de manifest. Això ho podeu fer afegint les següents línies en l’esmentat document:

  1. <uses-permission android:name="android.permission.INTERNET"/>
  2. <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

Això permet a la vostra aplicació tenir accés a Internet (obrir sockets de xarxa) i a informació de l’estat de la xarxa (per exemple, per saber si el Wi-Fi està activat o desactivat).

Abans de fer un intent de connexió a la xarxa, hauríeu de comprovar si el dispositiu té una connexió de xarxa disponible i funcionant correctament. Per això heu de fer servir objectes de la classe ConnectivityManager i NetworkingInfo de la següent manera:

  1. //Obtenim un gestor de les connexions de xarxa
  2. ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
  3.  
  4. //Obtenim l'estat de la xarxa
  5. NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
  6.  
  7. //Si està connectat
  8. if (networkInfo != null && networkInfo.isConnected()) {
  9. //Xarxa OK
  10. Toast.makeText(this,"Xarxa ok", Toast.LENGTH_LONG).show();
  11. } else {
  12. //Xarxa no disponible
  13. Toast.makeText(this,"Xarxa no disponible", Toast.LENGTH_LONG).show();
  14. }

Amb getSystemService() obteniu un objecte ConnectivityManager que serveix per gestionar la connexió de xarxa. Amb aquest objecte podeu obtenir un objecte NetworkInfo, amb getActiveNetworkInfo() que retorna una instància de NetworkInfo, que representa la primera interfície de xarxa que pot trobar o un null si cap connexió està connectada.

A NetworkInfo teniu el mètode isConnected(), que indica si existeix connectivitat a la xarxa i si és possible establir connexions.

En el cas que vulgueu obtenir informació individual dels diferents tipus de xarxes, ho podeu fer de forma individual amb getNetworkingInfo(), amb un argument que és una constant que indica el tipus de xarxa que voleu comprovar, com es pot veure en el següent codi:

  1. //Obtenim l'estat de la xarxa mòbil
  2. NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
  3. boolean connectat3G = networkInfo.isConnected();
  4.  
  5. //Obtenim l'estat delaxarxaWi-Fi
  6. networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
  7. boolean connectatWifi = networkInfo.isConnected();

És molt important que sempre comproveu la connexió a la xarxa i no doneu per fet que la xarxa està disponible. Penseu que les operacions de xarxa a un dispositiu mòbil solen ser mòbils, 3G o Wi-Fi, i existeixen possibilitats reals de caiguda de la connexió. Per tant, heu de fer aquestes comprovacions quan engegueu l’aplicació o quan hi torneu després d’haver-ne sortit. El lloc ideal per fer-ho seria al mètode onStart() de la vostra aplicació.

Imagineu que engegueu l’aplicació i es fa la comprovació de connexió al començament, i trobeu que existeix connexió. Si l’usuari executa l’aplicació de configuració del dispositiu i activa el mode d’avió, la vostra aplicació seguirà funcionant correctament, ja que quan l’usuari torni a la vostra aplicació onStart() tornarà a ser cridat i detectareu que la xarxa està desconnectada.

Però què passa quan la connexió a Internet canvia d’estat mentre esteu dins de la vostra aplicació? Això pot passar perquè el dispositiu perdi la cobertura o la connexió a la xarxa. Com que no es torna a cridar onStart(), no podríeu comprovar la connectivitat. Per això, el que heu de fer és registrar un broadcastReceiver (receptor broadcast o multidifusió) que escoltarà els canvis en l’estat de la connexió i comprovarà quin és l’estat de la xarxa.

En primer lloc, creeu una classe que hereti de BroadcastReceiver i implementeu el mètode abstracte onReceive().

  1. public class ReceptorXarxa extends BroadcastReceiver {
  2.  
  3. @Override
  4. public void onReceive(Context arg0, Intent arg1) {
  5. //Actualitzar l'estat de la xarxa
  6. ActualitzaEstatXarxa();
  7. }
  8. }

Si aquesta classe es farà servir dins de la vostra activitat no cal que creeu la classe com a un fitxer .java independent al vostre projecte, pot estar definida dins de la classe de la vostra aplicació. Dins del mètode onReceive() actualitzareu l’estat de la xarxa a la vostra aplicació.

El següent pas és registrar el receptor de broadcast perquè escolti els missatges relacionats amb els canvis de la connectivitat de la xarxa. Per fer-ho, creeu una instància de la classe receptora i registreu-la amb un filtre d’intent per escoltar únicament els missatges de broadcast sobre la connectivitat del dispositiu.

  1. private ReceptorXarxa receptor;
  2.  
  3. @Override
  4. publicvoid onCreate(Bundle savedInstanceState) {
  5. (...) //Resta de codi onCreate()
  6.  
  7. IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
  8.  
  9. receptor = new ReceptorXarxa();
  10. this.registerReceiver(receptor, filter);

El mètode registerReceiver() registra un receptor de broadcast per executar-se al fil principal de l’aplicació. El receptor de broadcast escoltarà qualsevol intent de broadcast que coincideixi amb el filtre definit en el fil principal de l’aplicació.

En aquest moment, els missatges de broadcast que anunciïn els canvis en la connectivitat de la xarxa seran rebuts pel vostre receptor, el qual actualitzarà l’estat de la xarxa de l’aplicació.

Tenir un receptor de broadcast que és cridat constantment o innecessàriament pot derivar en un consum excessiu dels recursos del sistema (entre d’altres, de la bateria). Si declareu el receptor de broadcast en el document de manifest, aquest pot engegar l’aplicació automàticament, encara que aquesta no estigui en execució.

Per aquest motiu, el més adequat seria registrar el receptor de broadcast quan es crea l’aplicació onCreate() i donar-lo de baixa al mètode onDestroy().

  1. public void onDestroy() {
  2. super.onDestroy();
  3.  
  4. //Donem de baixa el receptor de broadcast quan es destrueix l'aplicació
  5. if (receptor != null) {
  6. this.unregisterReceiver(receptor);
  7. }
  8. }

Mostrar pàgines web amb un 'widget'

Possiblement, el servei de comunicació més utilitzat avui en dia són les pàgines web, i per aquest motiu començarem mostrant com podem visualitzar-les dins les nostres aplicacions.

El procediment és molt senzill perquè hi ha un component anomenat WebView que ja fa gairebé tota la feina, només cal dir-li quina adreça volem visitar.

Creeu un nou projecte Android. Com que la vostra aplicació accedirà, lògicament, a Internet, heu d’afegir-hi el permís corresponent al fitxer AndroidManifest.xml (tot seguit del uses-sdk):

  1. <uses-permissionandroid:name="android.permission.INTERNET"/>

Guardeu i aneu al layout.xml per afegir el component WebView, a més d’un EditText per introduir l’adreça web i un botó per anar-hi:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:orientation="vertical">
  6.  
  7. <LinearLayout
  8. android:id="@+id/linearLayout1"
  9. android:layout_width="match_parent"
  10. android:layout_height="wrap_content"
  11. android:orientation="horizontal">
  12.  
  13. <EditText
  14. android:id="@+id/editText1"
  15. android:layout_width="264dp"
  16. android:layout_height="wrap_content"
  17. android:hint="Introdueix l&apos;adreça web">
  18.  
  19. <requestFocus/>
  20. </EditText>
  21.  
  22. <Button
  23. android:id="@+id/button1"
  24. android:layout_width="wrap_content"
  25. android:layout_height="wrap_content"
  26. android:onClick="onClickAnar"
  27. android:text="Anar"/>
  28.  
  29. </LinearLayout>
  30.  
  31. <WebView
  32. android:id="@+id/webView1"
  33. android:layout_width="match_parent"
  34. android:layout_height="match_parent"/>
  35.  
  36. </LinearLayout>

Només cal anar al codi de l’activitat per afegir la funcionalitat necessària. Bàsicament, es tracta d’escriure el mètode onClickAnar() que s’activarà quan cliquem el botó, i que ha de dir al WebView que obri una pàgina:

  1. public void onClickAnar(View v) {
  2. WebView webView = (WebView) findViewById(R.id.webView1);
  3. EditText editText = (EditText)findViewById(R.id.editText1);
  4.  
  5. String adreca = editText.getText().toString();
  6. webView.loadUrl(adreca);
  7. }

Amb això podem executar l’aplicació, indicar un web i anar-hi, tal com es pot veure a la figura.

Figura Obrint la pàgina web de l’IOC

Observareu que, si escriviu una adreça sense el prefix “http://”, l’aplicació dóna un error. Això és perquè realment l’adreça completa ha d’incloure aquest prefix (que és el protocol d’aplicació). Per tant, podeu afegir el següent codi perquè ho faci, editant el mètode onClickAnar():

  1. String adreca = editText.getText().toString();
  2.  
  3. if(!adreca.startsWith("http://") && !adreca.startsWith("https://")) {
  4. adreca = "http://" + adreca;
  5. editText.setText(adreca);
  6. }
  7.  
  8. webView.loadUrl(adreca);

Podeu comprovar com fent doble clic a la pàgina la vista s’apropa (fa zoom) i us permet arrossegar-la per tal d’anar-vos desplaçant per tots els seus continguts segons us calgui, així que podeu clicar enllaços i fer qualsevol altra activitat amb les pàgines que aneu obrint.

Model dels fils

Processos del sistema

Un procés és una instància d’un programa que s’està executant. Conté el codi i els recursos que està fent servir en aquests moments (per exemple, la memòria reservada). Depenent del sistema operatiu, pot contenir diversos fils d’execució que s’executen concurrentment.

Quan s’engega una aplicació, el sistema Android posa en marxa un nou procés per a l’aplicació amb un únic fil d’execució.

Per defecte, tots els components de l’aplicació s’executen en aquest fil d’execució. Però en alguns casos pot ser convenient executar diferents tasques de l’aplicació en fils d’execució independents.

Fils d'execució

Un fil d’execució és la unitat de processament més petita que es pot programar en un sistema operatiu. Poden existir múltiples fils dins d’un procés i compartir recursos (com memòria, codi i context), cosa que no succeeix entre diferents processos.

Quan una aplicació accedeix a Internet o a altres recursos externs, es produeix un retard de durada variable però que pot arribar a ser de molts segons, o fins i tot d’alguns minuts, des que l’aplicació demana les dades fins que aquestes hi arriben.

Si l’aplicació funciona amb un únic fil d’execució, aquest es pot quedar aturat en la crida a un mètode (per exemple, alguna que descarregui dades d’un servidor extern, el qual pot tardar a enviar la informació). En aquest cas, l’execució de l’aplicació es quedarà bloquejada fins que aquest mètode retorni les dades i així es continuï executant la resta del codi.

Si això ocorregués, els usuaris tindrien la impressió que l’aplicació s’ha quedat penjada i segurament la tancarien i no tornarien a obrir-la. És necessari que l’aplicació continuï responent a les accions de l’usuari i, si el temps d’espera és molt llarg, convé que mostri algun tipus d’animació o barra de progrés per tal que l’usuari vegi que l’aplicació està funcionant.

El mecanisme que permet que les aplicacions puguin fer dues o més operacions alhora són els fils (threads, en anglès).

Crear i sincronitzar dos fils és una tasca relativament complexa, i per aquest motiu la llibreria d’Android ens facilita la classe AsyncTask, que es fa càrrec de la major part de la feina per nosaltres.

Quan es fa servir un widget com WebView no cal un AsyncTask, perquè el WebView ja genera un fil propi internament.

El que fa l'AsyncTask és crear un altre fil que podeu fer servir per a qualsevol operació que preveieu que pugui tenir una durada notable i que, per tant, pugui bloquejar la interfície d’usuari. Concretament, qualsevol operació d’accés a Internet hauria de fer-se en un fil separat obligatòriament, i la manera més senzilla és fer servir una AsyncTask.

AsyncTask s’hauria de fer servir, idealment, per a operacions curtes (d’alguns segons, com a molt). Si necessiteu que els fils estiguin executant-se durant períodes de temps llargs, es recomana que feu servir l’API del paquet java.util.concurrent.

Una tasca asíncrona es defineix com una tasca que s’executa en el fil de background (fil BG, per background, és a dir, que s’executa pel darrere) i els resultats de la qual es mostren en el fil de la interfície d’usuari (l’anomenarem fil UI, de User Interface).

Per crear una AsyncTask heu de crear una nova classe que derivi d'AsyncTask, i implementar els mètodes descrits a continuació. El més important és saber que alguns mètodes s’executen al fil UI i d’altres al fil que fa l’operació en el background. Això és fonamental perquè només es poden modificar les vistes de l’aplicació (escriure valors, mostrar missatges, fer avançar barres de progrés o mostrar altre tipus de continguts) des del fil UI.

Un AsyncTask es defineix fent servir tres tipus genèrics:

  • Params: el tipus dels paràmetres enviats a la tasca durant l’execució.
  • Progress: el tipus d’unitats de progrés publicades durant la computació en el background. Això serveix per si voleu mostrar un progrés de la tasca feta mentre aquesta s’executa en el background. Per exemple, podríeu animar una barra per mostrar el progrés de la càrrega d’un fitxer.
  • Result: el tipus del resultat de la computació en el background.

No sempre es fan servir tots els tipus en una tasca asíncrona. Per marcar un tipus que no es farà servir, simplement utilitzeu el tipus void. Per exemple, la següent classe asíncrona defineix que tindrà un argument de tipus String i retornarà un Bitmap. No farà servir cap tipus de progrés de la tasca:

  1. private class CarregaImatge extends AsyncTask<String, Void, Bitmap> {
  2. ...
  3. }

Quan s’executa una tasca de forma asíncrona, aquesta realitza quatre passos:

  • onPreExecute() (UI): executat a l’UI immediatament després que la tasca és executada. Permet inicialitzar els elements de la interfície que siguin necessaris per a la tasca, com ara crear una barra de progrés.
  • doInBackground(Params…) (BG): és cridada després que onPreExecute() acaba d’executar-se. Posa en marxa l’operació que voleu executar en un altre fil en el background. Aquest mètode rep els paràmetres necessaris per fer l’operació asíncrona. El resultat de l’execució s’ha de retornar i es passarà de tornada en l’últim pas. Dins de doInBackground() es pot cridar publishProgress(Progress…) per publicar una o més unitats de resultat. Aquests valors es publiquen en el fil UI, a onProgressUpdate(Progres…).
  • onProgressUpdate(Progress…) (UI): és cridat en el fil UI després d’haver cridat publishProgress(Progress…). Us permet actualitzar algun element de la interfície per mostrar el progrés de l’operació mentre l’operació en el background encara està en execució, com ara actualitzar una animació o fer avançar una barra de progrés.
  • onPostExecute(Result) (UI): aquest mètode és cridat quan l’execució en el background finalitza. Rep el resultat de l’operació que ha executat l‘AsyncTask i com que s’executa al fil UI permet visualitzar els resultats obtinguts (com ara una imatge, una pàgina web o una llista de tweets).

Per fer servir AsyncTask us heu de crear la vostra classe, que hereti d'AsyncTask i implementi el mètode de callback doInBackground(), que s’executa en un fil en el background. Per actualitzar la vostra interfície d’usuari (o UI, user interface) heu d’implementar el mètode onPostExecute() que incorpora els resultats d’haver executat doInBackground() i s’executa en el fil UI, i per tant pot actualitzar l’estat de la vostra interfície sense problemes. Quan tingueu la classe creada, per llançar el fil de l'AsyncTask heu d’elaborar un nou objecte de la subclasse que heu creat i cridar el mètode execute(), passant-li la informació necessària perquè pugui fer l’operació.

Per exemple, la següent classe descarrega una imatge de la xarxa, mitjançant el mètode propi Bitmap DescarregaImatgeDeLaXarxa(String url), i fa servir el Bitmap per mostrar-lo a un widget:

  1. private class CarregaImatge extends AsyncTask<String, Void, Bitmap> {
  2.  
  3. @Override
  4. protected Bitmap doInBackground(String... url) {
  5. //Descarreguem la imatge d'Internet
  6. returnDescarregaImatgeDeLaXarxa(url);
  7. }
  8.  
  9. protected void onPostExecute(Bitmap result) {
  10. //Rebem el resultat de doInBackground (el Bitmap)
  11. //i el fem servir per carregar la imatge a l'UI
  12. mImageView.setImageBitmap(result);
  13. }
  14. }

Existeixen algunes regles per tal que AsyncTask funcioni correctament:

  • La classe AsyncTask s’ha de crear al fil UI.
  • La instància de la tasca s’ha de crear al fil UI.
  • execute(Params…) s’ha d’executar des del fil UI.
  • Mai heu de cridaronPreExecute(), onPostExecute(Result), doInBackground(Params…) o onProgressUpdate(Progress…) directament.
  • La tasca es pot executar únicament una vegada al mateix temps (si n’intenteu executar una altra, en llançarà una excepció).

Connexions HTTP

El protocol HTTP (HyperText Transfer Protocol, protocol de transferència d’hipertext) és un protocol estàndard d’Internet que serveix per a la transferència d’hipertext. És el protocol base sobre el qual està fonamentada la WWW (World Wide Web, ‘xarxa d’extensió mundial’). Fent servir HTTP a la vostra aplicació podreu realitzar diverses tasques, com descarregar-vos informació de text i binària (com ara pàgines web o imatges, respectivament).

Un hipertext és un text mostrat a un ordinador o un altre dispositiu electrònic amb enllaços (hyperlinks) a altres textos, que són d’accés immediat pel lector a través d’un clic del ratolí o de la pantalla tàctil. A banda de text, pot contenir taules, imatges i altres formats de representació de continguts multimèdia.

També es pot fer servir el protocol HTTPS (HTTP Secure, HTTP segur) per a comunicacions segures a través d’una xarxa d’ordinadors. HTTPS proporciona autentificació del lloc web i encriptació bidireccional en les comunicacions entre el client i el servidor.

Android inclou dos clients HTTP que podeu fer servir a la vostra aplicació: HttpURLConnection i el client d’Apache HttpClient. Ambdós suporten HTTPS, fluxos de pujada i descàrrega i IPv6. Segons articles de la pròpia Google, el client HTTP d’Apache és més estable a les versions d’Android Eclair (versió 2.1) i Froyo (versió 2.2). Però per a les versions Gingerbread (versió 2.3) i posteriors recomana fer servir HttpURLConnection, en el que enfocarà els seus esforços futurs.

Una connexió HttpURLConnection permet enviar i rebre dades per la web. Les dades poden ser de qualsevol tipus i mida, fins i tot podeu enviar i rebre dades de les quals no coneixeu la mida prèviament. Per usar aquesta classe heu de seguir els següents passos:

  • Obtingueu un objecte nou HttpURLConnection cridant el mètode URL.openConnection() i forçant el seu resultat a HttpURLConnection.
  • Prepareu la petició. La principal propietat de la petició és la seva URI. Les capçaleres de la petició poden tenir altres metadades com credencials, tipus de contingut, galetes de sessió, etc.
  • Si aneu a pujar dades a un servidor, la instància ha d’estar configurada amb setDoOutput(true). Heu de transmetre dades escrivint al flux de dades retornat per getOutputStream().
  • Llegiu la resposta. Les capçaleres de la resposta generalment inclouen metadades com el tipus de dades de la resposta i la seva mida, la data de modificació o galetes de sessió, entre d’altres. El cos de la resposta es pot llegir del flux retornat per getInputStream(). Si la resposta no té cos (body en anglès), el mètode retorna un flux buit.
  • Desconnecteu. Quan hàgiu llegit la resposta, heu de tancar l'HttpURLConnection amb disconnect().

Per exemple, amb el següent codi estaríeu llegint el contingut del web de l’IOC:

  1. URL url =new URL("http://ioc.xtec.cat/");
  2. HttpURLConnection urlConnection =(HttpURLConnection) url.openConnection();
  3. try {
  4. InputStream in = new Buffered InputStream(urlConnection.getInputStream());
  5. treballarAmbElFluxDeDades(in);
  6. finally {
  7. urlConnection.disconnect();
  8. }
  9. }

Comunicacions segures amb HTTPS

Si crideu openConnection() amb una URL que comenci amb la cadena “https”, se us retornarà un objecte HttpsURLConnection (fixeu-vos que no és el mateix que l’anterior, té una “s” al final del nom). Aquesta classe us permet especificar uns HostnameVerifier i SSLSocketFactory necessaris per al protocol HTTPS. Podeu consultar el funcionament d’aquesta classe a la Guia del desenvolupador d’Android http://developer.android.com/reference/javax/net/ssl/HttpsURLConnection.html.

Pujant dades a un servidor

Per pujar dades a un servidor web heu de configurar la connexió per sortida amb setDoOutput(true). Per millorar el rendiment de la vostra aplicació cal que crideu setFixedLengthStreamingMode(contentLength) quan conegueu per avançat la mida del cos de les dades que voleu enviar, o setChunkedStreamingMode(chunkLength) quan no la sabeu (té com a argument la mida de chunk, porció de dades, o 0 per fer servir la mida estàndard). Si no ho feu, HttpURLConnection copiarà les dades completes a enviar en un buffer (memòria cau) abans d’enviar-les, desaprofitant així memòria i fent que l’aplicació vagi més lenta. Per exemple, el codi següent estableix una connexió a un servidor i realitza una descàrrega i una pujada de dades. Les funcions EscriureFluxDeDades() i LlegirFluxDeDades() seran funcions pròpies que treballaran amb l'OutputStream i l'InputStream.

  1. HttpURLConnection urlConnection =(HttpURLConnection) url.openConnection();
  2. try{
  3. urlConnection.setDoOutput(true);
  4. urlConnection.setChunkedStreamingMode(0);
  5.  
  6. OutputStream out=new BufferedOutputStream(urlConnection.getOutputStream());
  7. EscriureFluxDeDades(out);
  8.  
  9. InputStream in=new BufferedInputStream(urlConnection.getInputStream());
  10. LlegirFluxDeDades(in);
  11. finally {
  12. urlConnection.disconnect();
  13. }
  14. }

Missatgeria

Els serveis de missatgeria SMS (Short Message Service, servei de missatges curts) es troben entre els serveis de telefonia més populars. El seu cost reduït (en comparació amb les trucades) ha fet que hagi esdevingut la forma més comuna per realitzar comunicacions senzilles durant anys. Darrerament, amb l’aparició dels smartphones i la seva connexió contínua a Internet, han aparegut serveis de missatgeria gratuïts a través de la xarxa de dades 3G (com Whatsapp), que han esdevingut una alternativa per a la comunicació de missatges entre dispositius que tenen connexió a Internet. En qualsevol cas, tots els telèfons permeten l’ús d’SMS, també els smartphones, tot i que aquest servei ha evolucionat cap a altres funcionalitats, com ara la validació del login en dues passes, la confirmació d’operacions bancàries, informacions del govern o d’empreses de subministraments…

Android ja porta inclosa per defecte una aplicació per enviar i rebre missatges SMS. Però potser voleu integrar les capacitats SMS dins de la vostra aplicació. Tot seguit teniu alguns exemples d’aplicacions Android que podrien fer servir el servei SMS:

  • Una aplicació que enviï un missatge de felicitació automàticament als nostres contactes el dia del seu aniversari.
  • Una aplicació que enviï un missatge quan algun valor de la borsa pugi o baixi d’un cert límit.
  • Una aplicació que enviï regularment la posició en la qual es troba el telèfon (per a persones amb alzheimer o nens petits).

Com veieu, les possibilitats són infinites i depèn únicament de la imaginació del desenvolupador.

Enviament d'SMS

Enviar missatges SMS des del vostre programa és molt senzill. En primer lloc, heu de declarar que la vostra aplicació tindrà permisos per enviar SMS. Per fer-ho, afegiu la següent declaració al fitxer de manifest del projecte:

  1. <uses-permission android:name="android.permission.SEND_SMS">
  2. </uses-permission>

El layout de l’activitat principal és molt senzill, amb unes caixes de text per introduir el número de telèfon i el text a enviar i un botó per enviar el missatge:

  1. <?xml version="1.0"encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:id="@+id/RelativeLayout1"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. android:orientation="vertical">
  7.  
  8. <TextView
  9. android:id="@+id/textView1"
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content"
  12. android:layout_alignBaseline="@+id/editTelèfon"
  13. android:layout_alignParentLeft="true"
  14. android:layout_alignParentTop="true"
  15. android:text="Telefon"
  16. android:textAppearance="?android:attr/textAppearanceLarge"/>
  17.  
  18. <EditText
  19. android:id="@+id/editTelèfon"
  20. android:layout_width="wrap_content"
  21. android:layout_height="wrap_content"
  22. android:layout_alignParentRight="true"
  23. android:layout_alignParentTop="true"
  24. android:layout_toRightOf="@+id/textView1"
  25. android:layout_weight="1"
  26. android:ems="10"
  27. android:inputType="phone">
  28.  
  29. <requestFocus/>
  30. </EditText>
  31.  
  32. <TextView
  33. android:id="@+id/textView2"
  34. android:layout_width="wrap_content"
  35. android:layout_height="wrap_content"
  36. android:layout_alignParentLeft="true"
  37. android:layout_below="@+id/editTelèfon"
  38. android:text="Missatge"
  39. android:textAppearance="?android:attr/textAppearanceLarge"/>
  40.  
  41. <EditText
  42. android:id="@+id/editMissatge"
  43. android:layout_width="fill_parent"
  44. android:layout_height="wrap_content"
  45. android:layout_alignParentLeft="true"
  46. android:layout_below="@+id/textView2"/>
  47.  
  48. <Button
  49. android:id="@+id/btnEnviarSMS"
  50. android:layout_width="fill_parent"
  51. android:layout_height="wrap_content"
  52. android:layout_alignParentLeft="true"
  53. android:layout_below="@+id/editMissatge"
  54. android:text="Enviar SMS"/>
  55.  
  56. </RelativeLayout>

Fixeu-vos que aquest layout està definit com unRelativeLayout. Podríeu obtenir un resultat similar amb un LinearLayout.

Les operacions amb el servei SMS estan gestionades mitjançant la classe SMSManager, que us permet enviar missatges de dades, text i missatges per parts. Per obtenir un objecte de la classe SMSManager heu de cridar el mètode SMSManager.getDefault(), per exemple:

  1. SmsManager sms = SmsManager.getDefault();

Quan hàgiu aconseguit un objecte SMSManager, podeu fer servir el mètode sendTextMessage, que té els següents arguments:

  • String destinationAddress: el número de telèfon on voleu enviar el missatge.
  • String scAddress: el número del centre de servei d’SMS. Si introduïu null, es farà servir el que té definit el telèfon a la seva configuració.
  • String text: text a enviar.
  • PendingIntent sentIntent: si està a null, no fa res. Si li passeu un PendingIntent, aquest serà enviat com a missatge de broadcast al dispositiu quan el missatge hagi estat enviat, o bé quan falli l’enviament. En el segon cas, aquest Intent us permetrà saber el tipus d’error que s’ha produït.
  • PendingIntent deliveryIntent: si és null no fa res. Si passeu un Intent, aquest serà enviat com a missatge de broadcast al dispositiu quan el missatge ha estat lliurat al destinatari.

La forma més fàcil d’enviar un missatge és simplement passar el número de telèfon i el missatge, com es veu a continuació:

  1. sms.sendTextMessage(numeroTelefon, null, missatge, null, null);

Podeu fer una prova de funcionament des del simulador. Executeu dos simuladors des de l’AVD Manager i envieu un missatge SMS de l’un a l’altre. Per fer-ho, heu de fer servir el número de port de la instància de simulador Android (està a la capçalera de la finestra) com a número de telèfon, com es pot veure a la figura.

Figura Enviant un missatge entre dues instàncies de l’emulador

Rebre SMS

Per rebre missatges dins de la vostra aplicació heu de fer servir un objecte BroadcastReceiver. Quan es rep un SMS al dispositiu, s’envia un missatge de broadcast anunciant la recepció. És aquest missatge el que podeu obtenir per fer alguna acció quan es rep un SMS.

Per rebre missatges SMS, en primer lloc heu de modificar el fitxer de manifest per indicar que l’aplicació té permisos per rebre SMS amb el codi. També heu de registrar al fitxer de manifest el receptor de broadcast que fareu servir:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.android.ioc"
  4. android:versionCode="1"
  5. android:versionName="1.0">
  6.  
  7. <uses-sdk android:minSdkVersion="15"/>
  8.  
  9. <uses-permission android:name="android.permission.RECEIVE_SMS"/>
  10.  
  11. <application
  12. android:icon="@drawable/ic_launcher"
  13. android:label="@string/app_name">
  14. <activity
  15. android:name=".RebreSMSActivity"
  16. android:label="@string/app_name">
  17. <intent-filter>
  18. <actionandroid:name="android.intent.action.MAIN"/>
  19. <categoryandroid:name="android.intent.category.LAUNCHER"/>
  20. </intent-filter>
  21. </activity>
  22.  
  23. <receiver android:name=".SMSReceiver">
  24. <intent-filter>
  25. <action android:name="android.provider.Telephony.SMS_RECEIVED"></action>
  26. </intent-filter>
  27. </receiver>
  28. </application>
  29.  
  30. </manifest>

Cal que indiqueu el nom del receptor de broadcast i, dins del filtre d’intents, quin tipus de missatge de broadcast estareu escoltant (en aquest cas el missatge de rebuda d’SMS, SMS_RECEIVED).

Dins de la carpeta /src del projecte, afegiu una nova classe dins del paquet de la vostra aplicació i anomeneu-la SMSReceiver. Aquesta classe heretarà de BroadcastReceiver. La classe BroadcastReceiver és la classe base que rebrà els Intents enviats mitjançant el mètode sendBroadcast(). La implementació del receptor ja ha estat anunciada a través del manifest de l’aplicació amb l’etiqueta <receiver>. Una altra forma de registrar el receptor de broadcast és mitjançant Context.registerReceiver().

La classe que creeu, que hereta de BroadcastReceiver, ha d’implementar el mètode onReceive(Context c, Intent i). Aquest és el mètode que és cridat quan el BroadcastReceiver rep un Intent de broadcast. Aquest mètode rep dos arguments: el context on el receptor s’està executant (on s’ha cridat) i l’Intent que s’està rebent.

L’objecte BroadcastReceiver únicament és vàlid durant la crida al mètode onReceive(). Una vegada que el codi retorna d’aquesta funció, el sistema considera l’objecte terminat i deixa d’estar actiu. Això té una repercussió important en el que es pot fer dins de la implementació del mètode onReceive(). Per exemple, no es poden fer coses que requereixen una operació asíncrona, ja que per fer-ho heu de sortir de la funció per manegar l’operació asíncrona, però en el moment en què se surt d'onReceive(), el BroadcastReceiver ja no està actiu i, per tant, el sistema és lliure de finalitzar el procés abans que l’operació asíncrona s’hagi completat.

Aquesta és la definició de la classe que fareu servir per escoltar els Intents de broadcast:

  1. package com.android.ioc;
  2.  
  3. import android.content.BroadcastReceiver;
  4. import android.content.Context;
  5. import android.content.Intent;
  6.  
  7. public class SMSReceiver extends BroadcastReceiver {
  8. public void onReceive(Context context, Intent intent) {
  9.  
  10. }
  11. }

Quan es rep un missatge SMS, el mètode onReceive() és cridat. El missatge SMS està contingut dins de l’objecte Intent (el segon argument que rep onReceive()) a través dels extres de l'Intent, que són un objecte Bundle. Reviseu la definició de la classe Intent a la Guia del desenvolupador d’Android per veure la seva definició i els extres, a http://developer.android.com/reference/android/content/Intent.html.

Aquest és el codi que obté el Bundle de l'Intent.

  1. //Obtenim els extres de l'Intent
  2. Bundle bundle = intent.getExtras();
  3.  
  4. //Si hi ha extres
  5. if(bundle !=null) {
  6.  
  7. }

Els missatges estan emmagatzemats en un array d'Objects en el format PDU.

Unitat de Descripció de Protocol

PDU (Protocol Description Unit, unitat de descripció de protocol) és un dels mètodes definits per la indústria per enviar i rebre missatges SMS. No cal que feu una dissecció de la PDU, ja que la classe SMSMessage hi pot treballar directament. Per a més informació sobre l’estructura d’una PDU podeu consultar: www.dreamfabric.com/sms.

Per obtenir l’array d’objectes a partir del Bundle cal que feu servir:

  1. //obtenir llistat d'SMS
  2. Object[] pdus = (Object[]) bundle.get("pdus");

Per extraure cada missatge, heu de fer servir el mètode estàtic SmsMessage.createFromPdu(), que us retorna un SmsMessage a partir d’un PDU (dels que trobeu a l’array d'Objects). El següent codi mostra com obtenir el missatge 0 (en forma d'SmsMessage) a partir de l’array d’objectes obtingut:

  1. SmsMessage missatge = null;
  2.  
  3. missatge = SmsMessage.createFromPdu((byte[])pdus[0]);

Quan tingueu l’objecte SmsMessage podeu obtenir accés al cos de missatge o el número de telèfon que l’ha enviat amb getMessageBody() i getOriginatingAddress(), respectivament.

Finalment, el codi següent mostra tot el codi del mètode onReceive(). Penseu que, com que es pot haver rebut més d’un missatge, la lectura dels missatges de l’array d’objectes es fa dins d’un bucle for, per llegir tots els missatges si és el cas.

  1. public void onReceive(Context context, Intent intent) {
  2.  
  3. //Rebre l'SMS
  4.  
  5. //Obtenim els extres de l'Intent
  6. Bundle bundle = intent.getExtras();
  7.  
  8. SmsMessage[] missatges = null;
  9. String s = "";
  10.  
  11. //Si hi ha extres
  12. if(bundle !=null) {
  13. //obtenir llistat d'SMS
  14. Object[] pdus = (Object[]) bundle.get("pdus");
  15.  
  16. SmsMessage missatge = null;
  17.  
  18. for(int i=0; i<pdus.length; i++) {
  19. missatge = SmsMessage.createFromPdu((byte[])pdus[i]);
  20. s += "SMS de " + missatge.getOriginatingAddress();
  21. s += " :";
  22. s += missatge.getMessageBody().toString();
  23. s += "\n";
  24. }
  25.  
  26. Toast.makeText(context, s, Toast.LENGTH_SHORT).show();
  27. }
  28.  
  29. }

Enviant un SMS mitjançant un 'Intent'

Podeu fer servir la classe SmsManager per enviar missatges SMS des de la vostra aplicació. Però si no ho voleu programar, podeu invocar l’aplicació de missatgeria inclosa en el mateix sistema Android perquè enviï el missatge per vosaltres.

Per activar l’aplicació de missatgeria des de la vostra aplicació podeu llançar un objecte Intent amb el tipus MIME “vnd.android-dir/mms-sms”. En aquest cas, l’únic que heu de fer és crear un objecte Intent, afegir les dades del missatge que voleu enviar com a extres: “address” en el cas del número de telèfon (se’n pot introduir més d’un, separats per punts i coma) i “sms_body” en el cas del cos del missatge. També heu d’introduir el tipus de l'Intent com “vnd.android-dir/mms-sms”. Una vegada fet això, simplement invoqueustartActivity() amb l'Intent creat, com es pot veure al següent codi:

  1. Intent i = new Intent(android.content.Intent.ACTION_VIEW);
  2. i.putExtra("address", editTelefon.getText().toString());
  3. i.putExtra("sms_body",editMissatge.getText().toString());
  4. i.setType("vnd.android-dir/mms-sms");
  5. startActivity(i);

Seguretat

Quan feu servir els receptors de broadcast heu de pensar que aquests són una porta d’entrada a la vostra aplicació per a la resta d’aplicacions del dispositiu. Heu de considerar que altres aplicacions poden abusar de l’ús de missatges i, per tant, heu de tenir en compte alguns conceptes de seguretat per a la vostra aplicació:

  • L’espai de noms d'Intent és global, per tant assegureu-vos que els noms d’acció dels intents estiguin escrits en un espai de noms vostre, o sense adonar-vos-en podríeu entrar en conflicte amb altres aplicacions.
  • Quan registreu un receptor de broadcast amb registerReceiver(), qualsevol aplicació pot enviar broadcasts a la vostra. Podeu controlar qui pot enviar broadcast a través de permisos.
  • Quan publiqueu un receptor al manifest de l’aplicació i especifiqueu filtres d'Intent per aquest, qualsevol altra aplicació pot enviar broadcast. Per evitar que altres aplicacions enviïn missatges de broadcast a la vostra, podeu fer-la “no disponible” per la resta amb android:exported=“false” al fitxer de manifest.
  • Quan feu servir sendBroadcast(Intent) o algun altre mètode similar, normalment qualsevol altra aplicació pot rebre aquests broadcasts. Podeu controlar qui rep els broadcasts mitjançant els permisos. A partir de la versió Ice Cream Sandwich d’Android podeu restringir el broadcast a una única aplicació.

Cap d’aquests problemes existeix quan es fa servir LocalBroadcastManager(), ja que els intents broadcast en aquest cas mai surten del procés de l’aplicació.

Es poden forçar els permisos en l’enviament i recepció broadcast. Per forçar els permisos quan s’envien missatges de broadcast compteu amb l’argument String receiverPermission dels mètodes sendBroadcast() i sendOrderedBroadcast(). Si no definiu aquest argument com a null, únicament els receptors als quals s’ha permès aquest permís (amb l’etiqueta de petició <uses-permission> al fitxer de manifest) podran rebre el broadcast.

Per establir els permisos sobre allò que pot rebre l’aplicació, heu d’introduir a l’argument String broadcastPermission un valor no null quan registreu el receptor amb el mètode registerReceiver(). També ho podeu fer estàticament dins de l’etiqueta <receiver> del fitxer de manifest de l’aplicació. Únicament els emissors de broadcast que tinguin aquest permís concedit (amb la petició amb l’etiqueta <uses-permission> al fitxer de manifest) podran enviar missatges a l’aplicació.

Podeu trobar més informació sobre seguretat a la Guia del desenvolupador d’Android, a http://developer.android.com/guide/topics/security/permissions.html.

Enviant un MMS mitjançant 'Intent'

Els MMS (Multimedia Messaging System, sistema de missatgeria multimèdia) són missatges de text semblant als SMS però amb un contingut extra de tipus multimèdia (foto, àudio, vídeo…). El procés d’enviament d’un missatge MMS és semblant al de l'SMS mitjançant un Intent, però en aquest cas s’han d’afegir una sèrie d’extres per indicar les dades multimèdia que es volen afegir al missatge. Per començar, l'Intent es crea amb el constructor d'Intent que té com a arguments una acció i un URI de les dades que es volen enviar:

  1. Intent intentMMS = new Intent(Intent.ACTION_SEND, Uri.parse("mms://"));

Després s’afegeixen els extres. A més del número de telèfon i el cos del missatge, afegiu el contingut multimèdia que es vol afegir al missatge. Per fer-ho agregueu un contingut extra del tipus que aneu a afegir. En aquest exemple de codi, s’inclou una imatge que es troba en la targeta SD del dispositiu.

  1. intentMMS.putExtra("address", editTelefon.getText().toString());
  2.  
  3. intentMMS.putExtra("sms_body",
  4. editMissatge.getText().toString());
  5. String url = "file://mnt//sdcard//foto.jpeg";
  6.  
  7.  
  8. intentMMS.setType("image/jpeg");
  9.  
  10.  
  11. intentMMS.putExtra(Intent.EXTRA_STREAM, Uri.parse(url));
  12.  
  13. startActivity(Intent.createChooser(intentMMS, "MMS:"));

Quan s’executa l’aplicació, es fa una crida que es tractada per l’aplicació de missatgeria del sistema. Es fa servir aquesta aplicació per enviar un missatge MMS amb les dades que s’han passat a través de l'Intent, com es pot veure a la figura.

Figura Enviament de missatgeria MMS

Analitzant codi XML

Un format molt útil per intercanviar informació entre dos dispositius és mitjançant XML. XML és un llenguatge de marques que defineix una sèrie de regles per codificar documents en un format que és comprensible, tant per les màquines (ordinadors, telèfons…) com per les persones. És un format que es fa servir freqüentment per compartir dades a Internet. Per exemple, els webs que tenen contingut que s’actualitzi freqüentment, com els blogs o pàgines de notícies, solen proporcionar codi XML per tal que programes externs puguin mostrar les actualitzacions de la pàgina. Per poder fer servir aquestes dades, les aplicacions s’han de descarregar les dades XML (per exemple, amb una connexió HTTP) i, posteriorment, analitzar el codi XML per obtenir les dades i tractar-les dins del programa com vulgui.

Existeixen diferents analitzadors d’XML que podeu fer servir a Android. Google recomana fer servir XmlPullParser, la documentació del qual podeu trobar a la Guia del desenvolupador d’Android:

http://developer.android.com/reference/org/xmlpull/v1/XmlPullParser.html.

Tot seguit veurem un exemple d’utilització de l’analitzador a través d’un exemple. L’objectiu és descarregar les noticies del web StackOverflow.com. El feed d’RSS d’aquest web sobre Android ordenat per les últimes entrades el podeu trobar a http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest i té la següent forma:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <feed xmlns="http://www.w3.org/2005/Atom" xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule" xmlns:re="http://purl.org/atompub/rank/1.0">
  3. <title type="text">newest questions tagged android - Stack Overflow</title>
  4. ...
  5. <entry>
  6. ...
  7. </entry>
  8. <entry>
  9. <id>http://stackoverflow.com/q/11530594</id>
  10. <re:rank scheme="http://stackoverflow.com">0</re:rank>
  11. <title type="text">No puc compilar</title>
  12. <category scheme="http://stackoverflow.com/feeds/tag?tagnames=android&amp;sort=newest/tags" term="android"/><category scheme="http://stackoverflow.com/feeds/tag?tagnames=android&amp;sort=newest/tags" term="android-emulator"/>
  13. <author>
  14. <name>aturing</name>
  15. <uri>http://stackoverflow.com/users/803649</uri>
  16. </author>
  17. <link rel="alternate" href="http://stackoverflow.com/questions/11530594/launch-of-android-emulator-failing" />
  18. <published>2012-07-17T20:44:06Z</published>
  19. <updated>2012-07-17T20:47:59Z</updated>
  20. <summary type="html">
  21. No trobo el botó per compilar l'aplicació. Algú em pot ajudar?
  22. </summary>
  23. </entry>
  24. <entry>
  25. ...
  26. </entry>
  27. ...
  28. </feed>

Si us hi fixeu, dins de les etiquetes <entry> i </entry> teniu tota la informació d’una entrada en el web.

Les entrades les emmagatzemem com una llista d’objectes de la classe Entrada, que conté tres strings per les diferents parts que us interessen de les notícies.

  1. //Aquesta classe representa una entrada de notícia de l'RSS Feed
  2. public static class Entrada {
  3. public final String titol; //Títol de la notícia
  4. public final String enllac; //Enllaç a la notícia completa
  5. public final String resum; //Resum de la notícia
  6.  
  7. private Entrada(String title, String summary, String link) {
  8. this.titol = title;
  9. this.resum = summary;
  10. this.enllac = link;
  11. }
  12. }

Tot seguit crearem una altra classe, StackOverflowXmlParser, per analitzar el contingut de l’XML. El mètode analitzat serveix per començar l’anàlisi de l’XML i rep un InputStream (que heu obtingut a través d’una connexió HTTP) com a entrada. El mètode retorna una llista d’objectes Entrada:

  1. public List<Entrada> analitza(InputStream in) throws XmlPullParserException, IOException {
  2. try {
  3. //Obtenim analitzador
  4. XmlPullParser parser = Xml.newPullParser();
  5.  
  6. //No fem servir namespaces
  7. parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
  8.  
  9. //Especifica l'entrada de l'analitzador
  10. parser.setInput(in, null);
  11.  
  12. //Obtenim la primera etiqueta
  13. parser.nextTag();
  14.  
  15. //Retornem la llista de notícies
  16. return llegirNoticies(parser);
  17. } finally {
  18. in.close();
  19. }
  20. }

L’objecte XmlPullParser l’obteniu amb una crida a Xml.newPullParser(). Una vegada especificades les seves característiques i el flux de dades d’entrada de l’analitzador crideu a nextTag() per obtenir el primer tag (etiqueta) del document XML. El mètode next() avança l’analitzador al següent event del document XML. Retorna un valor que indica el tipus d’event de l’estat de l’analitzador (el podeu obtenir també amb getEventType()). El mètode nextTag() ignora els espais en blanc i retorna la següent etiqueta de l’XML.

El mètode és l’encarregat de llegir totes les notícies i retornar el List d’entrades.

  1. private List<Entrada> llegirNoticies(XmlPullParser parser) throws XmlPullParserException, IOException {
  2.  
  3. List <Entrada>llistaEntrades = new ArrayList<Entrada>();
  4.  
  5. //Comprova si l'event actual és del tipus esperat (START_TAG) i del nom "feed"
  6. parser.require(XmlPullParser.START_TAG, ns, "feed");
  7.  
  8. //Mentre que no arribem al final d'etiqueta
  9. while (parser.next() != XmlPullParser.END_TAG) {
  10. //Ignorem tots els events que no siguin un començament d'etiqueta
  11. if (parser.getEventType() != XmlPullParser.START_TAG) {
  12. //Saltem al següent event
  13. continue;
  14. }
  15.  
  16. //Obtenim el nom de l'etiqueta
  17. String name = parser.getName();
  18.  
  19. // Si aquesta etiqueta és una entrada de notícia
  20. if (name.equals("entry")) {
  21. //Afegim l'entrada a la llista
  22. llistaEntrades.add(llegirEntrada(parser));
  23. } else {
  24. //Si és una altra cosa la saltem
  25. saltar(parser);
  26. }
  27. }
  28. return llistaEntrades;
  29. }

El mètode parser.require() comprova si l’event actual és del tipus adequat i amb el nom que esperem, en aquest cas, “feed”; ho podeu comprovar en l’estructura del codi XML.

A continuació s’aniran llegint etiquetes dins del bucle while fins que es trobi el final d’etiqueta. S’ignoraran tots els events llegits que no siguin de començament d’etiqueta amb el codi:

  1. //Ignora fins que no trobem un començament d'etiqueta
  2. if (parser.getEventType() != XmlPullParser.START_TAG) {
  3. continue;
  4. }

El mètode parser.getEventType() retorna el tipus d’esdeveniment que ha llegit l’analitzador. Existeixen els següents tipus d’events definits a XmlPullParser:

  • START_TAG: es llegeix un començament d’etiqueta XML.
  • TEXT: es llegeix text. Es pot obtenir el seu contingut amb el mètode parser.getText().
  • END_TAG: es llegeix el final d’una etiqueta XML.
  • END_DOCUMENT: no hi ha més events disponibles.

A continuació es cercarà entre les etiquetes fins que es trobi l’etiqueta entry. El mètode parser.getName() retorna el nom de l’etiqueta, es compara aquest nom per veure si és entry (fixeu-vos dins l’estructura del fitxer XML com les entrades estan contingudes dins d’una parella d’etiquetes <entry> </entry>).

Tota la resta d’etiquetes seràn ignorades. Una vegada trobada una secció entry de l’XML, es cridarà el mètode encarregat de llegir una entrada. El mètode llegirEntrada() retornarà un objecte Entrada que serà afegit al List retornat per llegirNoticies().

  1. //Analitza el contingut d'una entrada. Si troba un títol, resum o enllaç, crida els mètodes de lectura
  2. //propis per processar-los. Si no, ignora l'etiqueta.
  3.  
  4. private Entrada llegirEntrada(XmlPullParser parser) throws XmlPullParserException, IOException {
  5. String titol = null;
  6. String resum = null;
  7. String enllac = null;
  8.  
  9. //L'etiqueta actual ha de ser "entry"
  10. parser.require(XmlPullParser.START_TAG, ns, "entry");
  11.  
  12. //Mentre no acabi l'etiqueta d'"entry"
  13. while (parser.next() != XmlPullParser.END_TAG) {
  14. //Ignora fins que no trobem un començament d'etiqueta
  15. if (parser.getEventType() != XmlPullParser.START_TAG) {
  16. continue;
  17. }
  18.  
  19. //Obtenim el nom de l'etiqueta
  20. String etiqueta = parser.getName();
  21.  
  22. //Si és un títol de notícia
  23. if (etiqueta.equals("title")) {
  24. titol = llegirTitol(parser);
  25.  
  26. //Si l'etiqueta és un resum de notícia
  27. } elseif (etiqueta.equals("summary")) {
  28. resum = llegirResum(parser);
  29.  
  30. //Si és un enllaç
  31. } elseif (etiqueta.equals("link")) {
  32. enllac = llegirEnllac(parser);
  33. } else {
  34. //les altres etiquetes les saltem
  35. saltar(parser);
  36. }
  37. }
  38.  
  39. //Creem una nova entrada amb aquestes dades i la retornem
  40. return new Entrada(titol, resum, enllac);
  41. }

El mètode llegirEntrada() comprova que està dins de l’etiqueta entry amb una crida a parser.require(). Posteriorment, comprova el nom de l’etiqueta que troba i es crida els mètodes llegirTitol(), llegirResum() i llegirEnllac() per llegir els diferents tipus de contingut de l’entrada. Tots ells retornen un String que fareu servir finalment en la crida al constructor d'Entrada.

  1. //Llegeix el títol d'una notícia del feed i el retorna com String
  2. private String llegirTitol(XmlPullParser parser) throws IOException, XmlPullParserException {
  3. //L'etiqueta actual ha de ser "title"
  4. parser.require(XmlPullParser.START_TAG, ns, "title");
  5.  
  6. //Llegeix
  7. String titol = llegeixText(parser);
  8.  
  9. //Fi d'etiqueta
  10. parser.require(XmlPullParser.END_TAG, ns, "title");
  11. return titol;
  12. }
  13.  
  14. //Llegeix l'enllaç d'una notícia del feed i el retorna com String
  15. private String llegirEnllac(XmlPullParser parser) throws IOException, XmlPullParserException {
  16. String enllac = "";
  17.  
  18. //L'etiqueta actual ha de ser "link"
  19. parser.require(XmlPullParser.START_TAG, ns, "link");
  20.  
  21. //Obtenim l'etiqueta
  22. String tag = parser.getName();
  23.  
  24. //Obtenim l'atribut rel (mirar l'XML d'Stackoverflow)
  25. String relType = parser.getAttributeValue(null, "rel");
  26.  
  27. //Si l'enllaç és link
  28.  
  29. if (tag.equals("link")) {
  30. //Obtenim l'enllaç del valor de l'atribut "href". Revisar format XML stackoverflow
  31. if (relType.equals("alternate")) {
  32. enllac = parser.getAttributeValue(null, "href");
  33. parser.nextTag();
  34. }
  35. }
  36.  
  37. //Fi d'etiqueta
  38. parser.require(XmlPullParser.END_TAG, ns, "link");
  39.  
  40. return enllac;
  41. }
  42.  
  43. //Llegeix el resum d'una notícia del feed i el retorna com String
  44. private String llegirResum(XmlPullParser parser) throws IOException, XmlPullParserException {
  45.  
  46. //L'etiqueta actual ha de ser "summary"
  47. parser.require(XmlPullParser.START_TAG, ns, "summary");
  48.  
  49. String resum = llegeixText(parser);
  50.  
  51. parser.require(XmlPullParser.END_TAG, ns, "summary");
  52. return resum;
  53. }
  54.  
  55. //Extrau el valor de text per les etiquetes títol, resum
  56. private String llegeixText(XmlPullParser parser) throws IOException, XmlPullParserException {
  57.  
  58. String resultat = "";
  59.  
  60. if (parser.next() == XmlPullParser.TEXT) {
  61. resultat = parser.getText();
  62. parser.nextTag();
  63. }
  64.  
  65. return resultat;
  66.  
  67. }

En aquests mètodes, el que es fa és llegir el contingut de l’XML i retornar un String. El mètode per obtenir l’enllaç és una mica diferent, ja que els enllaços en aquest XML no vénen dins d’una etiqueta, sinó com un atribut de la mateixa, tal com es pot observar en aquesta secció d’XML:

  1. <link rel="alternate" href="http://stackoverflow.com/questions/tagged/?tagnames=xml&amp;sort=newest" type="text/html" />

Per obtenir el valor de l’atribut heu de fer servir el mètode parser.getAttributeValue().

Així doncs, l’analitzador anirà recorrent el fitxer XML tot analitzant les etiquetes i per cada entry crearà un objecte de la classe Entrada que quedarà emmagatzemat a un List <Entrada>, que serà retornat. Quan tinguem tota la informació de les entrades, aquestes s’hauran de mostrar en la nostra aplicació. Existeixen diferents formes de fer-ho: amb objectes creats dinàmicament, amb ListView… la que durem a terme nosaltres consisteix a crear un codi HTML a partir de la informació, que mostrarem en un WebView.

  1. ...
  2. //Llista de d'entrades de notícies
  3. List<Entrada> entrades = null;
  4.  
  5. //Cadena on construirem el codi HTML que mostrarà el widget webView
  6. StringBuilder htmlString = new StringBuilder();
  7.  
  8. ...
  9.  
  10. entrades = analitzador.analitza(stream);
  11.  
  12. ...
  13.  
  14. //analitzador.parse() retorna una llista (entrades) d'entrades de notícies (objectes
  15. //de la classe Entrada). Cada objecte representa un post de l'XML Feed. Ara es processen
  16. //les entrades de la llista per crear un codi HTML. Per cada entrada es crea un enllaç a la notícia completa
  17.  
  18. //Si tenim notícies
  19. if(entrades != null) {
  20.  
  21.  
  22. //Creem l'HTML a partir dels continguts del List<Entrada>
  23.  
  24. //Per indicar quan s'ha actualitzat l'RSS
  25.  
  26. Calendar ara = Calendar.getInstance();
  27. DateFormat formatData = new SimpleDateFormat("dd MMM h:mmaa");
  28.  
  29. //Títol de la pàgina
  30. htmlString.append("<h3> Noticies </h3>");
  31.  
  32. //Data d'actualització
  33. htmlString.append("<em>Actualitzat el " + formatData.format(ara.getTime()) + "</em>");
  34.  
  35. //Per cada notícia de la llista
  36. for (Entrada noticia : entrades) {
  37. //Creem un títol de la notícia que serà un enllaç HTML a la notícia completa
  38. htmlString.append("<p> <a href='");
  39. htmlString.append(noticia.enllac);
  40. htmlString.append("'>" + noticia.titol + "</a>");
  41.  
  42. //Si la notícia té un resum, l'afegim (els primers 70 chars)
  43. String breu = noticia.resum.substring(0, 70);
  44.  
  45. htmlString.append("<br><i>Resum:</i>"+breu+"...");
  46. htmlString.append("</p> <hr>");
  47. }
  48. }
  49. ...
  50. setContentView(R.layout.main);
  51.  
  52. //Mostra la cadena HTML a l'UI a través del WebView
  53. WebView myWebView = (WebView) findViewById(R.id.webView1);
  54.  
  55. myWebView.loadData(htmlString.toString(), "text/html", null);

Es van llegint totes les entrades del List i generant el codi HTML per tal que es mostri a l’aplicació tal com es pot veure a la figura.

Podeu descarregar el codi de la solució en el document XMLParse.zip, que trobareu en la secció “Annexos” del web del mòdul.

Figura Lector d’RSS en funcionament
Anar a la pàgina anterior:
Annexos
Anar a la pàgina següent:
Activitats