J2ME a la carta — Para MIDP 1.0 y 2.0

Anuncio
J2ME a la carta — Para MIDP 1.0 y 2.0
Mauricio Monsalve
1
Estructura básica
Ésta se entiende con el siguiente ejemplo:
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*; (1)
Imports básicos. Son el MIDlet y la interfaz.
public class XXX extends MIDlet implements CommandListener { (2)
La clase. Es MIDlet y tiene CommandListener ⇒ Posee void commandAction(...)
private Command EC;
private Display D;
private Form F;
Elementos básicos a usar en el MIDlet.
public XXX() { (3)
D=Display.getDisplay(this);
EC=new Command(“Salir”,Command.EXIT,2);
F=new Form(“Ejemplo”);
StringItem si=new StringItem(“”,“Mini-MIDlet”);
F.append(si);
F.addCommand(EC);
F.setCommandListener(this);
}
Hice el Display, agrego texto al formulario y hago que esta clase maneje comandos.
public void startApp() throws MIDletStateChangeException{ (4)
D.setCurrent(F);
}
El formulario usará el Display D.
public void pauseApp() { }
public void destroyApp(boolean z) { }
(5)
Abstractas: startApp, pauseApp, destroyApp... Deben salir siempre.
public void commandAction(Command c, Displayable S) (6) {
if (c==EC) {
destroyApp(false);
notifyDestroyed();
}
}
Aquı́ manejo la salida. Ojo: El código siempre acaba ası́.
}
El ejemplo anterior tiene como salida un MIDlet que dice “Mini-MIDlet” en el display y que al ejecutar
el nico comando aqu, “Salir”, la aplicacin termina su ejecución. Eso básicamente y, en temas de generalidad,
1
es lo único que se puede saber con certeza sobre este MIDlet. La apariencia del MIDlet variará de equipo
a equipo pues es decisión del fabricante cómo implementa los requerimientos (o especificaciones) que indica
Sun Microsystems sobre la ejecución de J2ME, Java Micro Edition.
Cada fabricante decide la apariencia de las interfaces de usuario generales (import javax.microedition.lcdui.*)
según más estime conveniente. Lo siguiente es un ejemplo de cómo se ven las interfaces grficas en diferentes
terminales (emulados con diferentes emuladores de Java Wireless Toolkit).
Pese a las diferencias, queda claro que el MIDlet mostrar el texto “Mini-MIDlet” y el comando “Salir”,
ambos de alguna forma (no necesariamente estarán siempre visibles, quizás el comando no se lea explicitamente, puede estar marcado como una flecha que lleve a un menú que tenga los comandos dispuestos por la
aplicación).
En cuanto a la estructura de la demo:
(1) Son los imports básicos. Ambos comienzan con javax.microedition pues son paquetes oficiales Java.
Ahora, midlet.* contiene la clase MIDlet, la más importante de todas. El celular tratará de cargar
el MIDlet. Más aún, la primera clase de la aplicación, la referenciada por el jad1 , es un MIDlet. El otro
contiene las clases con las interfaces gráficas.
(2) Un MIDlet precisamente debe heredar la clase MIDlet. Nótese que el nombre del archivo fuente del
MIDlet debe ser XXX.java puesto que el MIDlet se llama XXX. ActionListener es la interface que
indica que la clase XXX capturará los eventos provocados por la activación de los comandos. Esto es, debe
poseer el método void commandAction(Command,Displayable) que aparece más abajo (6).
(3) public XXX() es el constructor. La máquina que corra la aplicación invocará este método como primera
tarea. Nótese que posee el mismo nombre que la clase. Si la clase se llama Clase, entonces el método será
public Clase(). En este caso, public XXX() construye los objetos necesarios a usar más adelante. Eso
debiera ser lo tradicional.
(4) Una vez construido el MIDlet, la máquina virtual ejecutará el método startApp. Este método debe ser
escrito, aunque no contenga nada; es la exigencia del MIDlet. Pero nótese el contenido del método: Indica
que el Display (acá ‘D’) debe mostrar el MIDlet XXX (D.setCurrent(this)). Eso debiera ocurrir siempre.
(5) startApp, pauseApp y destroyApp deben ser escritos siempre. Han sido definidos como abstractos.
(6) void commandAction(Command,Displayable) es el método que debió ser colocado a causa de haber
implementado CommandListener en XXX. commandAction es llamado cada vez que una acción es ejecutada.
Los parámetros son el comando y el displayable (el objeto que puede ser colocado en la pantalla, como el
MIDlet y el Canvas) asociada a la circunstancia de activación del comando. En el ejemplo, aunque no era
necesario, se identificó el comando que llamó al MIDlet. El ejemplo muestra la importancia de que los
comandos sean variables globales.
Como se aprecia, no hay void main(). La clase se corre primero por el constructor (en este caso XXX())
y luego se llama a startApp() del MIDlet.
1 Una
aplicación J2ME consiste de dos archivos: un .JAD y un .JAR.
2
2
Interfaz de usuario
Viene de lcdui. Es una interfaz de usuario de alto nivel muy simple de implementar. Esto se debe a que
una pantalla de dispositivo móvil (sea Palm, teléfono, etc.) es muy pequeña. Ojo: Todo esto es compatible
con MIDP 1.0, luego deberı́a funcionar en general. MIDP 2.0 sólo extiende las capacidades de la versión 1.0.
Es compatible hacia atrás.
2.1
Comandos
Son las opciones que suelen aparecer en la base de la pantalla. Sintaxis del constructor:
X=new Command(”TXT”,Command.Y ,int prioridad);
Y ∈ {OK,CANCEL,STOP,BACK,HELP,SCREEN,ITEM}
ITEM y SCREEN son tipos genéricos.
2.2
Elementos (displayable)
Cada uno de estos elementos utiliza la pantalla (el Display) en su totalidad, compartiendo espacio con los
comandos. Para pasar de elemento a elemento, y procurar que la aplicación tenga más de una pantalla, se
debe pasar cada elemento al display ası́:
Display D;
...
D.setCurrent(X);
Aquı́ X es el elemento que usará la pantalla. Esto se usará cada vez que se necesite pasar de una pantalla
a otra. Ello deberı́a ocurrir en el método commandAction() (caso de programa ası́ncrono). En tanto, X
puede ser uno de los siguientes objetos:
TextBox: Caja de texto (input). Constructor:
TextBox(“Nombre”,“Def”,N max,TextField.Y);
N max: Número máximo de letras.
Y ∈ {ANY,NUMERIC,PHONENUMBER,URL,
EMAILADDR,PASSWORD}
getString() devuelve el valor ingresado.
3
Alert: Mensaje de alerta. Constructor:
Alert(“Nombre”,“Msg”,Image,AlertType.Y);
Image es una imagen. Puede ser null.
Y ∈ {ALARM,CONFIRMATION,ERROR,INFO,
WARNING}
List: Selección múltiple. Constructor:
List(“Nombre”,List.Y,String[] Elems,Image[] Imgs);
Imgs puede ser null.
Y ∈ {EXCLUSIVE,MULTIPLE,IMPLICIT2 }
Obtención de la información:
getSelectedFlags(boolean[]) guarda en el arreglo si un elemento fue elegido o no, en el caso que la
lista fuese de tipo MULTIPLE. De lo contrario, getSelectedIndex() devuelve el ı́ndice del elemento
(ı́ndice según el arreglo).
Form: Contenedor. En el ejemplo se usó Form. Pero es suficientemente complicado para darle una sección.
2.3
Contenedor Form
Form permite hacer pantallas a la medida. Métodos:
Form(“Tı́tulo”) Constructor. La cadena “Tı́tutlo” deberá aparecer en la parte superior de la pantalla.
append(Item) Agrega un ı́tem.
En cuanto a los ı́temes, son los siguientes:
StringItem: Etiqueta de texto. Constructor:
StringItem(“Tı́tulo”,“Txt”);
ImageItem: Una imagen. Constructor:
ImageItem(“Tı́t”,Img,ImageItem.LAYOUT Y,“Al”);
Img es de tipo Image.
Al es texto alternativo a la imagen.
Y ∈ {DEFAULT,LEFT,RIGHT,CENTER,
NEWLINE BEFORE,NEWLINE AFTER}
Para combinar LAYOUTs, usar or lógico.
2 Implı́cito
porque el foco implica la selección.
4
TextField: Campo de texto. Constructor:
TextField(“Tı́tulo”,“Txt”,N max,TextField.Y);
Su uso es idéntico al de TextBox.
DateField: Campo de fecha. Constructor:
DateField(“Tı́tulo”,DateField.Y);
Y ∈ {DATE,TIME,DATE TIME}
getDate() retorna la información.
Gauge: Barra de estado. Constructor:
Gauge(“Tı́tulo”,Interactivo?,min,max);
Interactivo? es de tipo boolean.
Para manejar la información (estado de la barra) se usa setValue() y getValue(), siendo el primero
el más importante.
ChoiceGroup: Selección múltiple. Constructor:
ChoiceGroup(“Tı́tulo”,Choice.Y,String[],Image[]);
Y ∈ {EXCLUSIVE,IMPLICIT,MULTIPLE}
Se usa de la misma manera (con los mismos métodos) que la clase List.
3
Generando aplicaciones
Cuando se crea una aplicación para dispositivo móvil se necesita una serie de rutinas (o prácticas) a seguir.
Todo esto se resuelve con el uso de la aplicación Java Wireless Toolkit creada por SunM icrosystems.
Es una aplicación muy cómoda y disponible para Windows, Unix/Linux y Solaris.
3.1
Nuevo proyecto
La instalación de la aplicación es super sencilla. Luego basta ejecutar KToolbar para iniciar el desarrollo
de la aplicación. En N ew P roject se escribe el nombre del proyecto y el nombre de la clase principal (sin
extensiones). Pueden ser el mismo.
Naturalmente, OpenP roject abre un desarrollo existente, Build compila y Run ejecuta. En Settings
se puede elegir, por ejemplo, el alcance de la aplicación: Si es MIDP 1.0, MIDP 2.0, si requiere Bluetooth,
MMAPI, Java3D, etc. Ojo: La máxima compatibilidad se alcanza con MIDP 1.0.
5
Al crear un nuevo proyecto se crea una estructura de directorios dentro del directorio de proyectos. Por
ejemplo, si creamos el proyecto XXX, aparecen:
XXX/
XXX/bin/
XXX/src/
XXX/lib/
XXX/res/
Y pueden aparecer otros sobre la marcha (clases/, tmp...). Lo importante el codigo fuente debe ir siempre
en src/ y que los recursos (imágenes y sonidos) deben ir siempre en res/.
En el directorio bin/ aparecerá MANIFEST.MF, que es el archivo con la información del proyecto, y el
archivo proyecto.jad. El archivo .jad es el descriptor de la aplicación. Si se empaqueta la aplicación (create
package), entonces aparecerá un archivo proyecto.jar. Un archivo .jar es un mero archivo .zip. Pero mucho
ojo, en el archivo .jad aparece el tamaño del archivo .jar; si no coinciden, habrá un fallo en la validación de
la aplicación, por lo que no funcionará en los distintos equipos.
Ahora bien, Wireless Toolkit no trae aplicaciones de edición de texto, ası́ que habrá que hacerlo a la
antigua en el directorio src/ (como más se quiera, se puede usar write, notepad, eclipse, vim, emacs, gedit3 ).
Ahora, KToolbar se puede dejar en segundo plano. Y cuando sea necesario probar, se traerá a primer plano
y se ejecutará build y run. La aplicación correrá automágicamente en un emulador. Ojo: se pueden agregar
dispositivos a emular.
3.2
Imágenes
La imagen deberı́a ser siempre png4 . Es la alternativa abierta a GIF, ya que esta última tuvo muchos
problemas de derechos. Se supone que todos los dispositivos móviles5 soportan png. Ojo: todas las imagenes
deben estar en el directorio res.
Una imagen se carga ası́:
Image img;
try{
img=Image.createImage("/imagen.png");
}
catch(IOException e) {
... // Control de algun error
}
El slash (/) en el nombre del archivo indica que se está solicitando el archivo en la raı́z del directorio de
recursos: res/.
3.3
Audio (MIDP 2.0)
MIDP 2.0 Media API permite la reproducción de sonidos en los dispositivos móviles. Como es MIDP 2.0,
no sirve para todos. Y si se desea más multimedia, MMAPI (Mobile Media API) es la solución, pero es un
paquete opcional. MMAPI ya permite reproducción de MPEG, GIF animados, etc. Sólo algunos equipos lo
soportan.
Para cargar música midi, por ejemplo, se ejecuta lo siguiente:
import java.io.*;
import javax.microedition.media.*
...
3 Gnome
editor, gedit, es sensacional. Crimson Editor, para Windows, es similar.
es abreviatura de Portable Network Graphics.
5 Al menos todos aquellos que soportan imágenes.
4 PNG
6
try {
InputStream ins = getClass().getResourceStream("jazz.mid");
Player p = Manager.createPlayer(ins,"audio/midi");
p.setLoopCount(5);
p.start();
} catch (Exception e) { ... }
En este ejemplo, se carga el archivo MIDI contenido en res/ y se ajusta al objeto de clase Player para
que reproduzca 5 veces seguidas esa melodı́a MIDI. Para acabar con la ejecución, se puede invocar el método
close() de Player (en este caso, serı́a p.stop() ). Lo anterior también libera los recursos del reproductor.
Manager es la clase estática que construye los Player según cada circunstancia. Manager.createPlayer()
hace un Player. Hay dos maneras de llamarlo:
Manager.createPlayer("URL"); //Invocando un URL
Manager.createPlayer(InputStream,"tipo"); //En este caso tipo=audio/midi.
//Otro tipo pudo ser audio/x-wav, por ejemplo.
Por supuesto que una aplicación compilada para MIDP 2.0 no funcionará en equipos con MIDP 1.0.
4
Gráficos a nivel más bajo
No es realmente a bajo nivel, pero al menos un nivel suficientemente bajo como para tener un control decente
sobre la gráfica del dispositivo.
4.1
Canvas
De tipo Displayable, puede ser usado por Display al igual que Form, TextBox, etc. Ası́ mismo, pertenece a
lcdui. Por eso mismo, una objeto Canvas acepta:
- La inclusión de comandos, i.e., miCanvas.addCommand(BYE);
- Ser puesto como Display, i.e., D.setCurrent(miCanvas);
Una opción muy recomendada es extender Canvas cuando sea necesario. Eso da uso completo a éste.
Las siguientes son funcionalidades que incluye Canvas:
int getGameAction(int keycode) Dado un keycode, entrega si este es un Canvas.LEFT, Canvas.RIGHT,
Canvas.UP, Canvas.DOWN, Canvas.FIRE, etc.
void keyPressed(int keycode) Hay que implementarlo. Este método es invocado cuando se pulsa
una tecla. En su interior es recomendado usar getGameAction para el caso de los juegos.
void keyReleased(int keycode) Hay que implementarlo. Tal como el anterior, pero cuando se libera
una tecla.
int getWidth() Entrega el ancho del Canvas.
int getHeight() Entrega el alto del Canvas.
int paint(Graphics g) Hay que implementarlo. Este método es invocado cuando hay que dibujar la
pantalla.
repaint() Solicita dibujar la pantalla.
7
4.2
Graphics
Este objeto controla los dibujos hechos en algún objeto que entrega algún objeto dibujable.
setColor(R,G,B) Elige un color para dibujar. Cada parámetro está entre 0 y 255.
setGrayScale(T) Elige un gris, T ∈ [0,255].
drawLine(x0 , y0 , x1 , y1 ) Dibuja una lı́nea entre (x0 , y0 ) y (x1 , y1 ).
drawRect(x0 , y0 , ancho, alto) Dibuja un rectángulo vacı́o.
fillRect(x0 , y0 , ancho, alto) Dibuja un rectángulo lleno.
drawArc(x, y, r0 , r1 , ω0 , ω1 ) Dibuja un segmento de arco de una elipse centrada en x, y, entre los angulos
ω0 y ω1 , cuyas distancias al centro son r0 y r1 respectivamente. Los ángulos son sexagesimales (0 a
360).
drawFillArc(x, y, r0 , r1 , ω0 , ω1 ) Idéntico al método anterior, pero rellena desde el centro hasta el arco.
drawString(“Texto”,x,y,Graphics.A) Dibuja una cadena de texto en la posición indicada.
A ∈ {LEFT, RIGHT, HCENTER} ∪ {TOP, BOTTOM}. Para combinar opciones se usa or.
drawImage(Image,x,y,Graphics.A) Dibuja una cadena de texto en la posición indicada.
A ∈ {LEFT, RIGHT, HCENTER} ∪ {TOP, BOTTOM, VCENTER}. Para combinar opciones se usa
or. Nótese que a diferencia de drawString, drawImage admite ancla en VCENTER (centro vertical).
Nota muy importante: Todos los métodos que hay que implementar son protected. Al escribirlos, es
posible colocar, o bien protected, o bien public, pero jamás colocarles private. ¡Deben ser vistos por otras
clases!
4.3
Image & Graphics
Una imagen, o sea, un objeto de tipo Image, puede ser dibujado mediante Graphics. Esto agrega una
nueva forma de tener imágenes; haciéndolas en el código en vez de cargarlas de un archivo. Pero ojo, que
Java no maneja el tema de transparencias. La única forma de tener transparencias es con imágenes PNG
transparentes.
El siguiente código es una receta general para crear imágenes mediante primitivas:
Image X=Image.createImage(100,60); //100 pixeles de ancho, 60 de alto
Graphics Y=X.getGraphics(); //Controlar de la grafica de X
... //hacer lo que se quiera en Y. Los cambios se veran en X.
El siguiente ejemplo ilustra un cuadrado verde con degradado:
Image X=Image.createImage(60,60);
Graphics Y=X.getGraphics();
for(int i=0;i<60;i+=5) {
Y.setColor(2*i,4*i,2*i); //Color primariamente verde
Y.fillRect(0,i,60,5); //Rectangulo de 60x5 desde 0,i
}
8
5
Lógica propuesta para juego J2ME
Esta es sólo una idea personal, pero creo que es bastante efectiva a la hora de construir juegos. Consiste
en dar todo el poder al Canvas, y hacerlo thread (hilo de ejecución). A continuación se muestra el esquema
general.
import javax.bla.bla.*; /bis/
public class X extends MIDlet implements CommandListener{
...
Display d;
Thread t;
Cvas c;
...
public X() {
d.getDisplay(this);
c=new Cvas();
t=new Thread(c);
...
}
public startApp() thr... {
t.start();
//Hilo de ejecucion paralelo
d.setCurrent(t);
}
...
} //Fin del MIDlet
public class Cvas extends Canvas implements Runnable{
...
public Cvas() {
super();
...
//Lo pertinente
}
public void paint(Graphics g) {
...
//Dibujar la pantalla
}
public void run() {
...
//Codigo SECUENCIAL del juego :)
...
sleep(50); //Ejemplo, una pausa, dormir 50 milisegundos
...
repaint(); //Ejemplo, actualizar la pantalla
...
}
...
//Resto de la logica del Canvas
}
Y, por supuesto, se debe implementar todo lo indicado en la sección Canvas. Con esta estructura, más
lo indicado en secciones anteriores, se podrı́a empezar a escribir un juego rápidamente.
9
6
Ejemplo: Diapositiva
La siguiente clase, llamada Diapo, extiende a Canvas, y tiene la gracia de ser una visualizadora de diapositivas. La diapositiva será un arreglo de Strings (cadenas, texto) cuyos elementos sean nombres de imágenes.
Al pulsar una tecla, la imagen cambiará. Nótese que lo siguiente no es un juego. Lo único que interesa es
saber cuándo se presiona una tecla. Luego su estructura es radicalmente simple: La lógica queda en paint y
en keyPressed. Teclas: Arriba o Izquierda hacen volver en la diapositiva, Abajo o Derecha hacen avanzar.
import javax.microedition.lcdui.*;
//Es solo un Canvas, no es un midlet
public class Diapo extends Canvas{
//No necesito threads, no implemento Runnable
int num_diap;
Image[] foto;
//El numero de la diapositiva
//El arreglo con las fotos
private Image Cargar(String nombre_foto) { //Carga una foto
Image x;
try { x = Image.createImage(nombre_foto); } //Trato de cargar la foto
catch(Exception e) { x=null; } //Y si no se puede, anulo
return x;
}
public Diapo(String[] txt) {
//Constructor. Recibe un arreglo de Strings.
foto = new Image[ txt.length ];
//Arreglo con tantos elementos como txt[]
for( int i=0 ; i<txt.length ; i++ ) //Para recorrer todo el arreglo
foto[i] = Cargar( txt[i] ); //La imagen[i] es el archivo[i]
num_diap=0; //Fijo la primera diapositiva
}
private void paint(Graphics g) {
//A dibujar. ’Graphics g’ es la pantalla.
g.setColor( 255 , 255 , 255 );
//Color blanco
g.fillRect( 0 , 0 , getWidth() , getHeight() );
//Pinto la pantalla
if ( foto[ num_diap ] != null)
//Solo dibujare algo que tengo
g.drawImage( foto[ num_diap ] , getWidth()/2 , getHeight()/2,
Graphics.HCENTER | Graphics.VCENTER );
//Dibujo la foto de forma que el centro de la imagen a pintar
//coincida con el centro de la pantalla (imagen centrada)
}
private void keyPressed( int keycode ) { //Eventos de teclado
int accion = getGameAction( keycode ); //A evento de juego
if ( ( accion == UP ) || ( accion == LEFT ) )
num_diap--;
//Arriba o izquierda => ir para atras
if ( ( accion == DOWN ) || ( accion == RIGHT ) )
num_diap++;
//Abajo o derecha => ir para delate
//Los siguientes son casos de borde (correctivos)
if ( num_diap < 0 ) num_diap = 0;
if ( num_diap >= foto.length ) num_diap = foto.length - 1;
repaint(); //Ahora tengo que dibujar
}
}
10
7
Ejemplo: MIDlet que utiliza la diapositiva
El siguiente MIDlet requiere tener en el directorio res/ las imágenes f oto1.png, f oto2.png y f oto3.png. Se
llama ppal, por tanto su archivo será ppal.java.
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
public class ppal extends MIDlet implements CommandListener{
Command EX;
Display d;
Diapo DI;
public ppal() {
d=Display.getDisplay(this);
EX=new Command("Salir",Command.EXIT,1);
String[] txt={"/foto1.png","/foto2.png","/foto3.png"};
DI=new Diapo(txt);
DI.addCommand(EX);
DI.setCommandListener(this);
}
public void startApp() throws MIDletStateChangeException{
d.setCurrent(DI);
}
public void pauseApp() {}
public void destroyApp(boolean z) {}
public void commandAction(Command c, Displayable dis) {
destroyApp(false);
notifyDestroyed();
}
}
11
Descargar