AguasNegras

This was not supposed to be like this

2 septiembre, 2013
por Agustín Ventura
0 Comentarios

Logging en Java con SLF4J y Log4j2

En este artículo se hace una breve introducción a la generación de logs en Java usando SLF4J y Log4j2, así como un breve repaso de las mejores prácticas relativas.

Introducción

En Java se da una circunstancia muy extraña, siendo el logging tan importante como es, no hay una buena solución integrada en el framework como tal. Es cierto que existe la Java Logging API o Java Logging Framework, pero fue una adición bastante a posteriori (en concreto, se añadió en el 2002, en la versión 1.4 del JDK). Para cuando esta API salió como parte del JDK ya teníamos un “estándar” de facto, Log4j, que fue creado en el 1999. Mientras tanto, y haciendo honor a aquella vieja tira de XKCD, seguían saliendo frameworks de loggings: logback, commons-logging, slf4j y otros tipos de soluciones a cada cual más exótica. En este artículo se hace un repaso bastante completo de la historia del desaguisado.
A verano de 2013, la  situación no ha mejorado, tal y como recoge una reciente encuesta de ZeroTurnAround. Si acaso se clarifican dos tendencias:

  1. En general, se suele usar una fachada de abstracción sobre el sistema de logs como puede ser SLF4J o commons-logging. Entiendo que ésto es debido a que muchas organizaciones imponen el uso de un framework de logging en concreto y mediante esta indirección ganas flexibilidad.
  2. Log4J sigue siendo 14 años después el framework de logging más usado. Si eso no es un estándar de facto…

Por tanto, parece razonable aplicar la filosofía de fachada de logging + framework de logging para el desarrollo de un nuevo producto. En cuanto a fachada de logging se usará SLF4J y como framework de logging se explicará el uso de Log4j2. ¿Por qué el 2 y no el 1? Pues básicamente por velocidad. El logging es una actividad que realmente es accesoria, por tanto no debería consumir recursos del sistema apenas, la página de Log4j2 explica que esta es una de las motivaciones tras la versión 2 del framework, y este artículo confirma su velocidad. Sin embargo hay que tener en cuenta que Log4j2 esta actualmente en beta 8, pero dado que vamos a usar SLF4J se puede sustituir por su versión 1 o por logback o como se desee.
Otro aspecto a considerar a la hora de usar la versión 2 de log4j es que cuando se usa Maven, el log4j 1.2.x incluye varias dependencias que lo más normal es que no se usen, como javax.mail, geronimo-jms, etc…

Configuración

La configuración en un proyecto con Maven es tan sencilla como añadir las siguientes dependencias al pom.xml:

<!-- slf4j -->
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.5</version>
</dependency>
<!-- log4j2 -->
<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-api</artifactId>
	<version>2.0-beta8</version>
</dependency>
<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-core</artifactId>
	<version>2.0-beta8</version>
</dependency>
<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-slf4j-impl</artifactId>
	<version>2.0-beta8</version>
</dependency>

La primera dependencia es la api de SLF4J, que realmente es la que se utilizará en la aplicación para escribir los mensajes de log. A continuación esta la API y el Core de Log4j2 y por último el puente entre SLF4J y Log4j2. Ya que SLF4J es una fachada de logging, el proyecto incluye varios puentes para trabajar con los frameworks más comunes, sin embargo, como Log4j2 es un proyecto nuevo, es éste el que incluye el puente para SLF4J.

Uso

El uso de SLF4J es bastante sencillo, basta con instanciar el objeto de logging, el logger:

private static Logger logger = LoggerFactory.getLogger(Logging.class);

Esta variable es privada para evitar que otras clases puedan usarla, porque en ese caso parecería que el error se ha producido en nuestra clase Logging, además es static para que tan solo haya una instancia del logger sin importar las instancias que haya de la clase (es decir, es singleton). Se puede usar también una instancia normal, cada aproximación tiene sus pros y sus contras como se discute aquí.
En cuanto al uso del logger es muy sencillo:

public static void main (String... args) {
	logger.info("Starting application");
	logger.debug("Loading Lannister house");
	House lannister = new House("Lannister");
	logger.debug("Invoking Clegane bannerman");
	lannister.invokeBannerMan("Clegane");
	logger.debug("Invoking erroneous bannerman");
	lannister.invokeBannerMan(null);
	logger.info("Ended application");
}

En este ejemplo, se crea una Casa llamada Lannister y se invoca a dos banderizos, uno llamado Clegane y otroal que por error se pasa null como nombre.
El resultado de ejecutar este programa es el siguiente:

14:21:23.629 [main] ERROR es.aguasnegras.logging.model.House - Error loading house Lannister bannerman: bannerman name can't be empty
Exception in thread "main" java.lang.IllegalStateException: Cant invoke bannerman without name
	at es.aguasnegras.logging.model.House.invokeBannerMan(House.java:44)
	at es.aguasnegras.logging.Logging.main(Logging.java:20)

La ejecución de la aplicación falla al invocar el banderizo con null (normal)
Como se ve la invocación al logger es en todo caso la misma, variando solo según el nivel de importancia del mensaje de error. En total en SLF4J vienen definidos los siguientes niveles de log:

  1.  Error: Ocurrió un error en la aplicación.
  2. Warn: Se ha dado una circunstancia de posible error.
  3. Info: Información sobre la ejecución de la aplicación.
  4. Debug: Información importante para debuggear la aplicación.
  5. Trace: Información de traza sobre la ejecución de la aplicación.

En otros frameworks existe un nivel adicional de log: Fatal, pero SLF4J no lo recoge, he aquí la explicación. Yo personalmente creo que puedo vivir sin ello.
El objeto de cualquier framework de logging es que podamos configurar fácilmente cuales de estos mensajes se mostrarán según el entorno. Lo más normal es que en desarrollo deseemos mostrar los mensajes de debug y superiores, mientras que en producción se establezca el nivel a info (o incluso a error). Ahora bien, si hay un error en producción, lo más interesante es ajustar directamente el nivel de log a trace y así dispondríamos de toda la información relevante.
Sin embargo, en el ejemplo pese a tener invocaciones a debug, a info y a trace, tan solo sale el mensaje relativo al error (que además se registra en la clase House). Esto es porque aún no hemos definido la configuración de los mensajes de log y por defecto log4j2 tan solo recoge los mensajes con nivel Error.

Configuración de Log4j2

En primer lugar, hay que decir que la configuración de log4j2 se realiza bien mediante un archivo xml, bien mediante un archivo json. En log4j 1.2 se podía configurar también mediante un archivo .properties, como a mí nunca me gustó esa opción (la veía confusa), agradezco que la hayan quitado.
Lo primero es crear en main/resources un fichero log4j2.xml (tal y como se explica aquí), una vez creado, se completa tal que así:

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="WARN">
  <appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    </Console>
  </appenders>
  <loggers>
    <root level="trace">
      <appender-ref ref="Console"/>
    </root>
  </loggers>
</configuration>

El archivo empieza con el elemento configuration que tiene un atributo status, ese atributo significa el nivel de log de error que quiero aplicar al mismo log4j, a warn esta bien.
A continuación defino un appender. Se puede pensar un appender es un destino de los mensajes de log, se puede tener un appender para presentar por consola, otro para guardar en archivo, otro para enviar por email o combinación de todos los anteriores. Aquí se pueden consultar todos los appenders que hay.
A su vez un appender tiene un layout que no es más que la forma de darle formato al mensaje de log: bien siguiendo un patrón, bien en html, etc… de nuevo hay una extensa lista.
Y por último estan los logger como tal. Los loggers tienen una particularidad, y es que existe un logger “padre” del que heredan todos los existentes: root, después de él se podrán crear los que sean oportunos, pero al menos siempre existirá root. Un logger tiene un nivel (que es el nivel mínimo de log cuyos mensajes se mostrarán) y una lista de appenders que se utilizarán para mostrar los mensajes.
De momento solo tengo configurado root y con nivel trace con lo que se mostrarán todos los mensajes que se generen. Tal que así:

17:02:03.721 [main] INFO  es.aguasnegras.logging.Logging - Starting application
17:02:03.723 [main] DEBUG es.aguasnegras.logging.Logging - Loading Lannister house
17:02:03.742 [main] TRACE es.aguasnegras.logging.model.House - Loaded house Lannister without bannermen
17:02:03.742 [main] DEBUG es.aguasnegras.logging.Logging - Invoking Clegane bannerman
17:02:03.763 [main] TRACE es.aguasnegras.logging.model.BannerMan - Loaded bannerman Clegane with house Lannister
17:02:03.763 [main] TRACE es.aguasnegras.logging.model.House - Adding bannerman Clegane to house Lannister
17:02:03.763 [main] DEBUG es.aguasnegras.logging.Logging - Invoking erroneous bannerman
17:02:03.763 [main] ERROR es.aguasnegras.logging.model.House - Error loading house Lannister bannerman: bannerman name can't be empty
Exception in thread "main" java.lang.IllegalStateException: Cant invoke bannerman without name
	at es.aguasnegras.logging.model.House.invokeBannerMan(House.java:44)
	at es.aguasnegras.logging.Logging.main(Logging.java:20)

Y ahora se puede empezar a modificar la configuración. Por ejemplo, si quiero que aparezcan todos los mensajes de error para Logging pero para las demás clases que solo aparezcan de info para arriba, puedo añadir este logger a la configuración:

<loggers>
	<root level="trace">
		<appender-ref ref="Console" />
	</root>
	<logger name="es.aguasnegras.logging.model" level="info" additivity="false">
		<appender-ref ref="Console" />
	</logger>
</loggers>

Estoy creando un logger nuevo para el paquete es.aguasnegras.logging.model que recogerá todos los mensajes de información y los mostrará por el appender Console. El atributo additivity a false indica que los mensajes que se muestren por este logger no se deberán mostrar por root (si no, saldrían duplicados).
Es importante tener en cuenta que el nivel de log de root es el mínimo para todo el sistema. Es decir, si yo arriba cambio el nivel de root por error y el de model por trace tan solo se mostrarán los mensajes de error, ya que el resto no se evaluarán.
Si por ejemplo ahora quisiera que si se mostraran todos los mensajes de log de la clase BannerMan, podría hacer así:

<root level="trace">
	<appender-ref ref="Console" />
</root>
<logger name="es.aguasnegras.logging.model" level="info" additivity="false">
	<appender-ref ref="Console" />
</logger>
<logger name="es.aguasnegras.logging.model.BannerMan" level="trace" additivity="false">
	<appender-ref ref="Console" />
</logger>

Y así, sucesivamente. Aquí, por mantener el ejemplo sencillo, solo he utilizado un appender, pero cada logger podría usar un appender distinto, por ejemplo, root podría utilizar la consola, pero model podría usar un archivo.

Uso Eficiente de la API

Arriba comentaba que un framework de logging, sobre todo ha de ser rápido y no consumir ciclos de CPU, ni memoria, etc… En general, para evitar el consumo “tonto” de recursos en muchos sitios recomiendan hacer lo siguiente:

if (logger.isTraceEnabled()) {
	logger.trace("Adding bannerman " + bannerManName + " to house " + name);
}

Este código, desde mi punto de vista tiene varios inconvenientes:

  1. En el caso mejor (trace no esta habilitado), se ejecuta una instrucción
  2. En el caso peor (trace esta habilitado), se ejecutan dos instrucciones y además se construye la cadena con el mensaje
  3. Además, para cumplir con DRY, nos veremos tentados de crear una fachada de logging sobre la fachada de logging (WTF!)

Para evitar todo esto, en SLF4J recomiendan esta forma de logar:

logger.trace("Adding bannerman {} to house {}", bannerManName, name);

Así de sencillo y de fácil. Por supuesto el método acepta múltiples parámetros y si se pasa un objeto se invoca el toString.

Código

Pues con esto se acaba este pequeño repaso de lo fundamental sobre SLF4J y Log4j2, el código, en github (para variar).

JustPlay en GitHub

Repositorio de JustPlay en GitHub

9 marzo, 2013
por Agustín Ventura
2 Comentarios

Emulador de Android en Ubuntu (Initializing hardware OpenGLES emulation support)

A veces (normalmente en una instalación nueva), los emuladores de Android no arrancan en Ubuntu de 64 bits debido a un error con OpenGLES.

Para comprobar si la solución es aplicable, hay que ejecutar este comando desde la consola:

$ ./emulator -avd GalaxyS -verbose

Donde GalaxyS será el nombre del avd que se desee arrancar, al final del log deberemos ver algo así:

emulator: Initializing hardware OpenGLES emulation support
Violación de segmento (`core' generado)

El problema y su solución se encuentra reportado aquí.

Básicamente la solución consiste en lo siguiente:

$ cd $ANDROID_SDK/tools/lib
$ mv libOpenglRender.so libOpenglRender.so.bak

Y listo.

14 agosto, 2012
por Agustín Ventura
0 Comentarios

JustPlay, lógica propia

Vale, una vez terminado el guarreo con el ciclo de vida, toca implementar la lógica de negocio propia de la aplicación.
Al ser JustPlay un reproductor de mp3 y ogg, vamos a ver que lógica tenemos que implementar:

Funcionalidad Detallada

  1. Gestión de la lista de reproducción, añadir directorios y canciones sueltas, borrar la lista de reproducción, eliminar canciones en particular y reordenarla mediante drag&drop.
  2. Reproducir: Reproducir toda la playlist desde el principio hasta el final dándole al botón de play.
  3. Reproducir una canción: Empezar a reproducir toda la playlist desde la canción seleccionada.
  4. Parar: Parar la reproducción, al darle a play se volverá a reproducir la lista desde el principio.
  5. Pausar: Parar la reproducción, al darle a play se continuará con la reproducción donde estaba.
  6. Canción anterior: Reproducir la canción anterior, en caso de ser la primera se reproducirá desde el principio.
  7. Canción siguiente: Reproducir la canción siguiente, en caso de ser la última no se hará nada.
  8. Avance rápido: Avanzar 15 sgs en la canción. Si la posición actual de la canción + 15 sgs > longitud de la canción, se pasa a la siguiente canción.
  9. Retroceso: Retroceder 15 sgs en la canción. Si la posición actual de la canción – 15 sgs < 0, se comienza desde el principio.

Definición de Responsabilidades

Entre los artefactos existentes voy a dividir la responsabilidad de la siguiente manera:

MediaPlayer:

  • Gestión de la interfaz del usuario.
  • Mostrar la playlist y los controles.
  • Toda la lógica de negocio la delega en MediaService y MediaThread

FileExplorer

  • Mostrar los contenidos de la memoria del teléfono, recordando el último directorio visitado.
  • Enviar a MediaPlayer una canción en concreto o las contenidas en un directorio y sus subdirectorios.

MediaServiceBinder

  • Devuelve una instancia de MediaService al objeto que lo solicite (MediaPlayer en este caso).

MediaService

  • Mantiene vivo MediaThread aunque se cierre MediaPlayer.
  • Presenta las notificaciones al usuario en la barra de estado.
  • Gestiona el CallManager que permite parar la reproducción cuando hay una llamada y volverla a iniciar cuando esta termina.

MediaThread

  • Gestiona la reproducción de la playlist implementando toda la funcionalidad detallada arriba.

 

18 mayo, 2012
por Agustín Ventura
2 Comentarios

JustPlay, binding al servicio

El siguiente paso en el desarrollo de JustPlay resulta bastante obvio, ligar el MediaPlayer 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 MediaPlayer 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 MediaPlayer, 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 MediaPlayer, 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 JustPlay en GitHub

17 mayo, 2012
por Agustín Ventura
0 Comentarios

JustPlay, explorador de archivos

Uno de mis requisitos en JustPlay (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=".FileExplorer" android:label="@string/explorer_name"/>

Para arrancarla, creo un botón en MediaPlayer:

<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 FileExplorer desde MediaPlayer.

Ahora toca implementar FileExplorer, 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 FileExplorer, 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 MediaPlayer, 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 MediaPlayer creo un onActivityResult que procese la vuelta desde FileExplorer:

@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 MediaPlayer
  2. Reproducir con un MediaPlayer en el thread.

Código

Sigue en GitHub

JustPlay en GitHub

Repositorio de JustPlay en GitHub

14 mayo, 2012
por Agustín Ventura
0 Comentarios

JustPlay, arquitectura

Bueno, ya va siendo hora de empezar el trabajo en JustPlay.

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 MediaPlayer 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:

JustPlay en GitHub

Repositorio de JustPlay en GitHub

16 abril, 2012
por Agustín Ventura
0 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
0 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
0 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 😀 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