Activitats

Descàrrega d'una imatge de la xarxa

L’objectiu d’aquesta activitat és crear una aplicació que ens permeti descarregar una imatge de la xarxa fent servir un AsyncTask.

Quan estem descarregant informació de la xarxa depenem de la connexió i de la velocitat de descàrrega, és per això que no podem preveure quant de temps es trigarà a obtenir un fitxer per petit que sigui. Si un usuari no disposa de feedback durant una descàrrega, es pot trobar desorientat i fins i tot pensar que l’aplicació no està responent. Per evitar aquest tipus de situacions hem de mostrar en tot moment en quin punt es troba la descàrrega, ja sigui mitjançant una barra de progrés o una informació numèrica.

L’AsyncTask disposa de tots els mètodes necessaris per realitzar aquesta activitat:

  • onPreExecute: esborra la imatge que conté l'ImageView i estableix la barra de progrés a 0.
  • doInBackground: descarrega la imatge i va fent crides a onProgressUpdate per anar actualitzant el progrés.
  • onProgressUpdate: actualitza el valor de la barra de progrés. La raó de ser d’aquest mètode és que doInBackground no permet fer canvis en la interfície d’usuari.
  • onPostExecute: una vegada ha acabat de descarregar la imatge, la inserim a l'ImageView.

Creeu un nou projecte que contingui activitat buida amb les següents propietats:

  • Application name: DescarregaImatge
  • Company domain: cat.xtec.ioc
  • Blank Activity: DescarregaImatge

El layout serà el següent:

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
  3. android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
  4. android:paddingRight="@dimen/activity_horizontal_margin"
  5. android:paddingTop="@dimen/activity_vertical_margin"
  6. android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".DescarregaImatge"
  7. android:orientation="vertical">
  8.  
  9. <Button
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content"
  12. android:text="@string/descarrega"
  13. android:id="@+id/buttonDescarrega"
  14. />
  15.  
  16. <ProgressBar
  17. style="?android:attr/progressBarStyleHorizontal"
  18. android:layout_width="match_parent"
  19. android:layout_height="wrap_content"
  20. android:id="@+id/progressBar"
  21. />
  22.  
  23. <ImageView
  24. android:layout_width="match_parent"
  25. android:layout_height="match_parent"
  26. android:id="@+id/imageView" />
  27.  
  28. </LinearLayout>

Recordeu-vos de crear l’string al fitxer strings.xml. El resultat serà similar al que es mostra en la figura

Figura Vista de l’activitat

La nostra aplicació necessita descarregar informació de la xarxa i guardar-la a la memòria del dispositiu. Per tal que funcioni, haurem de definir els següents permisos al fitxer AndroidManifest.xml:

  1. <!-- Permisos -->
  2. <uses-permission android:name="android.permission.INTERNET"/>
  3. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  4. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Això donarà com a resultat un fitxer similar al que es mostra tot seguit:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="ioc.xtec.cat.descarregaimatge" >
  4.  
  5. <!-- Permisos -->
  6. <uses-permission android:name="android.permission.INTERNET"/>
  7. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  8. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  9.  
  10. <application
  11. android:allowBackup="true"
  12. android:icon="@drawable/ic_launcher"
  13. android:label="@string/app_name"
  14. android:theme="@style/AppTheme" >
  15. <activity
  16. android:name=".DescarregaImatge"
  17. android:label="@string/app_name" >
  18. <intent-filter>
  19. <action android:name="android.intent.action.MAIN" />
  20.  
  21. <category android:name="android.intent.category.LAUNCHER" />
  22. </intent-filter>
  23. </activity>
  24. </application>
  25.  
  26. </manifest>

Una vegada haguem definit el layout i els permisos necessaris passarem a definir l’activitat que durà a terme la descàrrega de la imatge.

  1. package ioc.xtec.cat.descarregaimatge;
  2.  
  3. import android.graphics.drawable.Drawable;
  4. import android.os.AsyncTask;
  5. import android.os.Environment;
  6. import android.support.v7.app.ActionBarActivity;
  7. import android.os.Bundle;
  8. import android.util.Log;
  9. import android.view.View;
  10. import android.widget.Button;
  11. import android.widget.ImageView;
  12. import android.widget.ProgressBar;
  13.  
  14. import java.io.FileOutputStream;
  15. import java.io.IOException;
  16. import java.io.InputStream;
  17. import java.io.OutputStream;
  18. import java.net.HttpURLConnection;
  19. import java.net.URL;
  20.  
  21.  
  22. public class DescarregaImatge extends ActionBarActivity implements View.OnClickListener {
  23.  
  24. // TODO Indiquem aquí la URL de la imatge que volem descarregar
  25. public static String img = "";
  26.  
  27. // Crearem el recurs a la memòria del telèfon, definim la ruta fins el nom del fitxer
  28. public static String path_imatge = Environment.getExternalStorageDirectory().toString() + "/imatge.jpg";
  29.  
  30. // Declarem els components de la UI
  31. private Button btn_descarrega;
  32. private ProgressBar barra_progres;
  33. private ImageView image_vista;
  34.  
  35. @Override
  36. protected void onCreate(Bundle savedInstanceState) {
  37. super.onCreate(savedInstanceState);
  38. setContentView(R.layout.activity_descarrega_imatge);
  39.  
  40. // Afegim el listener al botó
  41. btn_descarrega = (Button) findViewById(R.id.buttonDescarrega);
  42. btn_descarrega.setOnClickListener(this);
  43.  
  44. // Posem la barra de progrés amb un màxim de 100
  45. barra_progres = (ProgressBar) findViewById(R.id.progressBar);
  46. barra_progres.setMax(100);
  47.  
  48. // Assignem l'ImageView de la vista
  49. image_vista = (ImageView) findViewById(R.id.imageView);
  50.  
  51. }
  52. @Override
  53. public void onClick(View v) {
  54.  
  55. // Gestió del clic
  56.  
  57. }
  58. }

Android ens obliga a fer les descàrregues de la xarxa des d’un fil independent i mai des del fil principal. Hi ha diverses maneres de fer-ho però la més clara i simple és mitjançant un AsyncTask. Pel nostre exemple, haurem de crear una classe que hereti les propietats d‘AsyncTask<String, Integer, Void>, és a dir, que el doInBackground rebrà Strings com a paràmetres d’entrada, actualitzarà el progrés de la UI amb Integers i no enviarà cap paràmetre a l'onPostExecute.

Al doInBrackground farem una connexió HTTP cap a la URL indicada i anirem emmagatzemant la imatge a un buffer de memòria, l’anirem guardant al disc i mentrestant actualitzarem la barra de progrés fent la crida a publishProgress.

El següent codi l’afegirem dins de l'activity:

  1. // AsyncTask que descarrega la imatge de la xarxa, rep strings com a paràmetre d'entrada, actualitza el progrés amb Integers i no retorna res
  2. private class TascaDescarrega extends AsyncTask<String, Integer, Void> {
  3.  
  4. // OnPreExecute s'executa abans que doInBackground
  5. @Override
  6. protected void onPreExecute() {
  7. super.onPreExecute();
  8.  
  9. // Eliminem la imatge i posem la barra de progrés a 0
  10. barra_progres.setProgress(0);
  11. image_vista.setImageDrawable(null);
  12.  
  13. }
  14.  
  15. // Tasca que realitza les operacions de xarxa, no es pot manipular l'UI des d'aquí
  16. @Override
  17. protected Void doInBackground(String... url) {
  18. try {
  19.  
  20. // Agafem la URL que s'ha passat com argument
  21. URL imatge = new URL(url[0]);
  22.  
  23. // Fem la connexió a la URL i mirem la mida de la imatge
  24. HttpURLConnection connection = (HttpURLConnection) imatge.openConnection();
  25. int totalImatge= connection.getContentLength();
  26.  
  27. // Creem l'input i un buffer on anirem llegint la informació
  28. InputStream inputstream = (InputStream) imatge.getContent();
  29. byte[] bufferImatge = new byte[1024];
  30.  
  31. // Creem la sortida, és a dir, un objecte on guardarem la informació (ruta de la imatge)
  32. OutputStream outputstream = new FileOutputStream(path_imatge);
  33.  
  34. int descarregat = 0;
  35. int count;
  36.  
  37. // Mentre hi hagi informació per llegir
  38. while ((count = inputstream.read(bufferImatge)) != -1) {
  39.  
  40. // Acumulem tot el que s'ha llegit
  41. descarregat += count;
  42.  
  43. // Calculem el percentatge respecte el total i l'enviem a publishProgress
  44. publishProgress(((descarregat * 100) / totalImatge));
  45.  
  46. // Guardem al disc el que hem descarregat
  47. outputstream.write(bufferImatge, 0, count);
  48.  
  49. }
  50.  
  51. // Tanquem els "stream"
  52. inputstream.close();
  53. outputstream.close();
  54.  
  55.  
  56. } catch (IOException exception) {
  57.  
  58. Log.d("ERR", "Alguna cosa no ha anat bé!");
  59. return null;
  60.  
  61. }
  62.  
  63. // No passem cap informació al onPostExecute
  64. return null;
  65.  
  66. }
  67.  
  68. @Override
  69. protected void onProgressUpdate(Integer... values) {
  70.  
  71. // Actualitzem la barra de progrés amb el valor que se'ns ha enviat des de doInBackground
  72. barra_progres.setProgress(values[0]);
  73. }
  74.  
  75. @Override
  76. protected void onPostExecute(Void aVoid) {
  77.  
  78. // Quan acaba la tasca, inserim la imatge a l'ImageView
  79. image_vista.setImageDrawable(Drawable.createFromPath(path_imatge));
  80.  
  81. }
  82. }

Només ens queda fer la crida a l’AsyncTask quan es faci clic al botó de descàrrega. Per fer-ho, canvieu el codi de l'onClick pel següent:

  1. @Override
  2. public void onClick(View v) {
  3.  
  4. // Executem l'AsyncTask passant-li com a argument la ruta de la imatge.
  5. if (v == btn_descarrega) new TascaDescarrega().execute(img);
  6.  
  7. }

A la figura podeu veure el resultat després de descarregar una imatge:

Figura Imatge descarregada

Podeu descarregar el codi de l’aplicació en el següent enllaç:

Descàrrega de dades text

L’objectiu d’aquesta activitat és aprendre a establir una connexió HTTP i fer-la servir per descarregar dades de tipus text.

A banda de les dades binàries, és molt probable que les vostres aplicacions facin servir connexions HTTP per descarregar dades de text, com ara contingut de pàgines web, fitxers de configuració de la vostra aplicació, fitxers de dades que per les seves característiques siguin més senzills d’emmagatzemar en format text… Tots ells estan codificats en format de cadena de caràcters.

Descarregar dades de tipus text és molt semblant a descarregar dades binàries. El procediment de connexió, de fet, és idèntic:

  1. private String descarregaText(String URL) {
  2. int BUFFER_SIZE = 2000; //Mida del buffer de text
  3. BufferedInputStream in; //Flux de dades de lectura
  4.  
  5. try {
  6. //Obrim la connexió
  7. in = ObreConnexioHTTP(URL);
  8. } catch (IOException e) {
  9. //Error
  10. Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
  11. e.printStackTrace();
  12. return "";
  13. }
  14.  
  15. //Obtenim un flux de caràcters
  16.  
  17. char[] inputBuffer = new char[BUFFER_SIZE]; //Buffer de caràcters
  18. int caractersLlegits; //Caràcters llegits
  19. String stringResultat = ""; //String resultat
  20.  
  21.  
  22. try {
  23. //Mentre s'hagin llegit caràcters
  24. while ((caractersLlegits = isr.read(inputBuffer)) > 0) {
  25. //Convertim els caràcters a String
  26. String stringLlegit = String.copyValueOf(inputBuffer, 0, caractersLlegits);
  27.  
  28. //Afegim els caràcters llegits al resultat
  29. stringResultat += stringLlegit;
  30.  
  31. }
  32. //Tanquem la connexió
  33. in.close();
  34. } catch (IOException e) {
  35. //Excepció
  36. Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
  37. e.printStackTrace();
  38. return "";
  39. }
  40.  
  41. //Retornem el resultat
  42. return stringResultat;
  43.  
  44. }
  45.  
  46. private BufferedInputStream ObreConnexioHTTP(String adrecaURL) throws IOException {
  47. int resposta;
  48.  
  49. URL url = new URL(adrecaURL);
  50. URLConnection connexio = url.openConnection();
  51.  
  52. if (!(connexio instanceof HttpURLConnection))
  53. throw new IOException("No connexió HTTP");
  54.  
  55. try {
  56. HttpURLConnection httpConn = (HttpURLConnection) connexio;
  57. httpConn.setAllowUserInteraction(false);
  58. httpConn.setInstanceFollowRedirects(true);
  59. httpConn.setRequestMethod("GET");
  60. httpConn.connect();
  61.  
  62. resposta = httpConn.getResponseCode();
  63. if (resposta == HttpURLConnection.HTTP_OK) {
  64. in = new BufferedInputStream(httpConn.getInputStream());
  65. }
  66. } catch (Exception ex) {
  67. throw new IOException("Error connectant");
  68. }
  69.  
  70. return in;
  71. }
  72. }

Quan hàgiu obtingut un flux de dades del servidor, heu d’obtenir un InputStreamReader. InputStreamReader és una classe que serveix per convertir un flux de dades en un flux de caràcters. Les dades llegides del flux d’origen es converteixen a caràcters.

  1. while( (caractersLlegits = isr.read(inputBuffer))>0 )

El mètode read() llegeix caràcters i els emmagatzema en la cadena de caràcters que se li passa com a argument, i retorna el nombre de caràcters que s’han llegit.

  1. String stringLlegit = String.copyValueOf(inputBuffer, 0, caractersLlegits);

El mètode copyValueof() crea un nou String a partir de l’array que se li passa com a argument des de la posició 0 fins a caractersLlegits. És a dir, crea un String a partir d‘InputBuffer des del començament amb mida caractersLlegits.

Aquest string es va afegint a l'string stringResultat, que serà el que retorni el mètode:

  1. //Afegim els caràcters llegits al resultat
  2. stringResultat += stringLlegit;

Finalment, al mètode onCreate() es fa servir el mètode que acabeu de crear per descarregar el text i, posteriorment, modificar el contingut del TextView.

  1. // Textview on es mostrarà la informació
  2. TextView tv;
  3.  
  4. @Override
  5. public void onCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. setContentView(R.layout.activity_main);
  8.  
  9. tv = (TextView) findViewById(R.id.textView1);
  10.  
  11. // Executem l'AsyncTask amb la URL
  12. new DescarregaText().execute("http://ca.wikipedia.org/wiki/Tirant_lo_Blanc");
  13.  
  14. }
  15.  
  16. // AsyncTask que descarrega text de la xarxa
  17. private class DescarregaText extends AsyncTask<String, Void, String> {
  18.  
  19. @Override
  20. protected String doInBackground(String... params) {
  21. // Descarreguem el text passat per argument
  22. return descarregaText(params[0]);
  23. }
  24.  
  25. @Override
  26. protected void onPostExecute(String s) {
  27. // Quan ha acabat la tasca, posem el text al TextView
  28. tv.setText(s);
  29. }
  30. }

Descarregueu tot seguit el document HTTPText.zip, que conté el codi de la solució:

13_httptext.zip ( 263.3 KB )

Anar a la pàgina anterior:
Programació de comunicacions
Anar a la pàgina següent:
Programació de comunicacions