martes, 30 de diciembre de 2014

TEMA #15: RMS

Cuando creamos nuestras aplicaciones/juegos utilizamos datos, los cuales pueden ser siempre los mismos o cambiar, lo más probable es que hagamos aplicaciones/juegos que necesiten guardar ciertos datos y mostrarlos cuando se necesiten. Durante todo el curso te hemos dado las herramientas para hacer tus propias aplicaciones/juegos o quizás compartirlas con alguien más.

Ahora bien, cuando salimos de nuestras aplicaciones, ya sea al pulsar el botón salir de nuestra aplicación/juego, o si al estar usando la aplicación/juego nuestro celular se queda sin batería y se apaga, todos los datos que hayan sido modificados durante el uso de la aplicación/juego se borrarán, y esto no es un error, sino que nuestro celular, en cuanto a J2ME, no dispone de una memoria predeterminada. Para esto es necesario utilizar el Record Management System (RMS), que nos permitirá guardar la información, aún si salimos de la aplicación/juego o si nuestro celular se apaga.

Supongamos por ejemplo que tenemos un juego de aviones y que el puntaje más alto del jugador se almacena en una variable de tipo int que inicialmente es cero. Si no utilizamos el RMS, cuando cerremos nuestro juego y lo volvamos a abrir el valor de esta variable será cero; sin embargo, si utilizamos el RMS podemos guardar este valor para que esté disponible la próxima vez que utilicemos nuestra aplicación.

Para utilizar el RMS es necesario importar el paquete javax.microedition.rms; que nos proporcionará el elemento RecordStore que va a ser el pilar de este tema.

La excepción que debemos atrapar en algunas ocasiones cuando usemos el RMS es RecordStoreException.

Para explicar el Record Management System (RMS) lo dividiremos en cinco partes:
  • Abrir, cerrar y eliminar un RecordStore.
  • Añadir un registro al RecordStore.
  • Leer un registro del RecordStore.
  • Organizar los registros de un RecordStore.
  • Borrar registros de un RecordStore.


Abrir, Cerrar y Eliminar Un RecordStore
Para poder almacenar nuestros datos necesitamos crear un RecordStore que es el encargado de todo.
1
RecordStore rs = RecordStore.openRecordStore(String nombre, boolean nuevo);
2
rs.closeRecordStore();
3
RecordStore.deleteRecordStore(String nombre);

En la línea número 1 vemos cómo crear un recordStore. Los parámetros que debeos proporcionar son: el nombre que le queremos colocar al RecordStore, y el segundo parámetro va a determinar si se debe crear un RecordStore en caso de que no exista uno con el mismo nombre (true), o no (false).

En la línea número 2 vemos cómo cerrar un RecordStore. Esto lo debemos hacer cuando terminemos de usar el RecordStore para que los datos se guarden correctamente.

En el caso de eliminar un RecordStore (línea 3), debemos especificar el nombre del RecordStore que queremos eliminar.



Añadir Un Registro Al RecordStore
Los datos que se pueden guardar en un RecordStore pueden ser simples (de un tipo) o complejos (de varios tipos). En este caso vamos a almacenar datos simples. Veamos cómo los añadimos al RecordStore:
byte[] datos;

try{
      int id = rs.addRecord(byte[] datos, int inicio, int número);
}catch(RecordStoreException rse){}

El primer parámetro que debemos proporcionar son los datos, en forma de un array de bytes, el segundo parámetro indica desde qué posición del array se comenzará a almacenar en el RecordStore, y el último parámetro indica cuántos valores, desde el inicio, se guardarán. Debemos guardar el identificador que resulta cuando añadimos un registro, en este caso la variable id se encargará de hacerlo. Nótese que debemos atrapar el error.

Algo importante en este paso es que podemos almacenar tanto datos numéricos como de tipo String, lo que debemos hacer es convertirlo a bytes a través del método .getBytes();. En el código que se ofrece al final se explica esto.



Leer Un Registro Del RecordStore
Para leer un registro debemos conocer el identificador (el que obtuvimos al añadir el registro a través de .addRecord();), sin embargo, en el siguiente punto vamos a ver cómo podemos saber el identificador de cada registro más fácilmente.
byte[] datos;

try{
      datos = rs.getRecord(int id);
}catch(RecordStoreException rse){}



Organizar Los Registros De Un RecordStore
Para organizar los registros disponemos de RecordEnumeration.
RecordEnumeration re = rs.enumerateRecords(RecordFilter rf, RecordComparator rc, boolean act);

El parámetro act hace que se actualice la información contenida en el RecordEnumeration cuando se añade o se borra un dato del RecordStore. Los otros dos parámetros no son importantes, para efectos de este curso.

De esta manera conocemos los identificadores de los registros que están en el RecordStore:
try{
      re = rs.enumerateRecords(null, null, true);
      while(re.hasNextElement()){
            System.out.println(re.nextRecordId());
      }
}catch(RecordStoreException rse){}

Aquí vemos otros dos métodos que no habíamos mencionado: .hasNextElement() que nos devolverá el valor true si hay un próximo elemento para leer, y .nextRecordId() que obtiene el identificador del registro. En este caso hemos colocado que el identificador del registro se muestre en la consola de salida del emulador de nuestra computadora (System.out.println(...)). Podemos acceder a la consola de salida del emulador entrando en el menú View --> Output console.



Borrar Registros De Un RecordStore
Hay un método utilizado para borrar los registros de un recordStore:
rs.deleteRecord(int id);

Debemos conocer el identificador (id) del registro que queremos borrar, o lo podemos obtener como habíamos mencionado anteriormente, a través de RecordEnumeration. Cuando queramos borrar todos los registros que están contenidos en un RecordStore lo que hacemos es borrar el RecordStore (.deleteRecordStore(String nombre);), sin embargo, si lo que queremos es borrar los registros que están dentro del RecordStore sin eliminar el RecordStore, usamos las siguientes líneas de código:
RecordEnumeration recEnum = rs.enumerateRecords(null, null, false);
while(recEnum.hasNextElement()){
      rs.deleteRecord(recEnum.nextRecordId());
}

El resto de las cosas que podemos hacer con un RecordStore las vamos a descubrir con la práctica. Descarga la aplicación de ejemplo para que veas el funcionamiento del RMS y que los datos guardados persisten a pesar de salirte de la aplicación. También puedes descargar el código y ver cómo se hace todo el proceso.

Aplicación de ejemplo RMS. Descargar App.
Descargar el código y los recursos utilizados en la aplicación. Descargar.

Hasta aquí ha finalizado nuestro curso, por ahora. Si nos has acompañado durante todo el camino hasta aquí ya sabes lo básico para empezar a hacer tus aplicaciones/juegos. Posteriormente se publicarán nuevos temas.

Disfruta lo que aprendiste =) Cualquier comentario o sugerencia no olvides escribir en la sección de comentarios.

TEMA #14: LECTURA DE DOCUMENTOS DE TEXTO (.TXT)

Este tema será corto y sólo comentaremos el código que permite leer un documento de texto (.txt), que son los documentos del bloc de notas de Windows. Este proceso lo hacemos utilizando tres estructuras de control: do/while, for, if/else. Veamos todo lo que debemos importar y el código que utilizaremos:

Imports
private InputStream is = getClass.getResourceAsStream(“dirección el documento”);
private InputStreamReader isr = newInputStreamReader(is, “UTF-8”);
private char[] buffer = new char[10];
private int nchars;
private StringBuffer sb = new StringBuffer();

Código De Lectura
Form form = new Form(“Texto”);

do{
      nchars = isr.read(buffer);
      for(int i=0; i<nchars; i++){
            if(buffer[i] == ´\n´){
                  sb.append(buffer[i]);
            }else{
                  sb.append(buffer[i]);
            }
      }
} while (nchars == buffer.lenght);

form.append(sb.toString());

Algo importante que debemos destacar es que al definir el InputStreamReader le debemos proporcionar el InputStream y el tipo de codificación del documento. El bloc de notas permite guardar texto en diferentes codificaciones: ANSI, Unicode, Unicode big endian y UTF-8; esto se elige cuando entremos en el menú archivo del bloc de notas y le demos a guardar como. Saldrá un cuadro de diálogo donde podemos elegir el nombre del archivo y la codificación.
 
El código que hemos visto nos va a permitir leer un documento de texto (.txt) y poder añadirlo a la pantalla, en este caso una Form. Este elemento de la Interfaz de Alto Nivel es la manera más sencilla de mostrar el texto en la pantalla. La Form se encarga de crear una barra lateral para el “scrolling” y se encarga de agregar saltos de línea cuando una palabra no entre en una línea.


Descarga la aplicación de ejemplo. Descargar App.
Descargar el código y los recursos utilizados en la aplicación. Descargar.

Ahora, existe otra forma de mostrar el texto en la pantalla y es a través de un lienzo de dibujo (Interfaz de Bajo Nivel). Lo que diferencia esta manera de mostrar el texto en pantalla es que nos debemos encargar de los saltos de línea y del “scrolling” del texto agregado, lo que hace más difícil el proceso. Con respecto a esta manera de mostrar el texto en pantalla, les dejo una aplicación de ejemplo y un código donde se muestra cómo hacerlo.


La ventaja que resulta de utilizar un lienzo de dibujo en lugar de una Form es que en el lienzo de dibujo podremos utilizar toda la pantalla para visualizar el texto, en cambio, si utilizamos una Form, el espacio se ve limitado por el título de la Form y la barra de comandos de la Form.
Si no quieres complicarte las cosas, puedes agregar el texto a una Form, pero cuando crees la Form, en lugar de colocarle un título le agregas la palabra null para que no se muestre la barra de título de la Form: Form form = new Form(null);. Veamos cómo se ve:


 
Esto es todo en este tema. Nos vemos en el próximo tema.

TEMA #13: THREAD

En este tema veremos la clase Thread y cómo usarla. La palabra Thread significa hilo; en el contexto de este curso (y de programación en general) lo vamos a definir como una tarea que puede realizarse simultáneamente con otra, y que nos va a permitir realizar procesos de manera simultánea. Podemos utilizar sólo un Thread dentro de nuestra aplicación/juego, o podemos utilizar varios simultáneamente.

¿Cómo Definir Un Thread?
Cuando trabajamos con una aplicación/juego podemos decidir si colocar un Thread “aislado” o crear una clase que implemente la interfaz Runnable. En este caso vamos a ver cómo usar un Thread “aislado”. El paquete que debemos importar a la hora de usar un Thread es java.lang.Thread;. Un Thread se define de la siguiente forma:

Definición
Notas
Thread th = new Thread(){
     public void run(){
           …
           try{
                 Thread.sleep(long milisecs);
           }catch(InterruptedException ie){}
     }
};
th.start();
Vemos la estructura del Thread... Dentro del método public void run(){...} debemos colocar las acciones que realizará el hilo.
Vemos que dentro debemos colocar también una línea de código que determinará cada cuanto tiempo trabajará el Thread y vemos que lanza un error el cual debemos capturar.
Por último iniciamos el Thread a través del método .start();.

Ejemplo: si tenemos un lienzo de dibujo y queremos que cada 5 segundos cambie de color, esto lo hacemos con Thread. Si queremos que una imagen se mueva de un lugar a otro continuamente, lo hacemos con Thread.

Como te habrás dado cuenta, en todas las aplicaciones de ejemplo la pantalla de presentación consiste en muchos círculos y un fondo que van cambiando poco a poco de color. Este efecto de cambio de color de poco a poco lo hacemos con Thread.


Redundancia de Thread
En mi experiencia con el uso de los Threads tuve un problema que me enseñó dos métodos de cómo evitar lo que yo llamo “redundancia de Thread”. Miremos el siguiente ejemplo gráfico con el cual explicaremos la redundancia de Thread:


Supongamos que estamos haciendo una aplicación que funcione como cronómetro y necesitamos que cada segundo se le sume 1 a la cuenta de los segundos y que se empiece a contar cuando se presione la tecla 1 del teclado de nuestro celular. Veamos el código de la imagen de acá arriba: el Thread que definimos se va a encargar de aumentar el valor de la cuenta (cuenta+=1;) y más abajo está cada cuánto tiempo se realizará la acción (Thread.sleep(1000);), que debe ser colocado en milisegundos. En este caso, cada 1 segundo (1000 milisegundos) se aumentará el valor de la variable cuenta en 1. Las últimas líneas determinan cuándo se iniciará el Thread, en este caso, cuando dentro de un lienzo de dibujo pulsemos la tecla 1 del teclado de nuestro celular.

Pero, ¿dónde está el problema?, ¿dónde ocurre la redundancia de Thread? Pues bien, dijimos que cuando pulsamos la tecla 1 del teclado de nuestro celular se inicia el Thread, ahora, si pulsamos nuevamente la tecla 1 cuando el Thread se está ejecutando se recibirá nuevamente la orden de iniciar el Thread. El efecto que causa la redundancia de Thread es que la velocidad del Thread se aumentará, deshaciendo así la coordinación del Thread. Esto no resulta útil porque en lugar de que se aumente el valor de la variable cuenta en 1 cada 1 segundo, se aumentará en 1 pero el tiempo será acortado (cada 0.5 segundos).

Descarga la aplicación de ejemplo donde se muestran los efectos de la redundancia de Thread. Descargar App.
Descarga el código y los recursos utilizados en esta aplicación. Descargar.



Métodos Para Evitar La Redundancia De Thread
Para evitar la redundancia de Thread me he dado cuenta de dos formas para evitarla, las llamaremos “Método de la llave” y “Método de confirmación”.

Método de la llave
Este método consiste en colocar una variable de tipo numérico (llave) que es la que va a determinar cuándo se inicia el Thread, y cuando se inicie el Thread, éste se encargará de cambiar el valor de la llave para que no se active nuevamente el Thread. Veamos el siguiente ejemplo gráfico:

En este ejemplo si pulsamos la tecla 1 del teclado de nuestro celular activará una llave que se encargará de activar el Thread. Una vez que el Thread se activa, el mismo Thread se encarga de desechar la llave para que cuando de pulse la tecla 1 nuevamente no ocurra la redundancia de Thread.

Ahora, ¿cómo creamos la llave y cómo la desechamos? Mencionamos anteriormente que la llave va a ser una variable de tipo int. Cuando creamos esta variable “creamos la llave”, cuando esta variable tenga un valor específico se activará el Thread (usamos la llave) y cuando el Thread se active se encargará de cambiar el valor de la variable (desechamos la llave).

Descarga un código donde se corrige el error de la aplicación del principio, utilizando este método. Descargar.
Descarga la aplicación. Descargar.

Método de confirmación
Este método consiste en utilizar la estructura de control if/else para confirmar si el Thread se encuentra ejecutándose o no, lo que nos permitirá activar el Thread, si no está activo, y en el caso de que esté activo, no hacer nada, evitando la redundancia de Thread. Veamos el siguiente ejemplo gráfico:

Vemos que para que se realice la acción primero se pasa por un filtro para confirmar que el Thread no se encuentre activo. Si no se encuentra activo se inicia (thr.start();), pero si ya se encuentra activo no se realiza ninguna acción.

Nota: para utilizar este método es indispensable que los Threads que utilicemos los coloquemos al principio y los definamos como un Thread vacío:

private Thread thr = new Thread();

Si no se hace esto no funcionará el método de confirmación.

Descarga un código donde se corrige el error de la aplicación del principio, utilizando este método. Descargar.
Descarga la aplicación. Descargar.

Por último te dejo la porción de código que corresponde a la pantalla inicial de las aplicaciones de ejemplo a lo largo del curso. Descargar.

TEMA #12: SONIDOS

En este tema veremos cómo agregar sonido a una aplicación/juego. Aprenderemos a cómo reproducir sonidos, a pausarlos y otras cosas. Observemos esta primera imagen:
A través de esta imagen lo que quiero explicar es que Manager es la clase principal en este tema y es la que va a permitir crear un Player, el cual va a permitir reproducir audio y video. La clase control, como vemos, es la que está dentro de Manager y Player, y va a permitir controlar los Players que creemos. El import que debemos hacer es: javax.microedition.media.*;


Tipos de Audio y Video
Los archivos de audio y de video tienen diferentes formatos y dependiendo del formato nuestro celular será capaz o no de reproducirlo. No nos detendremos en cada uno de estos formatos, sólo los mencionaremos.

Formatos De Audio
Clave
Formatos De Video
Clave
Wave audio files
“audio/x-wav”
3GP video files
“video/3gp”
AU audio files
“audio/basic”
MPEG4 video files
“video/mp4”
MP3 audio files
“audio/mpeg”
-
-
MIDI audio files
“audio/midi”
-
-
Tone Sequences
“audio/x-tone-seq”
-
-

Existen otros formatos de audio y de video, pero, en J2ME, estos son los importantes.
Es importante saber qué tipo de formato soporta tu celular para que evites usar un formato inadecuado a la hora de hacer aplicaciones/juegos para tu celular. Descarga esta aplicación que te dirá qué formatos de audio y video soporta tu celular. Descargar App.

Si alguna vez tienes problemas con el formato de un archivo de audio y quieres cambiarle el formato, te recomiendo el siguiente programa. Descargar.

Si tienes problemas con el formato de un archivo de video, te recomiendo el siguiente programa para cambiarle el formato. Descargar.


Fuente Del Sonido
El  archivo de sonido que se reproducirá en la aplicación/juego puede obtenerse de una dirección web o de la misma aplicación/juego. En lo particular, no he utilizado la primera opción. En este tema sólo vamos a trabajar con la segunda opción, para lo cual debemos conocer la clase InputStream que nos va a permitir comunicar la aplicación/juego con el archivo de audio (o video) guardado en la misma. Se define de manera sencilla:

InputStream inStream = getClass().getResourceAsStream(“ubicación del archivo”);

Vemos que esta vez no utilizamos la palabra “new”, y esto es una particularidad de esta clase cuando la usamos para localizar archivos guardados en la propia aplicación/juego. El único parámetro que debemos proporcionar es la ubicación del archivo y esto es similar a como lo hicimos al ubicar una imagen (ver tema correspondiente). También debemos recordar que el archivo debe ser copiado a la carpeta “src” del proyecto que estamos creando, para que la aplicación/juego pueda ubicarlo.


Player
El Player es el que se va a encargar de reproducir los archivos de audio o video. Es necesario mencionar que el Player puede estar en 5 estados: UNREALIZED, REALIZED, PREFETCHED, STARTED y CLOSED. Cuando creamos un Player está en el estado UNREALIZED. Cuando lo pasamos al estado REALIZED el reproductor realiza la comunicación necesaria para localizar los elementos que necesita para funcionar (tal como un archivo). Cuando de detiene el reproductor el Player pasa de un estado STARTED a un estado PREFETCHED.


Player de Audio
A través de la clase Manager podemos reproducir sonidos, desde sonidos sencillos hasta archivos de sonidos, por lo tanto hay tres formas de crear un sonido:

Sonido
Notas
Manager.playTone(...);
Reproduce un solo sonido.
Player p = Manager.createPlayer(Manager.TONE_DEVICE_LOCATOR)
Reproduce una secuencia de sonidos que determinemos.
Player p = Manager.createPlayer(InputStream iStream, String clave);
Reproduce un archivo de audio. Ya hablamos de InputStream. El parámetro clave depende del tipo de archivo (ver la primera tabla de este tema).

Nos concentraremos en la última de estas tres formas, que es la más importante.
Para crear un Player debemos definir primero el InputStream que utilizará y luego podemos definir el Player. Veamos un ejemplo:

InputStream inStream = getClass().getResourceAsStream(“/música.mp3”);

Player pl = Manager.createPlayer(inStream, “audio/mpeg”);
pl.start();

Luego que creemos el Player, para reproducirlo usamos el método .start();, como muestra el ejemplo.

Nota: al crear un Player ocurren dos errores que debemos atrapar, IOException y MediaException. Si queremos atrapar dos errores lo que debemos hacer es agregar otro catch para atrapar el segundo error, así:

InputStream inStream = getClass().getResourceAsStream(“/música.mp3”);

try{
    Player pl = Manager.createPlayer(inStream, “audio/mpeg”);
    pl.start();
}catch(IOException io){
}catch(MediaException me){}


Player de Video
Manager nos permite reproducir videos en nuestro celular, siempre y cuando lo soporte. Podemos definir este tipo de Player de la siguiente manera:

Player pl = Manager.createPlayer(InputStream iStream, String clave);

Pero esto no es todo lo que debemos hacer, sino que debemos tomar el control del video y colocarlo dentro de un elemento de tipo Form (no he probado colocarlo en un lienzo de dibujo...). Veamos un ejemplo completo:

try{
InputStream in = getClass.getResourceAsStream(“/mivideo.3gp”);
Player pl = Manager.createPlayer(in, “video/3gp”);

pl.realize();
GUIControl gc = (GUIControl)pl.getControl(“GUIControl”);
if(gc != null){
    Form form = new Form(“Mi Video”);
    form.append((Item)gc.initDisplayMode(GUIControl.USE_GUI_PRIMITIVE, null));
    display.setCurrent(form);
}

pl.start();

}catch(MediaException me){
}catch(IOException io){}

Un método importantes es .setDisplayFullscreen(boolean valor); que pertenece a VideoControl o GUIControl que permite expandir el tamaño del video hasta que llegue al tope del ancho o alto de la pantalla (true). Se hace de la siguiente forma:

GUIControl gc = (GUIControl)player.getControl(“GUIControl”);
gc.setDisplayFullscreen(true);

Descarga la aplicación de ejemplo de este tema. Descargar App.

Descarga el código y los recursos utilizados en la aplicación. Descargar.