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:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".DescarregaImatge" android:orientation="vertical"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/descarrega" android:id="@+id/buttonDescarrega" /> <ProgressBar style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/progressBar" /> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/imageView" /> </LinearLayout>
Recordeu-vos de crear l’string al fitxer strings.xml. El resultat serà similar al que es mostra en la figura
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:
<!-- Permisos --> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Això donarà com a resultat un fitxer similar al que es mostra tot seguit:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ioc.xtec.cat.descarregaimatge" > <!-- Permisos --> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".DescarregaImatge" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </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.
package ioc.xtec.cat.descarregaimatge; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Environment; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.ProgressBar; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; // TODO Indiquem aquí la URL de la imatge que volem descarregar // Crearem el recurs a la memòria del telèfon, definim la ruta fins el nom del fitxer public static String path_imatge = Environment.getExternalStorageDirectory().toString() + "/imatge.jpg"; // Declarem els components de la UI private ProgressBar barra_progres; private ImageView image_vista; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_descarrega_imatge); // Afegim el listener al botó btn_descarrega.setOnClickListener(this); // Posem la barra de progrés amb un màxim de 100 barra_progres = (ProgressBar) findViewById(R.id.progressBar); barra_progres.setMax(100); // Assignem l'ImageView de la vista image_vista = (ImageView) findViewById(R.id.imageView); } @Override // Gestió del clic } }
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
:
// 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 // OnPreExecute s'executa abans que doInBackground @Override protected void onPreExecute() { super.onPreExecute(); // Eliminem la imatge i posem la barra de progrés a 0 barra_progres.setProgress(0); image_vista.setImageDrawable(null); } // Tasca que realitza les operacions de xarxa, no es pot manipular l'UI des d'aquí @Override try { // Agafem la URL que s'ha passat com argument // Fem la connexió a la URL i mirem la mida de la imatge int totalImatge= connection.getContentLength(); // Creem l'input i un buffer on anirem llegint la informació byte[] bufferImatge = new byte[1024]; // Creem la sortida, és a dir, un objecte on guardarem la informació (ruta de la imatge) int descarregat = 0; int count; // Mentre hi hagi informació per llegir while ((count = inputstream.read(bufferImatge)) != -1) { // Acumulem tot el que s'ha llegit descarregat += count; // Calculem el percentatge respecte el total i l'enviem a publishProgress publishProgress(((descarregat * 100) / totalImatge)); // Guardem al disc el que hem descarregat outputstream.write(bufferImatge, 0, count); } // Tanquem els "stream" inputstream.close(); outputstream.close(); Log.d("ERR", "Alguna cosa no ha anat bé!"); return null; } // No passem cap informació al onPostExecute return null; } @Override // Actualitzem la barra de progrés amb el valor que se'ns ha enviat des de doInBackground barra_progres.setProgress(values[0]); } @Override // Quan acaba la tasca, inserim la imatge a l'ImageView image_vista.setImageDrawable(Drawable.createFromPath(path_imatge)); } }
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:
@Override // Executem l'AsyncTask passant-li com a argument la ruta de la imatge. if (v == btn_descarrega) new TascaDescarrega().execute(img); }
A la figura podeu veure el resultat després de descarregar una imatge:
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:
int BUFFER_SIZE = 2000; //Mida del buffer de text try { //Obrim la connexió //Error Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); e.printStackTrace(); return ""; } //Obtenim un flux de caràcters char[] inputBuffer = new char[BUFFER_SIZE]; //Buffer de caràcters int caractersLlegits; //Caràcters llegits try { //Mentre s'hagin llegit caràcters while ((caractersLlegits = isr.read(inputBuffer)) > 0) { //Convertim els caràcters a String //Afegim els caràcters llegits al resultat stringResultat += stringLlegit; } //Tanquem la connexió in.close(); //Excepció Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); e.printStackTrace(); return ""; } //Retornem el resultat return stringResultat; } int resposta; try { httpConn.setAllowUserInteraction(false); httpConn.setInstanceFollowRedirects(true); httpConn.setRequestMethod("GET"); httpConn.connect(); resposta = httpConn.getResponseCode(); } } return in; } }
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.
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.
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:
//Afegim els caràcters llegits al resultat 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
.
// Textview on es mostrarà la informació TextView tv; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextView) findViewById(R.id.textView1); // Executem l'AsyncTask amb la URL new DescarregaText().execute("http://ca.wikipedia.org/wiki/Tirant_lo_Blanc"); } // AsyncTask que descarrega text de la xarxa @Override // Descarreguem el text passat per argument return descarregaText(params[0]); } @Override // Quan ha acabat la tasca, posem el text al TextView tv.setText(s); } }
Descarregueu tot seguit el document HTTPText.zip, que conté el codi de la solució: