Documentos de Académico
Documentos de Profesional
Documentos de Cultura
App Android-1
Puntos a tratar
Creacin del proyecto con Eclipse Manifest, actividad principal y recursos Vista de tabla, layout para las filas, adaptador Descarga y parsing de XML en segundo plano Descarga de imgenes en segundo plano Dilogo, men, cambios de orientacin Intents para sntesis del habla y navegador Generar paquete y firmarlo
App Android-2
Configuracin de Eclipse:
Windows > Preferences > Android > SDK Location: Indicamos la ruta donde hemos descomprimido el Android SDK
App Android-3
AVD Manager
Crear nuevo dispositivo virtual (AVD) Seleccionar la versin de Android
App Android-4
Emulador
App Android-5
Crear el proyecto
Asistente de Eclipse
Genera la estructura bsica del proyecto AndroidManifest.xml Actividad principal Layout Recursos
Aadir recursos
En values/strings.xml las cadenas de texto que vamos a mostrar en la interfaz En drawable, el icono de CAMON a alta, media y baja resolucin En layout/main.xml la disposicin de los componentes de la interfaz grfica
App Android-7
strings.xml
Introducimos (en la pestaa XML):
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">CAMON</string> <string name="url_base">http://www.tucamon.es</string> <string name="url_rss">http://www.tucamon.es/welcome/rss?format=rss</string> <string name="noitems">Lista vaca</string> <string name="errordered">Error en la descarga!</string> <string name="calla">Calla!</string> <string name="recargar">Recargar</string> <string name="acerca_de">Acerca de...</string> <string name="acerca_de_camon">Acerca de CAMON</string> <string name="licencia">Esta aplicacin descarga las noticias RSS de www.tucamon.es y las muestra. Fue desarrollada en el taller de Android llevado a cabo en el aula de CAMON de Alicante en enero de 2011, por los asistentes y por Boyn Bonev (Universidad de Alicante) Esta aplicacin no es \"oficial\" de CAMON, no se puede vender y su finalidad es didctica. La Universidad de Alicante y CAMON no se responsabilizan de su uso y modificaciones hechas por terceros.</string> <string <string <string <string <string <string <string <string <string name="aceptar">Aceptar</string> name="ok">OK</string> name="leemelo">Lemelo</string> name="web">Web</string> name="atras">Atrs</string> name="espere">Espere...</string> name="descargandonoticias">Descargando noticias</string> name="descargandonoticia">Descargando noticia</string> name="imagenes_descargadas">Imgenes descargadas</string>
</resources>
App Android-8
Drawable
En las carpetas drawable introducimos el icono de la CAMON, redimensionado a tres resoluciones diferentes:
drawable-hdpi/icon.png de 72x72 pxeles drawable-mdpi/icon.png de 48x48 pxeles drawable-ldpi/icon.png de 36x36 pxeles
App Android-9
layout/main.xml
Introducimos un ListView que ser la tabla donde iremos colocando las noticias de la web de CAMON. De momento no producir ningn resultado visible.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ListView android:id="@+id/ListView01" android:layout_width="fill_parent" android:layout_height="fill_parent" android:scrollbars="vertical" /> </LinearLayout>
App Android-10
AndroidManifest.xml
Especificaremos que la aplicacin necesita permisos para acceder a Internet El XML quedara as:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="es.tucamon" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".Camon" 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> <uses-sdk android:minSdkVersion="4" />
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
App Android-11
Actividad principal
es.tucamon.Camon.java Aadimos dos cadenas que nos harn falta, recogindolas de los recursos string. Aadimos una variable Context porque accederemos a ella varias veces.
public class Camon extends Activity { private static String URL_BASE; private static String URL_RSS; Context context; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Context = getApplicationContext(); URL_BASE = getString(R.string.url_base); URL_RSS = getString(R.string.url_rss); } }
App Android-12
ListView listView;
/** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Context = getApplicationContext(); URL_BASE = getString(R.string.url_base); URL_RSS = getString(R.string.url_rss);
listView = (ListView)findViewById(R.id.ListView01); listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int position, long id) {
// en "id" tenemos el nmero de fila seleccionada
} });
} }
App Android-13
Clase noticia
Creamos una nueva clase Noticia.java en el mismo paquete
public String getTitulo() { return titulo; } public void setTitulo(String titulo) { this.titulo = titulo; } public Spanned getDescripcion() { return descripcion; } public void setDescripcion(Spanned descripcion) { this.descripcion = descripcion; } public class Noticia { public String getLink() { private String titulo; return link; private Spanned descripcion; } private String link; public void setLink(String link) { private String fecha; this.link = link; private String linkImagen; } private Drawable imagen; public String getFecha() { return fecha; public Noticia(){ } titulo = ""; public void setFecha(String fecha) { descripcion = new SpannedString(""); this.fecha = fecha; link = ""; } fecha = ""; public String getLinkImagen() { linkImagen = ""; return linkImagen; } } public Noticia(String titulo, String fecha, Spanned descripcion, public void setLinkImagen(String linkImagen) { String link,String linkImagen){ this.linkImagen = linkImagen; this.titulo=titulo; } this.fecha=fecha; public void setImagen(Drawable imagen) { this.descripcion=descripcion; this.imagen = imagen; this.link=link; } this.linkImagen=linkImagen; public Drawable getImagen() { } return imagen; } }
App Android-14
App Android-16
listView.setAdapter(noticiasAdapter);
listView.setOnItemClickListener(new OnItemClickListener() {
App Android-17
Declaracin de AsyncTask
La declaramos dentro de la clase Camon para poder acceder a sus campos (sus variables)
public class Camon extends Activity { private static String URL_BASE; private static String URL_RSS; Context context; ListView listView; ArrayList<Noticia> noticias; NoticiasAdapter noticiasAdapter; TareaDescarga tarea; @Override public void onCreate(Bundle savedInstanceState) { // ... lanzaDescargaDeNoticias(); } void lanzaDescargaDeNoticias(){ try { tarea = new TareaDescarga(); tarea.execute(new URL(URL_RSS)); } catch (MalformedURLException e) { e.printStackTrace(); } } private class TareaDescarga extends AsyncTask<URL, String, List<Noticia>>{ // ... } }
App Android-19
Mtodos de AsyncTask
private class TareaDescarga extends AsyncTask<URL, String, List<Noticia>>{ ArrayList<Noticia> noticiasDescargadas; ProgressDialog progressDialog; boolean error=false; @Override protected List<Noticia> doInBackground(URL... params) { return noticiasDescargadas; } @Override protected void onPreExecute() { } @Override protected void onCancelled() { } @Override protected void onProgressUpdate(String... values) { } @Override protected void onPostExecute(List<Noticia> result) { } }
App Android-20
Mtodos de AsyncTask
El mtodo doInBackground( ) realizar la descarga pero no podr acceder a la interfaz; lo har solicitando la ejecucin de onProgressUpdate( ) y onPostExecute( ) Tambin declaramos un ProgressDialog que debemos ir actualizando durante el progreso de la descarga (y parsing de XML).
App Android-21
App Android-22
AsyncTask doInBackground( )
Utilizaremos XmlPullParser para trocear el documento XML.
@Override protected List<Noticia> doInBackground(URL... params) { try { URL url = params[0]; noticiasDescargadas = new ArrayList<Noticia>(); XmlPullParserFactory parserCreator = XmlPullParserFactory.newInstance(); XmlPullParser parser = parserCreator.newPullParser(); parser.setInput(url.openStream(), null); int parserEvent = parser.getEventType(); int nItems = 0; while (parserEvent != XmlPullParser.END_DOCUMENT) { switch (parserEvent) { // Examinar la informacin parseada // ... } parserEvent = parser.next(); } } catch (Exception e) { Log.e("Net", "Error in network call", e); error = true; } return noticiasDescargadas; }
App Android-23
Informacin a parsear
Cdigo fuente descargado de http://www.tucamon.es/welcome/rss?format=rss
<?xml version="1.0" encoding="UTF-8"?> <rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"> <channel> <atom:link type="application/rss+xml" rel="self" href="http://www.tucamon.es/contenido/rss"/> <title>Feed de artículos</title> <link>http://www.tucamon.es/</link> <description>Feed de artículos</description> <language>es-ES</language> <item> <title>Presentación de Los zapatos de tacón rojos</title> <description><p>El <strong> ... <img alt="Zapatos" src="/photo_posts/0000/8463/zapatos.jpg?1293014230" /></description> <author>(Chivone)</author> <pubDate>Wed, 22 Dec 2010 11:46:57 +0100</pubDate> <link>/contenido/presentacion-de-los-zapatos-de-tacon-rojos</link> <guid>/contenido/presentacion-de-los-zapatos-de-tacon-rojos</guid> </item> <item> <title>D-FORMA inaugura su exposición en CAMON Madrid</title> <description><p>El colectivo D-Forma present&oacute; en CAMON& ... </description> <author>(Silvia Muñoz García)</author> <pubDate>Tue, 21 Dec 2010 19:56:47 +0100</pubDate> <link>/contenido/d-forma-emulo-exposicion-paisaje-terro</link> <guid>/contenido/d-forma-emulo-exposicion-paisaje-terro</guid> </item> ...
App Android-24
String imageSource=null; class ImageGetter implements Html.ImageGetter { public Drawable getDrawable(String source) { imageSource = source; return new BitmapDrawable(); } };
App Android-25
Primer prototipo
Tras descargar las noticias debe mostrarlas en la tabla El formato es el indicado por fila.xml y el adaptador que hemos implementado.
CAMON Alicante, 11-12 enero 2010 Depto. Ciencia de la Computacin e IA (Univ. Alicante) App Android-26
Descarga de imgenes
Aadimos a Noticia un mtodo que descarga la imagen de la noticia, dada una URL
public class Noticia { private String titulo; private Spanned descripcion; private String link; private String fecha; private String linkImagen; private Drawable imagen; //... public void loadImagen(String url) throws MalformedURLException, IOException{ InputStream is = (InputStream) new URL(url).getContent(); imagen = Drawable.createFromStream(is, "src"); } }
App Android-27
private class TareaDescargaImagen extends AsyncTask<List<Noticia>, String, Drawable>{ @Override protected Drawable doInBackground(List<Noticia>... arg0) { List<Noticia> noticias = arg0[0]; int imagenesSinCargar = noticias.size(); while(imagenesSinCargar > 0){ imagenesSinCargar = noticias.size(); for(Noticia n:noticias){ if(n.getImagen()==null || n.getImagen().getIntrinsicHeight() <= 0){ // Reintento necesario? try{ n.loadImagen(n.getLinkImagen()); publishProgress(""); }catch(Exception e){ n.setImagen(getResources().getDrawable(R.drawable.icon)); } }else{ imagenesSinCargar --; } } try { Thread.sleep(1000); } catch (InterruptedException e) { } } return null; } @Override protected void onPostExecute(Drawable result) { super.onPostExecute(result); Toast.makeText(context, getString(R.string.imagenes_descargadas), Toast.LENGTH_SHORT).show(); noticiasAdapter.notifyDataSetChanged(); } @Override protected void onProgressUpdate(String... values) { super.onProgressUpdate(values); noticiasAdapter.notifyDataSetChanged(); } }
App Android-28
Iniciar la descarga
Podemos iniciar la descarga de imgenes una vez que las noticias se hayan cargado.
private class TareaDescarga extends AsyncTask<URL, String, List<Noticia>>{ // ... @Override protected void onPostExecute(List<Noticia> result) { super.onPostExecute(result); for(Noticia n:noticiasDescargadas){ noticiasAdapter.add(n); } noticiasAdapter.notifyDataSetChanged(); progressDialog.dismiss(); if(error){ Toast.makeText(context, R.string.errordered, Toast.LENGTH_LONG).show(); } lanzaDescargaDeImagenes(); } } void lanzaDescargaDeImagenes(){ TareaDescargaImagen tdi = new TareaDescargaImagen(); tdi.execute(noticias); }
App Android-29
App Android-30
App Android-31
Dilogo Acerca de
case R.id.item03: AlertDialog.Builder ab=new AlertDialog.Builder(Camon.this); ab.setTitle(R.string.acerca_de_camon); ab.setIcon(getResources().getDrawable(R.drawable.icon)); ab.setMessage(R.string.licencia); ab.setPositiveButton(R.string.aceptar,null); ab.show(); break;
App Android-32
App Android-33
App Android-34
Cambios de orientacin
Cuando cambia la orientacin del mvil de vertical a horizontal, Android la reinicia para que todos los componentes se coloquen de nuevo en la nueva configuracin de pantalla. En consecuencia las noticias se empiezan a cargar de nuevo. Hay 3 maneras de evitarlo:
Deshabilitarlo: <activity android:name="Camon"
android:configChanges="keyboardHidden|orientation">
Ocuparnos de recolocar los componentes (complicado). Guardarnos las noticias para no volver a bajarlas.
CAMON Alicante, 11-12 enero 2010 Depto. Ciencia de la Computacin e IA (Univ. Alicante) App Android-35
Cambios de orientacin
public void onCreate(Bundle savedInstanceState) { // ... final ArrayList<Noticia> data = (ArrayList<Noticia>) getLastNonConfigurationInstance(); if (data == null) { noticias = new ArrayList<Noticia>(); noticiasAdapter = new NoticiasAdapter(context, R.layout.fila, noticias); lanzaDescargaDeNoticias(); }else{ noticias = data; noticiasAdapter = new NoticiasAdapter(context, R.layout.fila, noticias); } // ... } @Override public Object onRetainNonConfigurationInstance() { final ArrayList<Noticia> data = noticias; return data; }
App Android-36
App Android-37
Speech
Creamos, en un fichero aparte, una clase que encapsule toda la lgica necesaria para utilizar la sntesis del habla: Speech.java
package es.tucamon; import java.util.Locale; import android.app.Activity; import android.content.Intent; import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech.Engine; import android.speech.tts.TextToSpeech.OnInitListener; /** * Encapsulates some methods for using TextToSpeech from an Activity. * @author Boyan Bonev, Universidad de Alicante * @date December 20th, 2010 */ public class Speech { private Activity activity; private TextToSpeech tts; private static final int TTS_DATA_CHECK = 1; private String text; private boolean isTTSinstalled = false; private boolean isTTSinitialized = false;
/** * Gets the reference to the Activity for being able to call Intents. * @param activity */ public Speech(Activity activity){ this.activity = activity; } // ... }
App Android-39
Mtodos de Speech
/** * Does not assume that TextToSpeech is installed but starts an intent * for checking whether it is or not. The Activity which calls this * function should implement: * protected void onActivityResult(int requestCode, int resultCode, Intent data) { * speech.installOrSpeak(requestCode,resultCode); * } * @param txt Text to speak */ public void speakAfterCheckingForTTS(String txt){ this.text = txt; if(isTTSinitialized){ tts.speak(txt, TextToSpeech.QUEUE_ADD, null); }else{ Intent intent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA); activity.startActivityForResult(intent, TTS_DATA_CHECK); } } /** * Assumes that TextToSpeech is installed * @param txt Text to speak */ public void speakWithoutCheckingForTTS(String txt){ tts = new TextToSpeech(activity, new OnInitListener() { public void onInit(int status) { if (status == TextToSpeech.SUCCESS) { Locale loc = new Locale("es","",""); if (tts.isLanguageAvailable(loc) >= TextToSpeech.LANG_AVAILABLE) tts.setLanguage(loc); tts.setPitch(0.8f); tts.setSpeechRate(1.1f); isTTSinitialized = true; //Speak this: tts.speak(text, TextToSpeech.QUEUE_ADD, null); } } }); } /** * Called after checking for the installation of TTS * @param requestCode * @param resultCode */ public void installOrSpeak(int requestCode, int resultCode){ if(requestCode == TTS_DATA_CHECK){ if (resultCode == Engine.CHECK_VOICE_DATA_PASS) { isTTSinstalled = true; speakWithoutCheckingForTTS(text); } else { Intent installVoice = new Intent(Engine.ACTION_INSTALL_TTS_DATA); activity.startActivity(installVoice); } } } /** * Should be called in the onPause() or onStop() method of the Activity. */ public void stop(){ if (tts != null) { tts.stop(); tts.shutdown(); isTTSinitialized = false; } } public boolean isTTSinstalled() { return isTTSinstalled; }
App Android-40
speech = new Speech(this); // ... } protected void onActivityResult(int requestCode, int resultCode, Intent data) { speech.installOrSpeak(requestCode,resultCode); } @Override public void onStop() { speech.stop(); super.onStop(); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()){ case R.id.item01: speech.stop(); break; case R.id.item02: lanzaDescargaDeNoticias(); break; case R.id.item03: // ... break; } return true; }
App Android-41
Speech en NoticiaAlertDialog
Para pronunciar la descripcin de la noticia al pulsar leer del NoticiaAlertDialog, le pasaremos por el constructor el objeto speech ya inicializado .
public class NoticiaAlertDialog extends AlertDialog.Builder { private Noticia n; private Context c; private Speech s; protected NoticiaAlertDialog(Context context, Speech speech, Noticia noticia) { super(context); n = noticia; c = context; s = speech; this.setTitle(n.getTitulo()); this.setIcon(context.getResources().getDrawable(R.drawable.icon)); this.setMessage(n.getDescripcion()); this.setNegativeButton(context.getString(R.string.atras), null); this.setPositiveButton(context.getString(R.string.leemelo), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { s.speakAfterCheckingForTTS(n.getTitulo()+". "+n.getDescripcion()); } }); this.setNeutralButton(context.getString(R.string.web), new DialogInterface.OnClickListener() { // ... }); } }
App Android-42
Paquete
Podemos generar un paquete instalable de prueba, firmndolo con un certificado self-signed (no firmado por ninguna autoridad certificadora).
keytool -genkey -v -keystore mialmacen.keystore -alias aliasname -keyalg RSA -validity 10000 Introducimos una contrasea para el almacn y otra para el certificado (puede ser la misma). Eclipse: sobre el proyecto, Android tools > Export signed package. Nos pedir abrir el almacn, y su contrasea.
App Android-44
Probar el paquete
Debemos guardarlo en el dispositivo mvil, copindolo por cable o envindolo por e-mail.
Si al enviar el .apk a gmail, y al intentar abrirlo da un error, se puede probar renombrndolo a .zip; entonces el cliente de gmail nos permite guardarlo; le cambiamos el nombre a .apk (con un gestor de archivos) y lo ejecutamos.
Al ejecutarlo se inicia el instalador que nos advierte que la aplicacin necesita acceso a Internet.
CAMON Alicante, 11-12 enero 2010 Depto. Ciencia de la Computacin e IA (Univ. Alicante) App Android-45
Preguntas...?
App Android-46