AguasNegras

This was not supposed to be like this

18 mayo, 2012
por Agustín Ventura
Sin comentarios

CrankPlayer, binding al servicio

El siguiente paso en el desarrollo resulta bastante obvio, ligar el CrankPlayer con el MediaService, o lo que es lo mismo, hacer un bind. Para no perderme, he hecho un pequeño diagrama de interacción entre CrankPlayer y MediaService.

En él se aprecia que aparte de los métodos de negocio necesarios (denotados por una línea discontínua), todavía me faltan por implementar dos métodos del ciclo de vida: el bind y el unbind, que son precisamente los que me permiten establecer una comunicación bidireccional con el MediaService para indicar acciones y realizar y obtener resultados.

Bind y Unbind de MediaService

La definición más pura de Service dice que este es un componente aislado que ejecuta una computación. Esto esta muy bien pero en realidad, si no te puedes comunicar con él, su uso es bastante limitado, para eso existen los bound services. Un bound service se puede combinar con un servicio “normal” (y de hecho es lo que hago) y además ofrece dos métodos adicionales, el bind y el unbind. Lo importante es que en el bind devuelve una interfaz que permite interactuar con él.

Primero entonces creo la clase que hará de fachada del servicio que extiende de Binder:

public class MediaServiceBinder extends Binder {
 
  private final MediaService mediaService;
 
  public MediaServiceBinder(MediaService mediaService) {
    this.mediaService = mediaService;
  }
}

A continuación, declaro una variable de esta clase en el servicio:

private final IBinder mediaServiceBinder = new MediaServiceBinder(this);

Y la devuelve en el método onBind:

@Override
public IBinder onBind(Intent bindingIntent) {
  return this.mediaServiceBinder;
}
 
@Override
public boolean onUnbind(Intent intent) {
  // Nothing to do here
  return super.onUnbind(intent);
}

El onUnbind, de momento he considerado que no requiere ninguna acción en especial.

Vale, con esto el servicio ya devuelve su fachada.

Ahora viene la parte de CrankPlayer, siguiendo el diagrama de interacción, voy a hacer el bind en el onStart:

@Override
public void onStart() {
  super.onStart();
  Intent intent = new Intent(this, MediaService.class);
  bindService(intent, mediaServiceConnection, Context.BIND_AUTO_CREATE);
}

Pero bindService no devuelve el MediaServiceBinder inmediatamente, sino que es devuelto asincronamente, para eso se pasa el objeto mediaServiceConnection, que es una implementación de ServiceConnection, además creo una instancia propia de MediaServiceBinder:

private MediaServiceBinder mediaServiceBinder = null;
 
private ServiceConnection mediaServiceConnection = new ServiceConnection() {
  public void onServiceConnected(ComponentName className,
    IBinder service) {
    mediaServiceBinder = (MediaServiceBinder) service;
  }
 
  public void onServiceDisconnected(ComponentName arg0) {
    mediaServiceBinder = null;
  }
};

Para acabar con esta parte, en el onPause de CrankPlayer, hago el unBind:

@Override
public void onPause() {
  unbindService(mediaServiceConnection);
  super.onPause();
}

Con ésto ya estan implementados los servicios del ciclo de vida, y ahora ya solo me queda por implementar la lógica de negocio.

TODO

  1. Añadir el MediaPlayer al MediaThread
  2. Definir fachada de MediaService (o lógica de negocio) en MediaServiceBinder.
  3. Implementar la lógica en MediaService y MediaThread.

Código

En GitHub (para variar):

CrankPlayer en GitHub

Repositorio de CrankPlayer en GitHub

17 mayo, 2012
por Agustín Ventura
Sin comentarios

CrankPlayer, explorador de archivos

Uno de mis requisitos (de hecho, el fundamental), es poder reproducir a partir de archivos sueltos o directorios.

Por tanto, necesitaré un explorador de archivos, aunque sea muy básico, que permita ver los archivos en la memoria del dispositivo y añadirlos a una lista de reproducción. Gráficamente, la interacción entre las actividades sería así. En general la devolución del parámetro no tendrá mayor problema, ya que se puede hacer a través del intent de vuelta.

Para crear el explorador de archivos voy a seguir este tutorial, por supuesto añadiendo cosas de mi cosecha según me vaya pareciendo.

Creación de CrankExplorer

En primer lugar creo la activity tal cual heredando de CrankActivity y le añado la implementación por defecto de todos los métodos del ciclo de vida.

Ahora la declaro en el AndroidManifest:

<activity android:name=".CrankExplorer" android:label="@string/explorer_name"/>

Para arrancarla, creo un botón en CrankActivity:

<Button
android:id="@+id/buttonOpenExplorer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/open_explorer"
android:onClick="openCrankExplorer" />

Y el listener:

public void openCrankExplorer(View v) {
  Intent openCrankExplorer = new Intent(this, CrankExplorer.class);
  startActivityForResult(openCrankExplorer, REQUEST_CODE);
}

Con esto ya puedo llamar a CrankExplorer desde CrankPlayer.

Ahora toca implementar CrankExplorer, en general la implementación seguirá estas líneas:

  1. Se mostrarán todos los archivos y directorios que se puedan leer.
  2. Si se hace click en un directorio se abrirá.
  3. Si se hace click en un archivo, se abrirá un diálogo de confirmación para añadirlo a la lista de reproducción.
  4. Si se deja presionado un directorio se abrirá un menú contextual para añadir todo su contenido a la lista de reproducción.

Al lío, siguiendo lo que comentan, hago un layout para los archivos, le voy a llamar file_row.xml y a continuación creo el layout de CrankExplorer, crankexplorer.xml. No tiene mucha historia ya que es una lista y punto.

En cuanto a la actividad, lo primero que hago es filtrar los archivos mediante un FilenameFilter, para que solo muestre aquellos directorios y archivos que sean legibles y si es un archivo, que además termine en mp3 u ogg.

Añado un onListItemClick y si es un directorio se invocará a getDir sobre él, mientras que si es un archivo, se mostrará un diálogo que permitirá dos acciones:

  1. Añadir el archivo a la lista de reproducción.
  2. Cancelar

Por último, para devolver la canción a CrankPlayer, creo un método finish().

Para ir acabando, falta el menú contextual que me permita seleccionar un directorio para añadir (junto con todos los archivos contenidos en él y sus subdirectorios).
Primero creo el layout del menu y lo llamo crankexplorer_context.xml a continuación en el onCreate, registro la lista para el menú contextual:

registerForContextMenu(getListView());

Después tengo que crear dos métodos del ciclo de vida, el primero, la creación del menú:

@Override
public void onCreateContextMenu(ContextMenu menu, View v,
  ContextMenuInfo menuInfo) {
  super.onCreateContextMenu(menu, v, menuInfo);
  menu.setHeaderTitle(R.string.directory_contextual_title);
  MenuInflater inflater = getMenuInflater();
  inflater.inflate(R.menu.crankexplorer_context, menu);
}

Y el segundo, la acción a ejecutar cuando se selecciona:

@Override
public boolean onContextItemSelected(MenuItem item) {
  switch (item.getItemId()) {
    case R.id.addDirectoryItem:
      AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
      .getMenuInfo();
      this.selected = new File(this.path.get((int) info.id));
      if (this.selected.isDirectory()) {
        finish();
      }
      return true;
   default:
     return super.onContextItemSelected(item);
  }
}

Para acabar, implemento el método finish() para devolver bien la canción seleccionada, bien todas las canciones:

@Override
public void finish() {
  Intent data = new Intent();
  if (this.selected != null) {
    if (this.selected.isFile()) {
      data.putExtra("selectedFile", this.selected);
    } else if (this.selected.isDirectory()) {
      List<File> filesInDirectory = this.explodeDir(this.selected);
      data.putExtra("selectedFiles",
        (ArrayList<File>) filesInDirectory);
    }
  }
  setResult(RESULT_OK, data);
  super.finish();
}

Y en CrankPlayer creo un onActivityResult que procese la vuelta desde CrankExplorer:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if (data.hasExtra("selectedFile")) {
    this.playlist.add((File) data.getExtras().get("selectedFile"));
    this.renderPlaylist();
  } else if (data.hasExtra("selectedFiles")) {
    List<File> fileList = (List<File>) data.getExtras().get(
      "selectedFiles");
    this.playlist.addAll(fileList);
    this.renderPlaylist();
  }
}

TODO

Pues ya solo queda:

  1. Hacer el bind del service en CrankPlayer
  2. Reproducir con un MediaPlayer en el thread.

Código

Sigue en GitHub

CrankPlayer en GitHub

Repositorio de CrankPlayer en GitHub

14 mayo, 2012
por Agustín Ventura
Sin comentarios

CrankPlayer, arquitectura

Bueno, ya va siendo hora de empezar la aplicación en serio.

Después de mucho documentarme sobre Activities, Services y Threads, creo que tengo una aproximación bastante buena a la arquitectura que voy a intentar implementar.

Introducción

Un requisito fundamental es que la aplicación consuma los mínimos recursos posibles, con lo cual cabría pensar que se puede implementar mediante una Activity normal y corriente. Esto es un error, una Activity tiene un ciclo de vida determinado y cabe suponer (aunque no he hecho la prueba) que en cuanto pasara al estado “Paused”, se terminaría la reproducción de música.

Por tanto hay que buscar otro componente que escape del ciclo de vida de la Activity y con el cual se pueda comunicar la Activity es decir, un Service. El ciclo de vida del Service hace que este permanezca vivo aún cuando la Activity pase a cualquier estado que no sea “Resumed”.

En el tutorial de CrankPlay ya se ha visto un ejemplo de esto, sin embargo este ejemplo estaba claramente desfasado y se le pueden criticar varios puntos desde el punto de vista de mis requisitos:

  1. La comunicación entre procesos (IPC) no me es necesaria, tan solo necesito que una única actividad acceda al servicio.
  2. La definición de la interfaz con AIDL tan solo es necesaria si se desea IPC, por tanto, basta con heredar de Service.
  3. Además, en cuanto a este Service, hay que considerar que no es algo que yo lance y me pueda olvidar de él, sino que tendré que comunicarme periódicamente con él (modificación de la playlist, start/stop, etc…), es decir, necesito un BoundService.
  4. Por último, tal y como dice la documentación en numerosos sitios, un Service se ejecuta en el mismo thread que el UI Thread, con lo cual… necesitaré un thread propio que se encargue de la reproducción (actividad pesada por definición).

Gráficamente, esta propuesta quedaría así.

Ahora voy a montar la infraestructura básica.

Implementación

La actividad de momento no tiene mayor historia, basta con dejarla tal cual.

Para implementar el Service, creo una nueva clase (MediaService) y hago que herede de Service. Para que sea realmente funcional, lo declaro en el AndroidManifest.xml:

<service android:name="com.crankcode.services.MediaService" android:exported="false"/>;

El atributo “android:exported=false”, lo que indica es que el servicio no estará disponible para otras actividades del sistema, un poco de programación defensiva.

Para el thread, simplemente creo la clase MediaThread y hago que implemente Runnable.

Para arrancar el MediaService, en el onCreate de CrankPlayer escribo lo siguiente:

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  startService(new Intent(getBaseContext(), MediaService.class));
  setContentView(R.layout.main);
}

Y en el MediaService, defino su onStart así:

@Override
public void onStart(Intent intent, int startId) {
  Log.d("CrankPlayer", "MediaService.onStart()");
  this.mediaThread = new Thread(new MediaThread());
  this.mediaThread.start();
  super.onStart(intent, startId);
}

Por último, en el run de MediaService meto un chivato:

public void run() {
  Log.d("CrankPlayer", "MediaThread.run");
}

Ahora, a arrancar y ver si todo ha ido bien.

Y todo correcto, funcionando a la primera.

TODO

  1. Añadir la instancia de MediaPlayer al Thread
  2. Hacer el bind del service en la activity.

Código

Como de costumbre, el código esta en GitHub:

CrankPlayer en GitHub

Repositorio de CrankPlayer en GitHub

16 abril, 2012
por Agustín Ventura
Sin comentarios

CrankPlay, controles de reproducción

Última parte del tutorial que sigo para el CrankPlay, MusicDroid, ahora voy a añadir los controles básicos de reproducción. Esta parte se me antoja que no me será de mucha utilidad, ya que es bastante dependiente del diseño de la aplicación, pero que demonios…

Controles de Reproducción

El objetivo de esta parte es añadir los controles de reproducción a la aplicación. Estos se mostrarán como una capa semitransparente sobre la lista de canciones.

Me cojo el xml que viene en la página y lo pego. Como ya me ha pasado antes, justo delante del “id” hay que añadir el namespace: “android:”. Además quito unos puntos y comas y listo. Ahora a ver si explico un poco como es el layout.

En primer lugar, si se mira el songlist.xml, lo primero que se aprecia es que usa un LinearLayout. Un LinearLayout posiciona los elementos hijos en forma de columna (o fila). Y punto. En songlist.xml posiciona los elementos en columna porque el atributo “android:orientation” esta establecido al valor “vertical”.

Esto tiene sentido porque se quiere representar una lista de valores en columna, pero sin embargo, los controles de reproducción se quieren situar los unos en función de los otros… Y esto es lo que hace un RelativeLayout, permite posicionar sus hijos relativamente entre ellos.

Lo que se hace en controls.xml es utilizar un RelativeLayout para que ocupe toda la pantalla y en su interior otro en el centro (controlado por “android:layout_centerVertical” y “android:layout_centerHorizontal”) que tendrá un área total de 170dip x 170dip. ¿Qué es un dip? Pues es un “Density Independent Pixel”, para hacer una idea, 1 dip = 1 pixel en una pantalla de 160 dpi. Si la pantalla tiene mayor resolución, se escala automáticamente. Vamos, que es una medida independiente del tamaño de la pantalla.

Por último, para situar los ImageView se utiliza “android:layout_alignParentTop, android:layout_centerHorizontal”, etc…

Añadiendo una Animación

Cosa que yo no sabía, en android se pueden declarar animaciones con xml. El tutorial propone añadir una que haga que un botón pulsado se haga más grande y otra vez más pequeño. La verdad que el xml es bastante entendible, escala por 1.5 la imagen, alcanzando este tamaño en 600 msg.

Para invocar la animación, pues por ejemplo en el OnKeyUp de los controles se invoca al método startAnimation de la vista correspondiente pasándole como parámetro la animación definida… ningún misterio.

Creando un Tema

Además de la animación, en el tutorial la actividad con los controles de reproducción es transparente y se superpone a la actividad con la lista de mp3, haciendo por tanto posible ver la canción que se esta reproducción. Para conseguir este efecto hay que diseñar un tema (una apariencia visual) para esta actividad.

Lo más sencillo, y lo que hacen en el tutorial es crear un tema que herede del tema por defecto de android y decirle que el color de fondo de la actividad sea transparente y que no tenga título. Todo esto en un archivo llamado styles.xml en “res/values”. Para crear el archivo, he tenido que modificar el atributo parent y establecerlo como “parent=”android:Theme”", ya que si no, no lo reconoce.

Una vez hecho esto avisa del epígrafe de “@drawable/transparent_background”. Esto es debido… a que no existe, jeje.  Para crearlo, en “res/values” hay que crear un archivo llamado colors.xml y pegarle la definición de este transparent background.

Por último, se define la actividad en el AndroidManifest.xml aplicándole el tema.

Creando la Actividad

Pues una vez declarada, toca crearla. El código que viene en la página es bastante sencillo, se limita a gestionar la conexión con el servicio y a ejecutar la animación sobre el botón pulsado.

Como en el artículo anterior, tengo que tocar el método bind y eliminar el parámetro null que se le pasa ya que esa signatura esta deprecada y en el OnKeyUp tengo que capturar también una RemoteException. Con ésto ya compila.

Pero antes, hay que cambiar la actividad CrankPlay para que cuando se pulse en un elemento, invoque la nueva actividad y entonces empiece a reproducir. Lo único que el tutorial esta tan pasao de rosca que el método startSubActivity ya no existe, así que haré una llamada a startActivityForResult.

Y con ésto ya lanzo y andando, todo perfecto :)

Conclusiones

Bueno, en primer lugar, el autor prometía una cuarta parte del tutorial… pero va a ser que nunca se llevó a cabo xD De entrada tal y como esta a día de hoy hay 4 grandes problemas:

  1. Los controles no son táctiles!! Eso es, se utiliza OnKeyUp, asi que uno se puede hartar de pulsar encima que nada…
  2. Una vez parada una canción… no hay forma de reanudarla!
  3. De la misma manera, tampoco hay forma de salir de la actividad de control y volver a la lista de canciones.
  4. Por último, falta una barra de indicación de por donde va la canción o un minutero u algo, ah, y leer el id3 tag.
Pues nada, creo que con eso ya tengo deberes para un rato bueno. Además hay otro tema que es hacer una actividad que me haga de explorador de archivos para seleccionar que carpeta (con sus subcarpetas) quiero leer.

 

 

6 abril, 2012
por Agustín Ventura
Sin comentarios

CrankPlay, Service para reproducir sonido.

Siguiendo la segunda parte de MusicDroid, voy a hacer un Service (servicio) para reproducir la música. Para lo cual necesito primero definir claramente que es un Service.

Según la documentación oficial de Android, un Service es… bueno, es una chapa, vaya que un Service es un proceso en segundo plano sin interfaz de usuario y con el cual puede comunicarse cualquier otro componente o artefacto que viva en el ecosistema. La parte interesante es que esta comunicación es asíncrona y se hace por paso de mensajes (como todo en Android), así que cuidado con esta parte.

Desarrollo del Service

Ahora, en el tutorial, viene una parte que tengo que terminar de entender más a fondo. El tema es que para comunicarse con un Service hay que definir una interfaz en un lenguaje llamado AIDL, de momento voy a seguir al pie de la letra el tutorial y me creo mi CPSInterface.aidl y copio y pego tal cual, ya más adelante veré de mejorar esta parte (o entender mejor).

Efectivamente, al salvarlo, en el directorio gen, me ha creado un paquete com.crankcode.crankplay.services en el que ha creado el correspondiente .java. Maravilloso.

Ahora toca crear el Service en sí, CPService en este caso concreto. Este servicio se une con la clase generada anteriormente (CPSInterface.java) proveyendo una implementación de una clase abstracta interna de esa interfaz, CPSInterface.Stub, y este Stub es el que ejecuta la lógica para utilizar el MediaPlayer. Para mi gusto hay un abuso de clases anónimas (como en casi todo lo de Android), pero de momento esta bien así, a seguir adelante.

El siguiente paso es obvio, el Stub hace uso de una serie de funciones, playSong, prevSong, etc… que hay que definir en el CPService, en el tutorial viene un ejemplo de una. En general la lógica era la que ya hacía la Activity, pero además metiéndole la gestión de mensajes necesaria para comunicarse con el invocador.

Esta parte es la siguiente en ser analizada. Lo primero de lo que se queja es de que no existe playbackstart.png, así que me cojo tanto ese icono como playbackpause.png y los copio al res/drawable… Joe, si que esta desfasao el tutorial, yo tengo cuatro subdirectorios: drawable-hdpi, etc… Bueno, de momento lo copio a todos y ya lo veré en más detalle.

Una vez añadidos, se queja de que no existe el constructor de Notification. Claro, de entrada lo primero que veo en el tutorial es que hay que crear un NotificationManager para gestionar las notificaciones, así que añado el onCreate y el onDestroy… pero esto no cambia nada, vaya plan. Claro, tiene toda la pinta de que el constructor que se utiliza de Notification esta absoluta y totalmente deprecated. Un vistazo rápido a la documentación  me confirma que ese constructor ya no existe, así que usaré el siguiente:

Notification notification = new Notification(
					R.drawable.playbackstart, file, System.currentTimeMillis());

Con esto se ha terminado el servicio, ahora toca usar el servicio desde la actividad.

Uso del Service

Lo primero es declarar el servicio en el applicationManifest.xml y aquí me vuelvo a encontrar que el atributo class que usa el tutorial ya no existe, pues nada, lo declaro así:

<service android:name=".services.CPService" android:process=":remote"/>

Y cruzo los dedos… Una vez en la actividad, la lógica es bastante sencilla, se crea una instancia de la interfaz del servicio (CPSInterface) y se hace un bind en el OnCreate a ese servicio. Este bind lo he tenido que cambiar también a lo siguiente:

this.bindService(new Intent(CrankPlay.this, CPService.class),
				mConnection, Context.BIND_AUTO_CREATE);

Para hacer el bind es necesario tener una instancia de un ServiceConnection que es una clase que se encarga de gestionar la conexión y desconexión del servicio.

Y por último, se cambia el updateSongs y el OnListItemClick para hacer llamadas al servicio en vez de ejecutar la lógica de reproducción.

Pues con esto listo… vamos a ver si funciona. Pues no, no funciona, cuando hago click en una canción termina saliéndome una excepcion que dice “contentView required”. Después de bichear un rato por Google, parece que el error esta relacionado con la gestión de notificaciones (que lógicamente ha evolucionado bastante en estos años). Para hacer una prueba rápida, voy a tratar de quitar las notificaciones, asi que comento todo el código relativo al NotificationManager y las notificaciones.

Y ahora sí que arranca y reproduce. Bueno, pues parte dos de la misión, arreglar el tema de las notificaciones.

Y efectivamente, el tema de las notificaciones estaba más que deprecated, quedaría así

//Create the notification
Notification notification = new Notification(
	R.drawable.playbackstart, file, System.currentTimeMillis());
Context context = getApplicationContext();
//Create the intent that will be fired when user clicks on the notification
Intent notificationIntent = new Intent(this, CrankPlay.class);
//The PendingIntent represents the firing of above intent
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
	notificationIntent, 0);
//Notification general information
notification.setLatestEventInfo(context, "Playing ", file,
	contentIntent);
//Display
nm.notify(NOTIFY_ID, notification);

Los pasos a dar son bastante sencillos:

  1. Crear la notificación, incluyendo el icono a presentar, el texto descriptivo (en este caso es el nombre de la canción) y el momento de la notificación.
  2. Crear el Intent que se lanzará cuando se haga click en la notificación
  3. Crear el PendingIntent, que no es más que el disparador del Intent anterior, los parámetros son la actividad que lo lanza, un request code que no se usa, el intent que se lanzará y una serie de flags.
  4. Se añaden unos datos adicionales a la notificación y el PendingIntent.
  5. Se lanza la notificación.

Con estos cambios pruebo a arrancar… et voilà, todo perfectamente funcional. Es más, gracias al servicio, puedo ejecutar otras aplicaciones, que sencillamente haciendo click en la notificación, vuelvo a CrankPlay. ¡Bien!

Notas

Quizás en esta parte es donde el tutorial ha envejecido de peor manera. Por ejemplo, la declaración del Service o el uso de notificaciones. No solo eso sino que si se consulta cualquier fuente más moderna (por ejemplo, esta de Lars Vogel ) veremos que hoy en día hay otras opciones de Service no descritas en el tutorial, como por ejemplo crear un Service que se ejecute dentro del mismo hilo de la aplicación, con lo cual nos ahorramos crear el AIDL. No obstante si se quiere que el Service se ejecute en su propio hilo, sigue siendo absolutamente necesario el AIDL.

Esto abre una serie de consideraciones a la hora de diseñar la aplicación que tendré que considerar (por ejemplo, si el servicio es local… ¿muere si el sistema acaba con la actividad principal? En ese caso quizás no sea lo que necesito…).

5 abril, 2012
por Agustín Ventura
Sin comentarios

WordPress SEO, aspectos avanzados y conclusiones.

Sigo con el artículo de Yoast sobre WordPress SEO.

Optimizar las Descripciones

El siguiente punto en el que incide el artículo es en el de optimización de las etiquetas “<meta name=”description”>”, estas etiquetas son utilizadas por el buscador para devolver la página cuando su contenido es coincidente con una búsqueda.

Por defecto, el buscador utiliza la primera frase de la página, pero con el plugin podemos establecer una a mano. Si tomo el post anterior como ejemplo, ahora mismo me sale lo siguiente: “Bueno, pues para darme un poco de visibilidad en Google, he instalado el plugin WordPress SEO y tiene un estupendo artículo de acciones a …” Esta descripción es obviamente mala y se puede mejorar bastante.

Para ello, debajo de cada post una sección propia del plugin y un apartado “Meta Description”, en él introduzco: “ Introducción a WordPress SEO y Google Web Master Tools.” Obviamente, podía haber utilizado la etiqueta para poner todo un chorro de palabras simplemente para optimizar a tope la visibilidad de la página, pero no me interesa. Creo que una cosa es mejorar la visibilidad de la página y otra tratar de manipular a los motores de búsqueda. Lo dicho, lo mismo es que soy tonto xD

En esta sección del plugin también puedo ver que el template que he puesto para los títulos hace que sea demasiado largo para los motores de búsqueda, así que en la sección “SEO Title” le quito la parte del dominio. Es decir, el SEO Title coincidirá con el título de la página.

Optimización de Imágenes

Para la optimización de imágenes, aparte de recomendar un plugin, la recomendación básica que se dá es que todas las imágenes tengan un “title” y un “alt”. Dá la casualidad de que eso en particular es una neura mía y todas las imágenes lo tienen, así que mission accomplished.

XML Sitemap

El último apartado en esta sección, es la generación de un XML Sitemap. La idea del XML Sitemap es indexar fácilmente las páginas para los buscadores, vaya, facilitarles la vida. Se puede activar a través del plugin en la sección “SEO > XML Sitemaps”, marco el “Check this box to enable XML sitemap functionality.” y lo dejo tal y como esta por defecto.

Sin embargo, cuando le doy “XML Sitemap” para ver lo que ha generado, me devuelve un 404, no encuentra nada en http://aguasnegras.es/sitemap_index.xml… vaya rollo.

Plantillas

A continuación, viene una sección con consejos para las plantillas.

El primero es utilizar Breadcrumbs o rastro de migas, para indicarle al usuario donde esta y patatín y patatán. Vale, ODIO, los rastros de migas, por lo cual, obviaré esta sección.

El siguiente, paradójicamente, más que sobre SEO es sobre buena escritura en general. Viene a hablar sobre los encabezados, se recomienda que si es la página principal  “<h1>” sea tan solo el nombre del sitio, que los “<h2>” sean los títulos de los posts, etc…

Esto es algo bastante relacionado con el tema que se esté usando, y he de decir que Yoko lo cumple muy bien, así que nada que tocar por aquí.

El siguiente apartado, también muy relacionado con el tema que se este usando, es no incrustar css ni javascript en las plantillas de las páginas. Así se favorece que sea cacheado en el usuario y que los motores de búsqueda no los accedan, así como la velocidad de ejecución de la página. De nuevo aqui Yoko cumple bastante bien, en la página principal hay una mínima cantidad de css introducido en un etiqueta “<style>”, pero es más que aceptable y todo lo demás en lo que toca a css y javascript se encuentra en archivos separados.

El siguiente apartado es sobre velocidad. Dejando de lado técnicas más complejas o intrusivas como puede ser minimizar el número de llamadas a la base de datos o darse de alta en un CDN, la opción recomendada es instalar algún plugin de caché, en concreto recomienda “W3 Total Cache”. Hace tiempo que quiero probar uno, así que más adelante lo instalaré y lo probaré.

A continuación se habla sobre la barra lateral. Es cierto que el menú lateral es demasiado genérico. Por ejemplo, en AguasNegras aparecen mis enlaces a redes sociales en cualquier página que visites, cuando realmente solo tendría sentido en la página principal y en “Acerca De”. El problema es que hoy por hoy, según dice el artículo no hay forma sencilla de corregir ésto, más que programación. Así que se queda tal y como esta.

También se recomienda hacer un Sitemap en HTML, pero claro, siendo un blog esto no tiene mucho sentido.

Finalmente se dan unos consejos sobre “Author Highlighting”, pero me parece ya demasiado complicado, de nuevo hay que tocar código, etc… Así que  nada por aquí.

 Contenido Duplicado

A la hora de navegar por el blog existen distintas maneras, quizás demasiadas. Por ejemplo, si se tienen varios autores, se puede acceder a cada autor mediante /author/nombredelautor. En mi caso esta desactivado, pero es cierto que para un blog unipersonal, este tipo de taxonomía no tiene sentido. En la sección “SEO > Indexation” se pueden controlar todo este tipo de aspectos, por ejemplo, no indexar los archivos por fecha, o por categorías o tags.

A continuación viene una sección, que la verdad, parece un poco publicitaria, ya que muchas cosas te las explica por encima y te dice que WordPress SEO las gestiona automáticamente. También recomienda un plugin para la paginación, ya que si tienes un post muy popular en una categoría con muchos posts… digamos que se pierde un poco el grano entre la paja.

Estructura del Sitio

La primera recomendación que encuentro interesante en esta sección es que si tienes un post particularmente popular o interesante… lo conviertas en una página para maximizar su exposición. Realmente tiene sentido.

Además, se recomienda utilizar un plugin de posts relacionados, para maximizar las sinergias entre los propios posts, en concreto se recomienda Yet Another Related Posts plugin, así que nada, otro más a la lista. Creo que puede ser bastante interesante ya que en realidad yo trato de hacer esta funcionalidad a mano mediante las categorías y las etiquetas, pero claro, no es lo mismo.

Otros Aspectos

Finalizando este artículo, Yoast comenta otra serie de aspectos adicionales, por ejemplo, la importancia de fidelizar a los lectores, mediante RSS y suscripción por email, el motivar la discusión y la participación de los comentaristas, así como el hacerles un “follow back”, comentar en sus blogs, seguirles en Twitter, etc… En general en este sentido creo que se habla más de la construcción de una comunidad, cosa que por supuesto, influye en el SEO.

Por último se recomienda el uso de distintas herramientas para medir el impacto de las optimizaciones, como pueden ser el mismo Google Webmaster Tools y Google Analytics.

Conclusiones

El plugin WordPress SEO tiene muchísimas funcionalidades sin lugar a dudas. Es más, creo que tiene más de las que yo voy a poder utilizar eficientemente.

Sin embargo a lo largo de todo el artículo hay varios aspectos que me han ido “sorprendiendo”. En general la base del SEO esta en ofrecer buenos contenidos y que sean relevantes. Es decir, el SEO es GENERAR CONTENIDO.

Después esta generación tiene una gran parte que podríamos decir que es “maquetación”. De la misma forma que para un libro se escoge una encuadernación, una portada, una tipografía, etc… En la generación de contenidos hay que tener en cuenta detalles como los títulos, las etiquetas header, las keywords, etc…

Es decir, a la hora de redactar un post, o una página, una vez terminado, hay que cuidar los aspectos SEO.

Una vez hecho esto, hay disponibles una serie de herramientas para el análisis del rendimiento de la página, como pueden ser las citadas arriba.

Y como conclusión final, hay que tener en cuenta que si escribes es para ser leido, es decir, escribes para la comunidad, y es la comunidad la que te posiciona mejor o peor, en función del valor de tus contenidos.


4 abril, 2012
por Agustín Ventura
2 Comentarios

WordPress SEO, Google Webmaster Tools y optimización de títulos.

Bueno, pues para darme un poco de visibilidad en Google, he instalado el plugin WordPress SEO y tiene un estupendo artículo de acciones a realizar para utilizarlo, así que lo voy a seguir y voy a ir comentando.

Entorno

A día de hoy lo que tengo es un WordPress 3.3.1 con WordPress SEO 1.1.5

SEO

A continuación voy a ir siguiendo lo más fielmente posible las instrucciones que se dan en el artículo punto por punto.

Permalinks

Dice con toda razón que los permalinks han de ser legibles. Por ejemplo, ahora mismo los permalinks de aguasnegras.es son del tipo http://www.aguasnegras.es/blog/?p=410. Claramente es un error, debería incluir al menos el título del post, así que me voy a “Ajustes > Enlaces Permanentes” y lo cambio, por probar solo a título del post.

Vaya, me dice que no puede modificar el .htaccess y que tengo que añadir un código a mano… Bueno, pues nada, lo añado. Pero sin embargo ahora al mostrar la página principal me dice que no encuentra nada… ufff… Esto tiene más bien pinta de estar relacionado con algo más, así que mejor dejo de tocar. Siguiente apartado.

WWW o no WWW

Este punto es interesante, ya me obliga a pensar en el branding del sitio. Personalmente siempre que pienso en el sitio, pienso en “aguasnegras”, así que creo que el branding sería más adecuado como “aguasnegras.es” a secas. Esto implica dos cambios, ambos en “Ajustes > Generales”. En “Dirección de WordPress (URL)” y “Dirección del Sitio (URL)”, elimino las www y acepto.

El siguiente paso que comenta Yoast es hacer esto también en Google Webmaster Tools… lo cual estaría muy bien si supiera lo que es :D Según la página principal, sirve para mejorar la visibilidad de mi sitio en Google, pues nada, inicio sesión y le doy a añadir un sitio. En la URL introduzco “aguasnegras.es”. A continuación me dice que descargue un archivo html de verificación y lo suba a mi sitio, nada más fácil. Subido, verifico y le doy a continuar.

Y ya estoy en el panel de control de Google Webmaster Tools para aguasnegras.es. Hago click en “Información del Sitio > Configuración” y como URL establezco “aguasnegras.es”. Sin embargo al darle a “Guardar” me informa que tengo que confirmar que soy el propietario del dominio y patatín y patatán… después de cacharrear un rato, decido añadir a mis sitios “www.aguasnegras.es” y de repente empiezan a funcionar todas las analíticas y todo va bien… pues vaya, nada, ya hago el cambio y listo.

La verdad que el Google Webmaster Tools promete… habrá que echarle un vistazo más detenidamente otro día.

Títulos de los Posts

El tutorial sigue hablando del título de los posts, el título no solo es el texto que se muestra en la pantalla, si no que será lo primero que se muestre en los resultados de búsqueda.

Se recomienda que las primeras palabras del título sean las más significativas, ya que los motores de búsqueda hacen más hincapié sobre ellas… así como la gente cuando lee. Vaya, es de cajón, pero nunca me había dado por pensarlo, a partir de ahora tendré más cuidado.

Con la opción “SEO > Titles” puedo controlar como se muestran los títulos de las páginas de mi sitio, no solo de los posts. Como no estoy seguro de como esta el header.php de mi tema, marco la casilla de “Force rewrite titles”. Para probar, en el “Title Template” de “Homepage” pongo el ejemplo que viene en el artículo en sí: “%%sitename%% &#8226; %%sitedesc%%”, pulso “Save Settings” y recargo… Perfecto, me muestra “AguasNegras &#8226; This was not supposed to be like this”, para darle un toque personal, dejo el título del sitio como “AguasNegras || This was not supposed to be like this”, jeje.

Así en plan rápido, establezco las siguientes plantillas:

  1. Posts y Pages: %%title%% @ %%sitename%%
  2. Category: %%category%% @ %%sitename%%
  3. Post_Tag: %%tag%% @ %%sitename%%
  4. Author Archive: %%name%% @ %%sitename%%
  5. Date Archives: %%date%% @ %%sitename%%
  6. Search Pages: %%searchphrase%% @ %%sitename%%
  7. 404 Pages: Uops! @ %%sitename%%

Lo pruebo todo y perfecto, funcionan todos los títulos

1 abril, 2012
por Agustín Ventura
Sin comentarios

CrankPlay, mi primer proyecto Android. Parte 1

Bueno, pues después de un tiempo dedicado a un proyecto que tengo por ahí y que anda un poco empantanado, voy a empezar a hacer algo que llevo tiempo queriendo hacer. Un reproductor de mp3 para mi móvil.

Voy a empezar haciendo una pequeña elicitación de requisitos “grosso modo” para tenerlo como guía de aquí en adelante.

Requisitos

El reproductor tendrá las siguientes funcionalidades:

  1. Contará con una lista de reproducción en la que se almacenarán las canciones a reproducir en orden.
  2. Leerá las canciones de directorios en el almacenamiento del móvil, pudiendo en todo caso seleccionar una carpeta con lo cual se incluirían todos los mp3 en ella y sus subcarpetas en la lista de reproducción.
  3. Contará con los controles básicos en un reproductor musical: Play, Stop, Pause, Next, Back, Volumen.

Como requisitos no funcionales:

  1. Deberá consumir los recursos mínimos posibles.

Enfoque del Desarrollo

Pues para desarrollar, voy a empezar basándome en el tutorial MusicDroid de HelloAndroid. Este tutorial tiene ya bastantes años, así que iré haciendo un repaso de las APIs involucradas, actualizándolas según sea necesario.

Paso 1 – Crear el Proyecto

El primer paso es sencillo, creo el proyecto, inicializo git, creo repositorio en GitHub y lo subo, para más detalles, Tutorial de Git y GitHub.

Resultado, ya esta el proyecto en GitHub:

CrankPlay en GitHub

Repositorio de CrankPlay en GitHub

Ahora toca empezar con el tutorial de MusicDroid.

Paso 2 – Lista de canciones

El primer paso del tutorial es simplemente mostrar una lista de todos los archivos .mp3 que hay en la tarjeta sd.

Lo primero es el layout, el songlist.xml. Lo he modificado un poco de la siguiente manera:

  1. He sacado la cadena de texto “No songs found” al strings.xml.
  2. He cambiado el layout_weight, lo he quitado porque daba un warning en combinación con el layout_width.

Ahora, se hace el layout correspondiente a cada canción, song_item.xml. Es copiar y pegar y solo modifico el id, lo cambio por @+id/song_title.

Paso 3 – Actividad CrankPlay

Lo primero que hago en la Activity es hacer que extienda de ListActivity para poder gestionar nativamente el layout ListView. A continuación copio y pego las variables locales:

  1. MEDIA_PATH: directorio en el que estan los mp3 (la tarjeta SD por defecto).
  2. songs: lista de canciones en MEDIA_PATH.
  3. mp: instancia de MediaPlayer que reproducirá las canciones.
  4. currentPosition: posición de la canción que se esta reproduciendo.

En cuanto al OnCreate, sin misterios, se encarga de arrancar la aplicación y actualizar la lista de canciones.

En cuanto el updateSongList se encarga de cargar el directorio especificado en MEDIA_PATH y buscar en él todos los archivos terminados en .mp3 con la ayuda de la clase Mp3Filter.

Siguiente paso del tutorial, reproducir una canción cuando se hace click en ella. Para ello se usa un OnListItemClick que únicamente se encarga de llamar al método playSong, pasándole el nombre del archivo de la canción.

PlaySong se limita a usar el objeto mp para reproducir la canción. Primero se hace un reset con lo cual se pausa cualquier canción que se este reproduciendo y se carga la canción nueva. Se crea una clase interna anónima para implementar OnCompletionListener, que se limita a invocar al método nextSong para reproducir la siguiente canción (de haberla).

Y por último nextSong, sencillamente comprueba que no sea la última canción y si no lo es, incrementa currentPosition y llama a playSong.

Listo.

Paso 4 – Probar la Aplicación

Si ejecuto la aplicación directamente (Click con el botón derecho en el proyecto Run as > Android Application), se muestra el mensaje definido para el caso de que no haya canciones “No songs found”, y es correcto, hay que subir mp3s a la tarjeta SD.

Este apartado del tutorial ya esta un poco desfasado, los emuladores ya se pueden crear con el AVD Manager de Eclipse con la tarjeta SD ya creada, con lo cual basta con enviar el mp3 al dispositivo usando el adb en la línea de comandos. El primer paso es tener arrancado el emulador.

El segundo enviar el mp3 de prueba:

~/Android/android-sdks/platform-tools$ ./adb push ~/Música/Noel\ Gallagher\'s\ High\ Flying\ Birds\ -\ Noel\ Gallagher\'s\ High\ Flying\ Birds\ \(Deluxe\ Version\)\ \[theLEAK\]/01.\ Everybody\'s\ On\ The\ Run.mp3 /sdcard/Prueba.mp3

Y listo, ya sale andando y tengo Prueba.mp3 en la lista de canciones.

Ahora bien, si le doy a reproducir no se escucha nada, y en el LogCat se puede ver un Warning: “obtainBuffer timed out (is the CPU pegged?)”. Por lo visto este fallo lo produce el tener habilitados los snapshots en el emulador, así que nada, lo deshabilito y reinicio.

Listo, ya reproduce el mp3, así que ahí lo dejo, mañana más y mejor.

23 febrero, 2012
por Agustín Ventura
1 Comentario

Para cambiar la orientación del emulador de android de horizontal a vertical y viceversa, basta con pulsar ctrl+F11.

19 enero, 2012
por Agustín Ventura
Sin comentarios

Amazon Elastic Beanstalk

Visto que con Heroku se me esta atragantando el tema del despliegue de JSF 2, me he decidido a seguir los cantos de sirena y probar Amazon Elastic Beanstalk. La publicidad dice que Amazon me dá gratuitamente un Tomcat 6 o Tomcat 7 en la nube, así que merecía darle una ojeada. Lo primero de lo que me doy cuenta es que Amazon Elastic Beanstalk en realidad no es solo un Tomcat, sino que mas bien podríamos definirlo como un agrupamiento de tecnologías que Amazon ya tenía que se ofrecen simplificadamente y bajo un mismo paragüas, como EC2, S3, EBS, CloudWatch, etc… La página de Beanstalk promete que se despliegan WARs normales y corrientes y que se puede usar cualquier librería Java con normalidad, a esto le sumamos Amazon SimpleDB como base de datos relacional y Amazon DynamoDB como NoSQL y tenemos un stack potentísimo a nuestra disposición. Esta la parte buena, ahora la mala.

La mala para empezar es que solo dan un año de uso gratuito. Pero no un año de tiempo de computación ni nada así, no. Un año de uso desde el momento del registro. Punto. Un poco rácano a mi parecer, preferiría que me limitasen más en recursos disponibles y no tener límite de tiempo, pero bueno.

Para continuar la muy mala. La muy mala es que, por lo que te piden en el registro, parece que vivo en Libia o en Cuba o qué sé yo. Durante el proceso de registro hay que crear una cuenta, proporcionar una tarjeta de crédito (¿no es gratuito? Entiendo que la pidan por si me paso de recursos, pero quizás en ese caso sería preferible echar abajo el servicio oportuno hasta el mes siguiente) y te hacen una llamada telefónica para que confirme con un pin que aparece en la pantalla tu identidad. Lo dicho, un poco paranoide.

Una vez habiendo pasado por todo este calvario, me decido a seguir el primer tutorial de ejemplo en Amazon.

Resumen

  1. Instalar Eclipse 3.7 JEE y el plugin AWS Toolkit para Eclipse.
  2. Crear un proyecto y configurar la cuenta de AWS.
  3. Análisis del proyecto.
  4. Crear un servidor en Elastic Beanstalk y desplegar el proyecto.
  5. Modificar el proyecto y redesplegar.
  6. Parar el servidor.

Paso 1

Descargar el Eclipse for Java EE Developers en la versión oportuna (en mi caso, Linux 64 bits) de aquí. Cuando haya bajado basta con descomprimirlo y ejecutarlo.

Para instalar el AWS Toolkit, una vez abierto el Eclipse, pulso Help > Install New Software … > Add y en el diálogo Add Repository, en Name pongo “AWS” y en Location “http://aws.amazon.com/eclipse”, Ok. Selecciono el nuevo repositorio en el desplegable y ya abajo sale “AWS Toolkit for Eclipse”. Lo selecciono y Next, Next, acepto las licencias, acepto que se instale software sin firmar (sigh) y reinicio Eclipse una vez instalado.

Entorno instalado, guay, sin mayor problema.

Paso 2

Para crear un proyecto para desplegar en AWS, hago click en File > New > Other > AWS > AWS Java Web Project.

Nuevo Proyecto AWS

Nuevo Proyecto AWS

Pulso Next. Para que un proyecto AWS sea desplegable necesita una información acerca de la cuenta del desarrollador, eso, junto con el nombre del proyecto es lo que tengo que configurar en esta pantalla. Como es la primera vez que entro, tengo que crear la cuenta, así que hago click en “Configure AWS Accounts” y veo la siguiente pantalla.

Configurar cuenta de AWS

Configurar cuenta de AWS

Y ahí estan los datos de la cuenta, hay que ponerle un nombre de cuenta (meramente identificativo para el Eclipse), una clave de acceso y una clave secreta. Como no tengo ni idea de que es eso, hago click encima de “find your existing AWS security credentials” y una vez logado en la página de AWS veo una pantalla que haciendo scroll tiene esta pinta:

Credenciales de Seguridad

Credenciales de Seguridad

Ahí (en el borrón) esta la clave de acceso y si hago click en “mostrar” veo la clave secreta. Pues nada, copiar y pegar a la ventana del Eclipse. Hago click en Ok y en la pantalla de configuración del proyecto dejo seleccionado “Basic Java Web Application”. Finish.

Paso 3

El proyecto recién creado es un proyecto web dinámico normal de Eclipse.

Proyecto Java Web AWS

Proyecto Java Web AWS

Tiene una carpeta src/ en la que se encuentran los fuentes de Java, de momento vacía, una carpeta webcontent en la que va el contenido web de la aplicación (jsp, html, css, js, png, gif, etc…) y dentro de ella, como es habitual, una carpeta WEB-INF con el web.xml dentro y un directorio lib (también vacío).

El web.xml es absolutamente normal, y lo único destacable es que dentro de src/ se encuentra un archivo llamado “AWSCredentials.properties” que contiene… las credenciales en texto plano. Bueno, yo no es por ser destroyer, al fín y al cabo esa información solo es accesible para el desarrollador (es decir, yo mismo), pero tampoco cuesta trabajo cifrarlo con algún hash, por aquello de mejorar algo la seguridad. Sus motivos tendrán.

De momento, el proyecto cumple lo prometido, Java Web normal y corriente. Ahora toca ver qué tal la ejecución.

Paso 4

Para ejecutar el proyecto, hago click encima de él con el botón derecho y selecciono Run As > Run on Server… En esta pantalla dejo seleccionado “Manually define a new server” y selecciono un AWS Elastic Beanstalk for Tomcat 6. En “Server host name” escribo Tomcat6AWS y pulso Next.

Nuevo Tomcat 6 AWS

Nuevo Tomcat 6 AWS

En la siguiente pantalla tengo que seleccionar para empezar una región en la que desplegar el proyecto. No sé si será un bug del plugin o que solo esta permitido ahí, pero solo me deja seleccionar US-East(Northern Virginia). Me hubiera gustado más seleccionar Europe(Ireland) por aquello del tiempo de latencia, pero bueno.

Lo siguiente son conceptos ya propios de Amazon Elastic Beanstalk. Una aplicación (application) es un producto software (un WAR, vaya) con una configuración y una versión determinada, mientras que un entorno (environment) es una instancia determinada de esa aplicación.

Pues vale, dejo marcado “Create a new application” y en Name pongo AWSJavaWeb. Para el Environment uso de nombre AWSJavaWeb igualmente.

Application y Environment

Application y Environment

En la siguiente pantalla, Advanced Configuration, la verdad que no entiendo nada, parece que es algún sistema de autenticación (¿otro?), pero sigo el tutorial y selecciono “Deploy with a key pair” y le doy a Add (la cruz verde). Me sale un diálogo para introducir un nombre y un directorio, de nombre uso AWSJavaWeb y el directorio lo dejo tal y como esta. Pulso Ok y pulso Finish.

Advanced Configuration

Advanced Configuration

Pero todavía sale un cuadro de diálogo más… pidiéndome la versión de la aplicación, claro. Escribo v20120118.01 (primera versión, 18 de Enero de 2012).

Versión de la Aplicación

Versión de la Aplicación

Pulso OK y espero mientras el cuadro de diálogo me va informando. Entiendo que el proceso es generar un WAR, subirlo a Amazon S3, crear una instancia de Amazon EC2 con el Tomcat 6 y desplegarlo… nada más… Cuando acaba:

Aplicación desplegada en Elastic Beanstalk

Aplicación desplegada en Elastic Beanstalk

Paso 5

Para ver como lleva esto los cambios, abro index.jsp y cambio el contenido de la etiqueta “title” a lo siguiente: Hello agustinventura AWS Java Web Application.

Guardo los cambios, click con el botón derecho en el proyecto, Run As > Run on Server… Selecciono el servidor que ya he creado y hago click en Finish. Me vuelve a salir el cuadro de dialogo para la versión, esta vez escribo v20120118.02 y OK.

Vuelvo a esperar (aunque menos) y…

Cambios Desplegados

Cambios Desplegados

Ahí esta, cambios desplegados en producción.

Paso 6

Para evitar que el servidor siga funcionando (y por tanto, me facturen), lo tengo qué parar. ¿Cómo? Pues en la pestaña Servers, lo selecciono y hago click en Stop (el botón rojo, o bien click con el botón derecho encima del servidor y Stop).

Y aquí me llevo la primera decepción, aunque tampoco es muy importante, tras 10 minutos esperando, decido entrar en la consola de aws ya que me parece extraño. Entro, selecciono AWS Elastic Bean Stalk, la aplicación y al hacer click en Events, veo que ya se ha parado… hace 10 minutos. Vaya, que el plugin se ha quedado colgado, habrá que reportarlo.

Consola AWS

Consola AWS

Conclusiones

Básicamente, y a falta de hacer algo más complejo, puedo dividir las conclusiones en pros y contras:

Pros:

  1. Parece que el desarrollo es Java Web “estándar”.
  2. El servidor de despliegue es un Tomcat (con todas sus cosas buenas y malas).
  3. El plugin para Eclipse, todo un detalle.

Contras:

  1. El proceso de alta… creo que fue más fácil darme de alta en Paypal…
  2. ¿Solo un año de prueba?¿De verdad?
  3. Los dos puntos anteriores me hacen concluir que el entorno es digamos… poco amistoso para desarrolladores aficionados, o porqué no, startups. En este sentido lleva ventaja Heroku.

El próximo paso que me gustaría dar es la prueba de fuego, a ver como se comporta para desplegar JSF 2 (que tengo atragantado en Heroku).