Master Universitario en Informática Aplicada a las Telecomunicaciones Móviles Programación Java J2ME: Java para dispositivos móviles Febrero 2006 David Bueno Vallejo Plan • • • • • • • • • Introducció Introducción Instalació Instalación Primer Programa Interfaz de Usuario en J2ME – Commands – Screen (Alertas, formularios, imá imágenes, choicegroup, choicegroup, ticker) ticker) – GameCanvas (lectura de teclado, dibujar en pantalla, sprites, sprites, tiledLayer, tiledLayer, tiledManager, tiledManager, scroll, scroll, colisiones) RMS HTTP Vibració Vibración e Iluminació Iluminación Otros: bluetooth/ bluetooth/obex, obex, servicios Web, Grá Gráficos 3D, sonido y mú música Referencias 2 1 Introducción • Se • llama J2ME (Java Micro Edition) Edition) a las implementaciones de Java par dispositivos mó móviles Hay varias implementaciones dependiendo del tipo de dispositivo Teléfonos, PALM, Blackberry PDAs Pocket PC Blackberry CLDC y MIDP Personal Java (jdk 1.22) API propia • En este tema se ve la implementació implementación relacionada con Telé Teléfonos Mó Móviles, PALM y Blackberry (API MIDP) 3 Introducción El universo de J2ME. Imagen de [Knudsen2003] 4 2 Introducción Componentes software de MIDP. Imagen de [Knudsen2003] 5 Instalación • Java Development Kit (JDK) Descripció Descripción: Entorno de desarrollo de java para PC. Versió Versión 1.4.2 o superior (ú (última 1.5 y 1.6 Beta) Descarga: http://java.sun.com/j2se/downloads.html j2sdkj2sdk-1_4_21_4_2-windowswindows-i586.exe i586.exe y j2sdkj2sdk-1_4_21_4_2-doc.zip (docs) docs) Instalació Instalación: • Ejecutar el instalador y seleccionar la carpeta por defecto. P.ej: P.ej: c:\ c:\jsdk1.4.2 jsdk1.4.2 • Descomprimir la documentació documentación en esa carpeta • Actualizar la variable de entorno PATH=c:\ PATH=c:\jsdk1.4.2 jsdk1.4.2\\bin\ bin\ 6 3 Instalación • J2ME Sun Java Wireless Toolkit Descripció Descripción: Entorno para compilar y generar .jar .jar y .jad .jad incluye emuladores Descarga: http://java.sun.com/products/sjwtoolkit/download http://java.sun.com/products/sjwtoolkit/download--2_3.html sun_java_wireless_toolkitsun_java_wireless_toolkit-2_32_3-betabeta-windows.exe (Admite bluetooth, , multimedia, API 3D, posicionamiento,… bluetooth posicionamiento,…) Instalació Instalación: • Ejecutar el instalador y seleccionar destino: c:\ c:\WTK23 7 Primer Programa: Hola Mundo • A continuació continuación se va a realizar el primer programa: HolaMundo • Primero se compilará compilará y ejecutará ejecutará en el PC en un emulador • Despué Después se enviará enviará al mó móvil y se ejecutará ejecutará en él. • Los pasos son los siguientes: 1. Con un editor de texto escribir el programa de la transparencia siguiente. Guardarlo como HolaMundo.java 8 4 Primer Programa: Código y explicación import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class DBVHolaMundo extends MIDlet implements CommandListener { private Display pantalla ; private TextBox texto1 ; private Command salir; public DBVHolaMundo() { pantalla = Display.getDisplay(this); // Devuelve un manejador de la pantalla salir = new Command("Salir", Command.SCREEN, 1); // Crea un nuevo comando para Salir texto1 = new TextBox("Hola Mundo", "Mi primer MIDlet", 40, 0); // Crea un cuadro de texto texto1 .addCommand(salir); // Añade el comando al cuadro de texto texto1 .setCommandListener(this); // Indica que la gestión de evento se hace en esta clase } public void startApp() { // Se ejecuta al comenzar la aplicación pantalla .setCurrent(texto1); // Muestra en la pantalla el texto y el comando } public void pauseApp() { } // Se ejecuta si se interrumpe la aplicación public void destroyApp(boolean unconditional) { } // Se ejecuta al terminar public void commandAction(Command c, Displayable displayable) { // Gestiona los eventos if (c == salir) { // Si el evento es pulsar el botón salir, termina destroyApp(false); notifyDestroyed(); } } } 9 Primer Programa: Proyecto 2. Abrir el programa Ktoolbar (c:\ (c:\j2me\ j2me\WTK21\ WTK21\bin) bin) 1. Seleccionar New Project •Esto crea la siguiente estructura en c:\ c:\j2me\ j2me\WTK23\ WTK23\apps\ apps\HolaMundo •Hay que guardar en src el có código (.java) (HolaMundo.java (HolaMundo.java anterior) •En classes se guardará guardarán los (.class (.class)) •En bin se guardará guardarán los ficheros que hay que enviar al mó móvil (.jad (.jad y .jar .jar)) 10 5 Primer Programa: Generación 2. Ktoolbar 2. Compilar/Enlazar el có código con el botó botón Build 11 Primer Programa: Ejecución en emulador 2. Ktoolbar 3. Seleccionar el emulador en el desplegable Device 4. Ejecutar mediante el botó botón Run •Siguiendo estos pasos se pueden probar todos los programas que se realicen en el emulador. El siguiente paso será será pasar el programa al telé teléfono 12 6 Primer Programa: Preparación para el móvil 2. Ktoolbar 5. Editar datos del proyecto: (Project (Project Settings Required) Required) Nombre del proveedor Nombre del perfil. Por defecto es MIDP-2.0. Si el teléfono sólo es compatible con MIDP-1.0 es IMPRESCINDIBLE cambiarlo. (Por ejemplo, para el Nokia 3650) 6. Generar los fichero .jad .jad y .jar .jar:: Project Package Create Package 7. Los 2 ficheros a enviar al telé teléfono son: HolaMundo.jad HolaMundo.jar, HolaMundo.jar, guardados en c:\ c:\j2me\ j2me\WTK21\ WTK21\apps\ apps\HolaMundo\ HolaMundo\bin y 13 Primer Programa: Envío al teléfono bluetooth 3. Enviar los 2 ficheros al telé teléfono por bluetooth 1. Instalar dispositivo bluetooth en PC (Como ejemplo se explicará explicarán los pasos para el dispositivo Bluetooth 3Com) 2. Arrancar en el PC el Bluetooth Connection Manager (o programa similar que traiga el Bluetooth) Bluetooth) 3. Encender en el telé teléfono el bluetooth (Normalmente: Conexiones Bluetooth Activar) Activar) 4. Si todo va bien ☺ el PC verá verá el telé teléfono. Con el botó botón derecho sobre el icono seleccionar enviar archivo 5. Seleccionar los 2 ficheros HolaMundo.jad y HolaMundo.jar y enviarlos al telé teléfono 14 7 Primer Programa: Envío al teléfono bluetooth 3. Enviar los 2 ficheros al telé teléfono por bluetooth 6. En el telé teléfono se recibirá recibirán 2 mensajes. Uno por cada fichero. Aceptarlos. 7. En el menú menú mensajes Buzon de entrada del telé teléfono Abrir el fichero HolaMundo.jad. HolaMundo.jad. Aparecerá Aparecerá un mensaje como: ¿Instalar HolaMundo 1.0 suministrado por David Bueno? 8. Donde el nombre de la aplicació aplicación, la versió versión y el suministrador son los que se hayan seleccionado al editar los datos del proyecto 9. Si la instalació instalación es correcta, se podrá podrá ejecutar la aplicació aplicación en el menú ú del telé é fono Aplicaciones Hola Mundo men tel 15 Primer Programa: Envío al teléfono infrarrojos • NOKIA: – Si se pasan los ficheros directamente no pueden ejecutarse – Es necesario descargar el instalar la aplicació aplicación: NOKIA PC Suite – Se puede descargar de http://www.nokia.es http://www.nokia.es (seleccionando el teé teéfono y las aplicaciones para éste) 16 8 Primer Programa: Envío al teléfono serie/usb • SIEMENS: – Conectar el telé teléfono al cable serie o usb – Es necesario descargar el instalar la aplicació aplicación: Siemens Data Suite – Se puede descargar de http://www.siemens http://www.siemens--mobile.com (seleccionando el teé teéfono y las aplicaciones para éste) – Despué Después aparecerá aparecerá el telé teléfono como una carpeta y habrá habrá que copiar los ficheros .jad .jad y .jar .jar en la carpeta /java/jam /java/jam//HolaMundo 17 Primer Programa: Envío al teléfono por Web • Se necesita un servidor Web accesible desde el exterior – Dentro de la UMA es necesario tener abierto el puerto 80 en los servicios centrales de Informá Informática • En el servidor web habrá habrá que configurar 2 tipos MIME: text/vnd.sun.j2me.app-descriptor application/java-archive jad jar • En algunos mó móviles será será suficiente con descargar el .jar .jar • En lo que no sea suficiente, habrá habrá que descargar el .jad .jad pero modificando la linea URL de la siguiente forma: Antes MIDlet-Jar-URL: DBVMijar.jar Después MIDlet-Jar-URL: http://miweb.com/DBVMijar.jar 18 9 Interfaz de Usuario en J2ME • Hay 3 formas como un usuario puede interactuar con las aplicaciones – Command. Command.- Son acciones que se asocian a los botones que el usuario pulsa en el dispositivo para realizar alguna tarea – Screen. Screen.- Interfaz de alto nivel con alertas, formularios, cuadros de texto, radiobuttons, radiobuttons, checkbox, checkbox, listas similares a las utilizadas en HTML – Canvas. Canvas.- Interfaz de bajo nivel en la que el usuario trabaja a nivel de pixel, pixel, utilizadas para aplicaciones como juegos 19 IU: Display Class • La pantalla del dispositivo se asocia a un objeto Display • Un MIDlet está está asociado a un único objeto Display • En este objeto se pueden mostrar objetos de tipo Displayable (Screen y Canvas) Canvas) Screen List Form TextBox Displayable Canvas Javax.microedition.lcdui Alert GameCanvas Javax.microedition.lcdui.game 20 10 IU: Display Class • La referencia al objeto Display se obtiene normalmente en el constructor o en startApp() startApp() aunque se declare como atributo de la clase public class NombreClase… { Private Display pantalla; Public void startApp() { pantalla= Display.getDisplay(this); } } • Se pueden tener varios objetos de tipo Displayable pero sólo uno (aunque puede ser compuesto como un form) form) que se muestre en el Display. Display. Para asociarlo a la pantalla se utiliza setCurrent y se puede cambiar cuando se quiera TextBox cuadrotexto; cuadrotexto=new TextBox(“titulo”,”contenido”,30,TextField.ANY); pantalla.setCurrent(cuadrotexto); 21 IU: Command Class • Son acciones que se asocian a botones del dispositivo. Los posibles tipos son: Command.BACK Command.CANCEL Command.EXIT Command.HELP Command.ITEM Command.OK Command.SCREEN Command.STOP Ir a la pantalla anterior Cancelar la operación actual Terminar la aplicación Mostrar ayuda Asocia una acción a un elemento de la pantalla Aceptar Acción genérica para comandos específicos de la aplicación Para parar la operación actual • Aunque existan todos esos tipos en realidad no influye el • hecho de elegir uno u otro salvo en la posició posición en la que aparecerá aparecerá en pantalla Para crear una nuevo objeto Command 3 pará parámetros (texto, tipo, prioridad [0 es la má máxima]): Command miok; Miok=new Command(“Aceptar”, Command.OK,0) 22 11 IU: Command Class • Para añ añadir el Command al display Pantalla.addCommand(miok); • Para asociar una acció acción a un Command hay que implementar la interfaz CommandListener public class MiClase extends MIDlet implements CommandListener { … public void commandAction(Command c, Displayable displayable) { if (c == quitar) { destroyApp(false); notifyDestroyed(); } else if (c==miok) { … } } • Como ejemplo un programa con 2 cuadros de texto, uno de los cuales es una ayuda, en total hay 3 command asociados a las operaciones de salir de la aplicació aplicación, ayuda y volver (salir de la ayuda) 23 Programa: DBVCommands 24 12 Programa: DBVCommands import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class DBVCommands extends MIDlet implements CommandListener { private Display pantalla ; private TextBox tintro,tayuda ; private Command salir, ayuda, volver; public DBVCommands () { pantalla = Display.getDisplay(this); // Preparando la pantalla principal tintro = new TextBox("Mis Comandos", "Puedes salir o elegir ayuda", 40, 0); salir = new Command("Salir", Command.EXIT, 0); ayuda = new Command("Ayuda",Command.HELP,1); tintro .addCommand(salir); tintro.addCommand(ayuda); tintro.setCommandListener(this); // Preparando pantalla de ayuda tayuda = new TextBox("Ayuda", "Para salir debe regresar :-)", 40, 0); volver=new Command("Volver",Command.BACK,1); tayuda.addCommand(volver); tayuda.setCommandListener(this); } public void startApp() { pantalla .setCurrent(tintro); } public void pauseApp() { } 25 Programa: DBVCommands public void destroyApp(boolean unconditional) { } public void commandAction(Command c, Displayable displayable) { if (c == salir) { destroyApp(false); notifyDestroyed(); } else if (c==ayuda) { pantalla.setCurrent(tayuda); } else if (c==volver) { pantalla.setCurrent(tintro); } } } 26 13 IU: Screen:Alertas • Las alertas son cuadros de dialogo que avisan al usuario • de algú algún problema, confirman alguna acció acción del usuario, o muestran un recordatorio en la pantalla Hay varios tipos predefinidos AlertType.ALARM, AlertType.CONFIRMATION, AlertType.ERROR, AlertType.INFO, AlertType.WARNING • El constructor de la alerta tiene 4 pará parámetros: tí título, texto, imagen, tipo Alert mialerta; mialerta=new Alert(“Titulo”,”Texto de la alerta”, null, AlertType.CONFIRMATION); • Las alertas pueden ser: – Modales. Permanecen hasta que el usuario realce alguna acció acción mialerta.setTimeout(Alert.FOREVER); – Temporales. Se cierran pasado algunos segundos mialerta.setTimeout(5000); // Espera 5 segundos 27 IU: Screen:Alertas • Por defecto aparecen acciones ‘Command’ Command’ asociadas a • las alertas, aunque pueden definirse nuevas con addCommand para personalizar los resultados Para mostrar la alerta se cambia el display como siempre pantalla.setCurrent(mialerta); • Cuando la alerta se cierra se vuelve a la pantalla anterior • Si se quiere que al cerrar la alerta se pase a otra pantalla se utiliza un segundo pará parámetro de setCurrent pantalla.setCurrent(mialerta,siguientepantalla); 28 14 IU: Screen:Formularios • Los formularios permiten recoger datos del usuario • En un formulario se pueden incluir derivados de la clase Item – ChoiceGroup, ChoiceGroup, CustomItem, CustomItem, DateField, DateField, Gauge, ImageItem, ImageItem, Spacer, StringItem, StringItem, TextField Form miform; miform=new Form(“Título"); • Se añ añaden elementos al formulario con Append TextField usuario; usuario=new TextField("usuario","",10,TextField.ANY); miform.append(usuario); • A continuació continuación un ejemplo de un formulario que pide un nombre de usuario y una contraseñ contraseña. Utiliza una alerta para mostrar el nombre leí leído 29 Programa: DBVAcceso 30 15 Programa: DBVAcceso import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class DBVAcceso extends MIDlet implements CommandListener { private Display pantalla ; private Form flogin ; private Alert confirmacion; private TextField usuario; private TextField passwd; private Command salir,aceptar; private String cad; public DBVAcceso () { pantalla = Display.getDisplay(this); // Preparando la pantalla principal flogin=new Form("Control Acceso"); usuario=new TextField("usuario","",10,TextField.ANY); passwd=new TextField("contraseña","",10,TextField.ANY|TextField.PASSWORD); aceptar = new Command("Enviar", Command.OK, 0); salir = new Command("Salir", Command.EXIT, 0); flogin.append(usuario); flogin.append(passwd); flogin.addCommand(aceptar); flogin.addCommand(salir); flogin.setCommandListener(this); // Alerta de confirmacion de datos confirmacion= new Alert ("Confirmación","", null, AlertType.CONFIRMATION); confirmacion.setTimeout(Alert.FOREVER); } 31 Programa: DBVAcceso } public void startApp() { pantalla .setCurrent(flogin); } public void pauseApp() { } public void destroyApp(boolean unconditional) { } public void commandAction(Command c, Displayable displayable) { if (c == salir) { destroyApp(false); notifyDestroyed(); } else if (c==aceptar) { cad="Bienvenido al sistema " + this.usuario.getString(); confirmacion.setString(cad); pantalla.setCurrent(confirmacion); } } 32 16 IU: Screen: Formularios: Imágenes • Para trabajar con imá imágenes hay que hacerlo en formato • • • .png El programa Paint de Windows guarda cualquier imagen en este formato Hay que guardar las imá imágenes en la carpeta res que genera J2ME Wireless Toolkit (WT) al crear un proyecto La referencia a la imagen se hace con esta carpeta como raiz /. – Es decir, una imagen dentro de esta carpeta se referencia “/imagen.png” imagen.png” – Una imagen en res/imagenes se referencia res/imagenes “/imá /imágenes/imagen.png genes/imagen.png”” • Las imá imágenes se añ añadirá adirán al fichero .jar .jar (lo hace WT) 33 IU: Screen: Formularios: Imágenes • Una objeto imagen se declara y crea: Image mimagen; mimagen=Image.createImage("/nombre.png"); catch por si no • Es importante crear la imagen en un try.. try..catch se encuentra try { // Ejemplo de imagen en formulario mimagen=Image.createImage("/nombre.png"); } catch (java.io.IOException error) { Alert alerta=new Alert("Error","No se pueda cargar la imagen",null,AlertType.ERROR); alerta.setTimeout(5000); pantalla.setCurrent(alerta); } 34 17 IU: Screen: Formularios: Imágenes • Para usarla en una alerta se pondrá pondrá como 3er pará parámetro en el constructor de la alerta intro=new Alert(“Titulo", “Texto...",mimagen,AlertType.INFO); • Para usarla en un formulario hay que crear un ImageItem imagenit=new ImageItem(null,mimagen,ImageItem.LAYOUT_CENTER,"Davilin"); principal.append(imagenit); • En el siguiente ejemplo se carga una imagen en una alerta que dura 3 segundos y despué después se muestra un formulario con otra imagen 35 Programa: DBVImagenes 3 segs. 36 18 Programa: DBVImagenes (1/3) import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class DBVImagenes extends MIDlet implements CommandListener { private Alert intro; private Display pantalla ; private Form principal ; private Command salir; private Image imagen2,imagen1; private ImageItem imagenit; private String cad; public DBVImagenes() { pantalla = Display.getDisplay(this); // Preparando la pantalla principal principal=new Form("Imagenes"); salir = new Command("Salir", Command.EXIT, 0); principal.addCommand(salir); principal.setCommandListener(this); ... 37 Programa: DBVImagenes (2/3) // Creación de la imagen try { // Ejemplo de imagen en formulario imagen2=Image.createImage("/david2.png"); imagenit=new ImageItem(null,imagen2,ImageItem.LAYOUT_CENTER,"Davilin"); principal.append(imagenit); } catch (java.io.IOException error) { Alert alerta=new Alert("Error","No se pueda cargar la imagen",null,AlertType.ERROR); alerta.setTimeout(5000); pantalla.setCurrent(alerta); } // Preparacion de la pantalla de bienvenida try { // Ejemplo de imagen en alerta imagen1=Image.createImage("/david1.png"); intro=new Alert("Bienvenido", "Bienvenido a mi programa\nCargando...",imagen1,AlertType.INFO); intro.setTimeout(3000); } catch (java.io.IOException error) { Alert alerta=new Alert("Error","No se pueda cargar la imagen",null,AlertType.ERROR); alerta.setTimeout(5000); pantalla.setCurrent(alerta); } } 38 19 Programa: DBVImagenes (3/3) public void startApp() { pantalla.setCurrent(intro,principal); } public void pauseApp() { } public void destroyApp(boolean unconditional) { } public void commandAction(Command c, Displayable displayable) { if (c == salir) { destroyApp(false); notifyDestroyed(); } } } 39 IU: Screen:Formularios:ChoiceGroup • Permite crear botones de tipo radio y checkbox. checkbox. • Hay 3 tipos Choice.EXCLUSIVE Choice.MULTIPLE Choice.IMPLICIT Botones Radio Botones Checkbox Menú • Para crear las selecciones: miradio=new ChoiceGroup("Mi Radio",Choice.EXCLUSIVE); miradio.append("opcion1",null); // 2º parámetro es una imagen miradio.append("opcion2",null); principal.append(miradio); • Para procesar elemento seleccionado en radio: i=miradio.getSelectedIndex(); mensaje=new StringItem("",miradio.getString(i)+"\n"); • Para procesar elementos seleccionados en check: for (i=0;i<micheck.size();i++) { if (micheck.isSelected(i)){ mensaje=new StringItem("",micheck.getString(i)+"\n"); principal.append(mensaje); } } 40 20 Programa: DBVChoice 41 Programa: DBVChoice (1/3) import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class DBVChoice extends MIDlet implements CommandListener { private ChoiceGroup miradio, micheck, milista; private Command procesaradio, procesacheck, salir; private Display pantalla ; private Form principal ; private String cad; public DBVChoice() { pantalla = Display.getDisplay(this); // Preparando la pantalla principal principal=new Form("Imagenes"); salir = new Command("Salir", Command.EXIT, 0); procesaradio = new Command("Ver Radio", Command.SCREEN, 0); procesacheck = new Command("Ver Check", Command.SCREEN, 0); principal.addCommand(salir); principal.addCommand(procesaradio); principal.addCommand(procesacheck); principal.setCommandListener(this); // Radio miradio=new ChoiceGroup("Mi Radio",Choice.EXCLUSIVE); miradio.append("opcion1",null); miradio.append("opcion2",null); principal.append(miradio); 42 21 Programa: DBVChoice (2/3) // Checkbox micheck=new ChoiceGroup("Mi Check",Choice.MULTIPLE); micheck.append("check1",null); micheck.append("check2",null); principal.append(micheck); } public void startApp() { pantalla.setCurrent(principal); } public void pauseApp() { } public void destroyApp(boolean unconditional) { } public void commandAction(Command c, Displayable displayable) { int i; StringItem mensaje; if (c == salir) { destroyApp(false); notifyDestroyed(); 43 Programa: DBVChoice (3/3) } } } else if (c==procesaradio) { i=miradio.getSelectedIndex(); mensaje=new StringItem("",miradio.getString(i)+"\n"); principal.append(mensaje); } else if (c==procesacheck){ for (i=0;i<micheck.size();i++) { if (micheck.isSelected(i)){ mensaje=new StringItem("",micheck.getString(i)+"\n"); principal.append(mensaje); } } } 44 22 IU: Screen:Ticker • • • Permite mostrar un scroll horizontal. Se puede asociar a cualquier clase derivada de Screen Para crearlo y asociarlo, por ejemplo a un formulario cad=new String("Soy un texto que se desplaza por la pantalla"); miticker=new Ticker(cad); principal.setTicker(miticker); • Para modificar su valor miticker.setString(“Texto nuevo del ticker"); • A continuació continuación un ejemplo que tiene ticker y un radio y muestra en el ticker informació información adicional sobre el significado del radio 45 Programa: DBVTicker 46 23 Programa: DBVTicker (1/2) import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class DBVTicker extends MIDlet implements CommandListener, ItemStateListener { private ChoiceGroup miradio; private Command salir; private Display pantalla ; private Form principal ; private String cad; private Ticker miticker; public DBVTicker() { pantalla = Display.getDisplay(this); // Preparando el ticker cad=new String("Soy un texto que se desplaza por la pantalla"); miticker=new Ticker(cad); // Preparando la pantalla principal principal=new Form("Formulario"); principal.setTicker(miticker); // Se asocia el ticker al formulario salir = new Command("Salir", Command.EXIT, 0); principal.addCommand(salir); principal.setCommandListener(this); // Radio miradio=new ChoiceGroup("Mi Radio",Choice.EXCLUSIVE); miradio.append("opcion1",null); miradio.append("opcion2",null); principal.append(miradio); principal.setItemStateListener(this); // Listener que se llama si cambia el radio } 47 Programa: DBVTicker (2/2) public void startApp() { pantalla.setCurrent(principal); } public void pauseApp() { } public void destroyApp(boolean unconditional) { } public void commandAction(Command c, Displayable displayable) { if (c == salir) { destroyApp(false); notifyDestroyed(); } } public void itemStateChanged(Item item) { // Listener de RadioButton int i; if (item==miradio) { i=miradio.getSelectedIndex(); if (i==0) { // Dependiendo del radio elegido se cambia en texto del ticker miticker.setString("La Opcion primera es muy buena"); } else { miticker.setString("Elegir la 2 te dara buena suerte"); } } else { miticker.setString("no se ha elegido miradio"); } } } 48 24 IU: Canvas y GameCanvas • Canvas es la clase bá básica para trabajar con la pantalla a bajo nivel: • • • • Dibujar pixels, pixels, lineas, lineas, figuras, etc. GameCanvas mejora Canvas incluyendo carga de Imá Imágenes, Sprites, Sprites, TiledLayers y un mejor control de los botones del telé teléfono Estas clases son la utilizada para hacer los juegos Java que han hecho tan populares los mó móviles La programació programación de juegos se convierte en algo mucho má más sencillo que en cualquier otra plataforma El programador no tiene que preocuparse de problemas clá clásicos como: – Refresco de pantalla, doble buffering, buffering, colisiones, gestió gestión de capas o mapas • Las clases anteriores se encargan de todo el ‘trabajo sucio’ sucio’ 49 IU: GameCanvas • Las aplicaciones no suelen utilizar directamente • • GameCanvas, GameCanvas, sino que heredan de esta clase Como mínimo hay dos clases: clases: Una para el midlet principal y otra para la clase que hereda de GameCanvas El juego suele ejecutarse en una hebra independiente con el siguiente bucle principal // Dentro de la clase que hereda de GameCanvas public void run() { Graphics g = getGraphics(); while (jugando == true) { colision(); // Controla colisiones leeTeclado(); // Lee acciones del usuario dibujaPantalla(g); // Dibuja la escena actual try { Thread.sleep(retraso); } // Retraso entre iteraciones catch (InterruptedException ie) {} } } 50 25 IU: GameCanvas: Lectura de teclado • Para leer de teclado es necesario leer el estado de las teclas con: int keyStates = getKeyStates(); • Hay varias constantes en GameCanvas para cada tecla: tecla: DOWN_PRESSED, LEFT_PRESSED, RIGHT_PRESSED, UP_PRESSED, FIRE_PRESSED, GAME_A_PRESSED, GAME_B_PRESSED, GAME_C_PRESSED, GAME_D_PRESSED // Metodo para gestionar el teclado private void leeTeclado() { int keyStates = getKeyStates(); // Izquierda if ((keyStates & LEFT_PRESSED) != 0) { // Acciones cuando pulse izquierda } // Derecha if ((keyStates & RIGHT_PRESSED) !=0 ) { // Acciones cuando pulse izquierda } … } 51 IU: GameCanvas: Dibujar en la pantalla • Suele haber un mé método en el que se realiza todo el • refresco de pantalla: imá imágenes, lineas, lineas, sprites, sprites, fondos… fondos… Para hacer cualquier dibujo se utiliza un objeto Graphics que puede obtenerse dentro de GameCanvas con Graphics g=getGraphics(); • Algunos de las acciones posibles: g.setColor(0xbbecf3); // Selecciona el color de lo siguiente que se dibuje g.fillRect(0, 0, ancho, alto); // Dibuja un rectangulo relleno con ese color g.setColor(0x0000ff); g.drawLine(0,100,200,0); // Dibuja una línea // Para escribir un texto, se indican las coordenadas de la pantalla y la // alineación del texto en la pantalla g.drawString(“Hola", anchop, altop, Graphics.RIGHT | Graphics.BOTTOM); 52 26 IU: GameCanvas: Sprites • • • La clase Sprite maneja los personajes del juego Carga del Sprite en un fichero con 5 posiciones Se indica el tamañ tamaño de un elemento 24 Sprite babosin; Image imagen; imagen = Image.createImage("/babosin.png"); babosin = new Sprite (image,16,24); 0 1 16 2 3 4 24 80 Fichero: babosin.png 53 IU: GameCanvas: Sprites • Para mostrar el sprite hay que seleccionar la posició posición de • • pantalla Tambié También cual de los frames se va a mostrar Y có cómo quiere mostrarse: Su posició posición original, invertida o girada en x grados Sprite.TRANS_NONE, Sprite.TRANS_MIRROR, Sprite.TRANS_ROT90, Sprite.TRANS_ROT180, Sprite.TRANS_ROT270, Sprite.TRANS_MIRROR_ROT90, Sprite.TRANS_MIRROR_ROT180, Sprite.TRANS_MIRROR_ROT270 • Para dibujar se necesita un Graphics g que puede obtenerse con: Graphics g = getGraphics(); getGraphics(); babosin.setFrame(3); // Selecciona el frame (4ª imagen de las 5) babosin.setTransform(Sprite.TRANS_NONE); // Muestra la imagen en su posición original babosin.setPosition(100,100); // Colocará en la pantalla el sprite en la posición (100,100) babosin.paint(g); // Dibuja el Sprite. 54 27 Programa: DBVGameCanvas • Se va a mostrar en un programa los elementos • • • • relacionados con GameCanvas vistos hasta este punto Se dibujaran algunas lílíneas y rectá rectángulos y se moverá moverá un Sprite por la pantalla Se va a utilizar una clase para el midlet (DBVGameCanvas), DBVGameCanvas), que será será muy similar en todos los programas que usen GameCanvas Por otro lado, se va a utilizar una clase que hereda de GameCanvas (BabosinCanvas) BabosinCanvas) sobre la que recae la mayor parte de la acció acción Esta última tiene una hebra que es iniciada por la clase principal 55 Programa: DBVGameCanvas Cursores 56 28 Programa: DBVGameCanvas (1/2) import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class DBVGameCanvas extends MIDlet implements CommandListener { private Display display; private BabosinCanvas babosinCanvas; private Command salir; public void startApp() { try { display = Display.getDisplay(this); babosinCanvas= new BabosinCanvas(); babosinCanvas.start(); // Inicia la hebra del canvas salir = new Command("Salir", Command.EXIT, 0); // Boton para salir babosinCanvas.addCommand(salir); babosinCanvas.setCommandListener(this); display.setCurrent(babosinCanvas); } catch (Exception ex) { System.out.println(ex); } } public void pauseApp() { } 57 Programa: DBVGameCanvas (2/2) public void destroyApp(boolean unconditional) { if (babosinCanvas != null) { babosinCanvas.stop(); // Para la hebra del canvas } } } public void commandAction(Command c, Displayable s) { if (c.getCommandType() == Command.EXIT) { System.gc(); // Llama al recolector de basura destroyApp(true); notifyDestroyed(); } } 58 29 Programa: DBVGameCanvas-BabosinCanvas (1/4) import javax.microedition.lcdui.*; import javax.microedition.lcdui.game.*; public class BabosinCanvas extends GameCanvas implements Runnable { private boolean jugando; // Indica que la partida esta en curso private long retraso; // Retraso entre cada ciclo private int babosinX, babosinY; // Coordenadas del personaje private int ancho; // Ancho de la pantalla private int alto; // Alto de la pantalla private int posbabosin; // babosin actual del personaje (0-4) private Sprite babosin; // Sprites del personaje // Constructor public BabosinCanvas() throws Exception { super(true); ancho = getWidth(); alto = getHeight(); babosinX = ancho / 2; babosinY = alto / 2; retraso = 20; // Carga el personaje principal Image image = Image.createImage("/babosin.png"); babosin = new Sprite (image,16,24); } 59 Programa: DBVGameCanvas-BabosinCanvas (2/4) // Crea y arranca la hebra del juego public void start() { jugando = true; Thread t = new Thread(this); t.start(); } public void stop() { jugando = false; } // Bucle principal del juego public void run() { Graphics g = getGraphics(); while (jugando == true) { leeTeclado(); // Lee acciones del usuario dibujaPantalla(g); // Dibuja la escena actual try { Thread.sleep(retraso); } catch (InterruptedException ie) {} } } 60 30 Programa: DBVGameCanvas-BabosinCanvas (3/4) // Metodo para gestionar el teclado private void leeTeclado() { int keyStates = getKeyStates(); if ((keyStates & LEFT_PRESSED) != 0) {// Izquierda babosinX = Math.max(0, babosinX - 1); posbabosin=(posbabosin+1)%5; babosin.setFrame(posbabosin); babosin.setTransform(Sprite.TRANS_NONE); } if ((keyStates & RIGHT_PRESSED) !=0 ) {// Derecha babosinX = Math.min(ancho-16, babosinX + 1); posbabosin=(posbabosin+1)%5; babosin.setFrame(posbabosin); babosin.setTransform(Sprite.TRANS_MIRROR); } if ((keyStates & UP_PRESSED) != 0) {// Arriba babosinY = Math.max(0, babosinY - 1); babosin.setFrame(0); } if ((keyStates & DOWN_PRESSED) !=0) {// Abajo babosinY = Math.min(alto-24,babosinY + 1); babosin.setFrame(4); } } 61 Programa: DBVGameCanvas-BabosinCanvas (4/4) // Dibuja la pantalla private void dibujaPantalla(Graphics g) { // lineas montañas g.setColor(0xbbecf3); g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(0x0000ff); g.drawLine(0,getHeight()/2,getWidth()/2,0); g.drawLine(getWidth()/2,0,getWidth(),getHeight()/2); g.setColor(0xf5d58d); g.drawLine(0,(getHeight()/4)*3,getWidth(),(getHeight()/4)*3); // dibuja el personaje babosin.setPosition(babosinX,babosinY); babosin.paint(g); } flushGraphics(); // actualiza la pantalla } 62 31 IU: GameCanvas: TiledLayer Deco.png + = Image tileImages = Image.createImage("/deco.png"); // Carga la imagen con los elementos de la pantalla TiledLayer decorado = new TiledLayer(40,24,tileImages,8,8); // reserva espacio para la capa del decorado // Selecciona los 40x24 elementos del decorado según su posición en deco.png // 0 indica que no hay nada en esa posición int[] map= {42,41,42,41,1,2,42,41,1,2,42,41,1,2,42,41,1,2,42,41,1,2,42,41,1,2,42,41,1,2,42,41,1,2,42,41,42,41,1,2, 264,264,264,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,264, 264,264,264,0,0,248,249,250,251,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,264, … } // Guarda los elementos del mapa en la posición de pantalla que corresponda for (int i=0; i < mapa.length; i++) { int columna = i % 40; // Se divide por el número de elementos de una fila=numero de columnas int fila = i/40; decorado.setCell(columna,fila,mapa[i]); // Actualiza 1 a 1 todas las celdas } 63 IU: GameCanvas: TiledLayer • Para mover un TiledLayer se utiliza setPosition decorado.setPosition(decox,decoy); • Para dibujarlo se puede utilizar el mé método paint de TiledLayer o un LayerManager que gestiona todas las capas private LayerManager controlcapas; // Declaración del LayerManager … controlcapas=new LayerManager(); // Crea el control de capas … controlcapas.append(decorado); // Añade cada capa que se quiera controlar … Controlcapas.paint(g,0,0); // Dibuja todas las capas controladas a partir de (x,y) en la pantalla 64 32 IU: GameCanvas: TiledLayer+Sprite+Image + + = // Dibuja la imagen de fondo g.drawImage(fondo,decox/4,decoy/4,0); decorado.setPosition(decox,decoy); // Actualiza el decorado controlcapas.paint(g,0,0); // Pinta el decorado // Actualiza el personaje babosin.setPosition(babosinX,babosinY); babosin.paint(g); // pinta el personaje 65 IU: GameCanvas: Scroll Fondos TRUCO • En muchos juegos de plataformas los escenarios pueden estar a diferentes distancias • Los escenarios má más lejanos deben moverse a menor velocidad que los cercanos para producir un movimiento real • Es fá fácil conseguir ese efecto dá dándole al primer plano unas coordenadas (x,y (x,y)) y a cada plano má más lejano se le asignará /distancia) asignará un (x/distancia,y (x/distancia,y/distancia) • En el ejemplo anterior el fondo se desplaza 4 veces má más lentamente que el decorado principal // Dibuja la imagen de fondo g.drawImage(fondo,decox/4,decoy/4,0); 66 33 IU: GameCanvas: Colisiones • Se puede mirar en cada momento si un Sprite choca con • otro Sprite, Imagen o TiledLayer Las colisiones se pueden hacer a nivel de pixel (pixelLevel=true) pixelLevel=true) o aproximando la imagen a un rectangulo (pixelLevel=false) pixelLevel=false) // Mira si el sprite babosin choca con la imagen situada en x,y choca=babosin.collidesWith(imagen,x,y,pixelLevel); // Mira si el sprite babosin choca con la otro Sprite choca=babosin.collidesWith(enemigo,pixelLevel); // Mira si el sprite babosin choca con un tiledLayer choca=babosin.collidesWith(escenario,pixelLevel); 67 Programa: DBVBabosin • En el siguiente programa se combina todo lo relacionado • • • • con GameCanvas: GameCanvas: Sprites, Sprites, Imá Imágenes, TiledLayer, TiledLayer, LayerManager, LayerManager, Control de Teclado… Teclado… Con esto se podrí podría hacer prá prácticamente cualquier juego en 2D Se va a utilizar para el midlet la misma clase que en (DBVGameCanvas) DBVGameCanvas) Por otro lado, se va a utilizar una clase que hereda de GameCanvas (BabosinCanvas) BabosinCanvas) en la que se desarrolla el juego El programa carga una imagen de fondo, un tiledLayer y un sprite y cuando se mueven los cursores, el sprite se desplaza por el fondo que tambié también se desplaza, detectando posibles colisiones 68 34 Programa: DBVBabosin Con los cursores se desplaza el personaje, el escenario y el fondo 69 Programa: DBVBabosin (1/6) import javax.microedition.lcdui.*; import javax.microedition.lcdui.game.*; public class BabosinCanvas extends GameCanvas implements Runnable { private boolean jugando; // Indica que la partida esta en curso private long retraso; // Retraso entre cada ciclo private int babosinX, babosinY; // Coordenadas del personaje private int antx,anty; // Coord. anterior del personaje (para colisiones) private int decox,decoy; // Coordenadas del decorado private int antdecox,antdecoy; private int ancho; // Ancho de la pantalla private int alto; // Alto de la pantalla private int posbabosin; // sprite actual del personaje (0-4) private LayerManager controlcapas; private int ultmov; // Ultimo movimiento: 0 nada 1 izq 2 dch 3 arr 4 aba private TiledLayer decorado; // Escenario private boolean cayendo=true; private Image fondo; private Sprite babosin; // Sprite del personaje principal 70 35 Programa: DBVBabosin (2/6) // Constructor public BabosinCanvas() throws Exception { // Llama al constructor de GameCanvas, al ser true, desabilita // el manejo estandar del teclado super(true); // Lee las coordedanas del dispositivo ancho = getWidth(); alto = getHeight(); // Coloca el muñeco en una posición que no choque babosinX = 100; babosinY = 30; // Inicializa las coordenadas del decorado a la esquina sup,izq de la pantalla decox=0; decoy=0; // inicializa coordenadas anteriores (para deshacer movimientos si choca) antdecox=decox; antdecoy=decoy; antx=babosinX; anty=babosinY; ultmov=0; retraso = 5; decorado=iniDecorado(); // Carga e inicializa el decorado fondo= Image.createImage("/fondo2.png"); // Carga imagen de fondo controlcapas=new LayerManager(); // Crea el control de capas controlcapas.append(decorado); Image image = Image.createImage("/babosin.png"); // Carga el personaje principal babosin = new Sprite (image,16,24); } // Fin del constructor 71 Programa: DBVBabosin (3/6) // Controla las colisiones private void colision() { if (!babosin.collidesWith(decorado, false)) { // Si babosin colisiona con el decorado se vuelve a su estado anterior decox=antdecox;decoy=antdecoy; babosinX=antx; babosinY=anty; } } // Dibuja todo private void dibujaPantalla(Graphics g) { g.setColor(0xffffff); // Dibuja un fondo blanco g.fillRect(0, 0, getWidth(), getHeight()); g.drawImage(fondo,decox/4,decoy/4,0); // Dibuja la imagen de fondo decorado.setPosition(decox,decoy); // Actualiza el decorado controlcapas.paint(g,0,0); // Pinta el decorado babosin.setPosition(babosinX,babosinY); // Actualiza el personaje babosin.paint(g); // pinta el personaje g.setColor(0x000000); // Pone el color a negro if (babosin.collidesWith(decorado, true)) {// Texto que indica si choca o no g.drawString("CHOCA", ancho, alto, Graphics.RIGHT | Graphics.BOTTOM); } else { g.drawString("nochoca", ancho, alto, Graphics.RIGHT | Graphics.BOTTOM); } // refresca los graficos flushGraphics(); } 72 36 Programa: DBVBabosin (4/6) // Inicializa el decorado que es un TiledLayer private TiledLayer iniDecorado() throws Exception { // Carga la imagen con los elementos de la pantalla Image tileImages = Image.createImage("/deco.png"); // reserva espacio para la capa del decorado TiledLayer tiledLayer = new TiledLayer(40,24,tileImages,8,8); // Selecciona los 40x24 elementos del decorado segun su posicion den deco.png // 0 indica que no hay nada en esa posicion int[] mapa= { 42,41,42,41,1,2,42,41,1,2,42,41,1,2,42,41,1,2,42,41,1,2,42,41,1,2,42,41,1,2,42,41,1,2,42,41,42,41,1,2, 264,264,264,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,264, 264,264,264,0,0,248,249,250,251,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,264, … }; // Guarda los elementos del mapa en la posicion de pantalla que corresponda for (int i=0; i < mapa.length; i++) { int columna = i % 40; int fila = i/40; tiledLayer.setCell(columna,fila,mapa[i]); // Actualiza 1 a 1 todas las celdas } } return tiledLayer; 73 Programa: DBVBabosin (5/6) // Maneja la entrada de teclado private void leeTeclado() { int estadoTeclado; // se guardan los valores para restaurar si choca antdecox=decox; antdecoy=decoy; antx=babosinX; anty=babosinY; estadoTeclado = getKeyStates(); // Lee el estado del teclado babosin.setFrame(0); // selecciona el 1er sprite de babosin // Mira si se ha movido a la Izquierda if ( (estadoTeclado & LEFT_PRESSED) != 0) { //mueve a la izquierda el decorado y babosin decox+=1; babosinX = Math.max(0, babosinX - 1); // Cambia el sprite de babosin tambien posible con babosin.nextFrame() posbabosin=(posbabosin+1)%5; babosin.setFrame(posbabosin); babosin.setTransform(Sprite.TRANS_NONE); ultmov=1; } // Derecha if ( (estadoTeclado & RIGHT_PRESSED) !=0 ) { // Igual que izquierda salvo // babosin.setTransform(Sprite.TRANS_MIRROR); } } 74 37 Programa: DBVBabosin (6/6) } // Bucle principal del juego public void run() { Graphics g = getGraphics(); while (jugando == true) { colision(); // Controla colisiones leeTeclado(); // Lee acciones del usuario dibujaPantalla(g); // Dibuja la escena actual try { Thread.sleep(retraso); } catch (InterruptedException ie) {} } } // Crea y arranca la hebra del juego public void start() { Thread t; jugando = true; t= new Thread(this); t.start(); } // para la ejecucion public void stop() { jugando = false; } 75 RMS (Record Management System) • • • • • El almacenamiento en J2ME se realiza a travé través de RMS No existen ficheros RMS permite guardar registros numerados El contenido de un registro es un array de bytes La gestió gestión de todo lo que haya en el registro debe hacerla el usuario. – Por ejemplo, si se guarda una cadena y un nú número será será el usuario el encargado de escribirlo y leerlo correctamente convirtiendo los datos a arrays de bytes • La clase RecordStore es la encargada de la manipulació manipulación de los registros Id registro contenido 1 valor1 2 76 38 RMS: RecordStore • RecordStore permite realizar operaciones con los • • registros del tipo: Crear, Insertar, Borrar, ... Todas las operaciones que se realizan con RecordStore necesitan estar en un try.. catch try..catch Para crear o abrir: RecordStore recordstore; // Declaración try { // Abre un registro llamado ‘contador’ y lo crea si es necesario (2º parametro a true) recordstore = RecordStore.openRecordStore("contador", true ); } catch (Exception error) { alert = new Alert("Error Creando", error.toString(), null, AlertType.WARNING); alert.setTimeout(Alert.FOREVER); display.setCurrent(alert); } • En adelante se pondrá pondrán las sentencias sin el try aunque debe estar 77 RMS: RecordStore: Escritura Simple • El caso má más simple de registros es aquel en el que se • • escribe un único tipo de datos Para escribir un dato, por ejemplo, un String hay que convertirlo a un array de bytes Despué Después se añ añade con addRecord: addRecord: String outputData = "1"; // Cadena que se va a escribir byte[] byteOutputData = outputData.getBytes(); // Conversión de la cadena // Se añade la array de bytes al registro desde el inicio (0) y con longitud (length) recordstore.addRecord(byteOutputData, 0, byteOutputData.length); 78 39 RMS: RecordStore: Lectura Simple • Para leer se utiliza getRecord que necesita como mí mínimo • • el nú número de registro a leer aunque tiene varias versiones Puede ser necesario saber cuantos registros hay en el RecordStore, RecordStore, puede consultarse con getNumRecords Por ejemplo para leer todas las cadenas almacenadas en el RecordStore: RecordStore: // Crea un array de bytes para leer los datos byte[] byteInputData = new byte[30]; int length = 0; // Almacenará el número de bytes leidos String cadena=new String(""); // Bucle para los todos los registros que haya (getNumRecords) for (int x = 1; x <= recordstore.getNumRecords(); x++) { // Lee el registro de la posición x en el array de bytes al inicio (0) length = recordstore.getRecord(x, byteInputData, 0); cadena=cadena + " "+ new String(byteInputData); } 79 RMS: RecordStore: Borrado y Cierre • Para cerrar el RecordStore y dejar de usarlo recordstore.closeRecordStore(); • Para borrar todo el RecordStore (mé (método static) static) RecordStore.deleteRecordStore("contador"); • Para borrar un registro concreto, se indica su posició posición recordstore.deleteRecord(3); 80 40 RMS: RecordStore: Emulador • En el Wireless Toolkit puede accederse a los RecordStore que se almacenan en: C:\WTK22\appdb\ • Para cada telé teléfono emulado hay una carpeta con su nombre, por ejemplo para el DefaultColorPhone C:\WTK22\appdb\DefaultColorPhone • Cada recordset es precedido por run_by_class_storage_ run_by_class_storage_ • Es decir si tenemos un RecordStore llamado contador, que se ha usado en el telé teléfono por defecto se encontrará encontrará en: C:\WTK22\appdb\DefaultColorPhone\run_by_class_storage_contador.db • Este fichero binario puede visualizarse con un editor hexadecimal o borrarse si la aplicació aplicación empieza a dar problemillas ;;-) 81 Programa: DBVRMSSimple • Para probar los elementos bá básicos de registros se va a • • • mostrar el programa DBVRMSSimple Este programa crea un RecordStore llamado contador en el que la primera vez escribe la cadena ‘1’ Y posteriormente escribe ‘2’, ’3’, ... Dispone de un menú menú que permite Crear, Insertar, Ver, Borrar y Salir 82 41 Programa: DBVRMSSimple Crear Incrementar Incrementar Ver Salir 83 Programa: DBVRMSimple (1/5) import javax.microedition.rms.*; import javax.microedition.midlet.*; import javax.microedition.lcdui.*; import java.io.*; import java.lang.Integer; public class DBVRMSSimple extends MIDlet implements CommandListener { private Display display; private Alert alert; private Form form; private Command salir,crear,ver,incrementar,borrar; private RecordStore recordstore = null; public DBVRMSSimple() { display = Display.getDisplay(this); salir = new Command("Salir", Command.SCREEN,4); // Definición del menú incrementar = new Command("Incrementar", Command.SCREEN, 2); crear=new Command("Crear", Command.SCREEN, 1); ver=new Command("Ver",Command.SCREEN,3); borrar=new Command("Borrar",Command.SCREEN,3); form = new Form("Record"); form.addCommand(salir); form.addCommand(crear); form.addCommand(incrementar); form.addCommand(ver); form.addCommand(borrar); form.setCommandListener(this); } 84 42 Programa: DBVRMSimple (2/5) public void startApp() { display.setCurrent(form); } public void pauseApp() { } public void destroyApp( boolean unconditional ) { } public void commandAction(Command command, Displayable displayable) { // Para cada opción del menú se realiza una acción sobre el RecordSet if (command == salir) {// Para cerrar el RecordStore try { recordstore.closeRecordStore(); } catch (Exception error) { alert = new Alert("Error Cerrando", error.toString(), null, AlertType.WARNING); alert.setTimeout(Alert.FOREVER); display.setCurrent(alert); } destroyApp(true); notifyDestroyed(); } else if (command==crear) {// Para crear el RecordStore e insertar el valor ‘1’ try { recordstore = RecordStore.openRecordStore("contador", true ); } catch (Exception error) { alert = new Alert("Error Creando", error.toString(), null, AlertType.WARNING); alert.setTimeout(Alert.FOREVER); display.setCurrent(alert); } 85 Programa: DBVRMSimple (3/5) try { String outputData = "1"; byte[] byteOutputData = outputData.getBytes(); recordstore.addRecord(byteOutputData, 0, byteOutputData.length); } catch ( Exception error) { alert = new Alert("Error Escribiendo 1", error.toString(), null, AlertType.WARNING); alert.setTimeout(Alert.FOREVER); display.setCurrent(alert); } } else if (command == incrementar) {// Para añadir nuevos valores try { // Se mira cuantos elementos hay y se guarda ese valor+1 en el RecordStore String outputData = (new Integer(recordstore.getNumRecords()+1)).toString(); byte[] byteOutputData = outputData.getBytes(); recordstore.addRecord(byteOutputData, 0, byteOutputData.length); } catch ( Exception error) { alert = new Alert("Error Escribiendo", error.toString(), null, AlertType.WARNING); alert.setTimeout(Alert.FOREVER); display.setCurrent(alert); } } else if (command == ver) { // Muestra todos los registros 86 43 Programa: DBVRMSimple (4/5) } try { byte[] byteInputData = new byte[30]; int length = 0; String titulo=null; String cadena=new String(""); for (int x = 1; x <= recordstore.getNumRecords(); x++) { length = recordstore.getRecord(x, byteInputData, 0); titulo="Leyendo" + (new Integer(x)).toString(); cadena=cadena + " "+ new String(byteInputData); } // Se muestra el resultado en un alert alert = new Alert(titulo, cadena, null, AlertType.WARNING); alert.setTimeout(Alert.FOREVER); display.setCurrent(alert); } catch (Exception error) { alert = new Alert("Error Leyendo", error.toString(), null, AlertType.WARNING); alert.setTimeout(Alert.FOREVER); display.setCurrent(alert); } 87 Programa: DBVRMSimple (5/5) else if (command == borrar) { // Elimina el registro completo } } } if (RecordStore.listRecordStores() != null) { try { RecordStore.deleteRecordStore("contador"); } catch (Exception error) { alert = new Alert("Error Borrando", error.toString(), null, AlertType.WARNING); alert.setTimeout(Alert.FOREVER); display.setCurrent(alert); } } 88 44 RMS: Datos complejos • Si se quieren guardar en un Registro datos compuestos la mejor forma es crear para escritura: escritura: – un ByteArrayOutputStream DataOutputStream • Y el simé simétrico para lectura – ByteArrayInputStream DataInputStream • Estas estructuras permiten almacenar datos de distintos • tipos La clase DataOutputStream tiene métodos para escribir todo tipo de elementos: elementos: boolean writeBoolean Byte writeByte Float writeFloat Int writeInt String writeUTF … … 89 RMS: Escritura de datos complejos • Si se quiere por ejemplo escribir un número y un String // Creación de variables necesarias byte[] outputData; ByteArrayOutputStream outputStream=new ByteArrayOutputStream(); DataOutputStream outputDataStream=new DataOutputStream(outputStream); outputDataStream.writeInt(numero); // Escritura del número outputDataStream.writeUTF(nombre); // Escritura del nombre outputDataStream.flush(); // Enviar los datos al flujo outputData=outputStream.toByteArray(); // Convierte los datos a un array de bytes recordstore.addRecord(outputData, 0, outputData.length); // Escribe los datos en el registro outputDataStream.close(); outputStream.close(); 90 45 RMS: Lectura de datos complejos • Si se quiere leer un nú número y un String // Creación de variables necesarias byte[] byteInputData = new byte[30]; ByteArrayInputStream inputStream; DataInputStream inputDataStream; inputStream=new ByteArrayInputStream(byteInputData); // Crea el ByteArray inputDataStream=new DataInputStream(inputStream); // Crea el DataInputStream int length = records.getRecord(i, byteInputData, 0); // Lee el registro i-esimo numero= inputDataStream.readInt(); // Lee el número cadena= inputDataStream.readUTF()); // Lee la cadena inputStream.reset(); // Limpia el buffer para poder seguir leyendo más registros • El resto de las operaciones de creació creación y borrado son • iguales que para el caso de datos simples A continuació continuación un ejemplo en el que se añ añaden pares (nombre, puntos) a una lista de records 91 Programa: DBVRMSRecords Insertar Listado Salir 92 46 Programa: DBVRMSRecords (1/5) import javax.microedition.rms.*; import javax.microedition.midlet.*; import javax.microedition.lcdui.*; import java.io.*; public class DBVRMSRecords extends MIDlet implements CommandListener { private Alert alert; private Command salir,listado,nuevorecord, insertar, eliminar; private Display pantalla; private Form principal,frecord; private TextField fnombre,fpuntos; private RecordStore records = null; public DBVRMSRecords() { pantalla = Display.getDisplay(this); // Preparacion de la pantalla para poner records frecord=new Form("Nuevo Record"); fnombre=new TextField("Nombre:","",30,TextField.ANY); fpuntos=new TextField("Puntos:","",30,TextField.NUMERIC); insertar=new Command("Insertar", Command.SCREEN,0); frecord.append(fnombre); frecord.append(fpuntos); frecord.addCommand(insertar); frecord.setCommandListener(this); // Preparando la pantalla principal principal=new Form("Administracion"); salir = new Command("Salir", Command.EXIT, 0); nuevorecord = new Command("Nuevo Record", Command.SCREEN, 0); listado = new Command("Listado", Command.SCREEN, 0); eliminar=new Command("Eliminar Todo", Command.SCREEN,0); 93 Programa: DBVRMSRecords (2/5) principal.addCommand(salir); principal.addCommand(nuevorecord); principal.addCommand(listado); principal.addCommand(eliminar); principal.setCommandListener(this); } public void startApp() { cargarRecords(); mostrarRecords(); pantalla.setCurrent(alert,principal); } public void pauseApp() { } public void destroyApp( boolean unconditional ) { } public void commandAction(Command command, Displayable pantallaable) { if (command == salir) { // Acción para terminar if (records!=null) { cerrarRecords(); } destroyApp(true); notifyDestroyed(); } else if (command==nuevorecord) {// Añadir un nuevo record pantalla.setCurrent(frecord); } else if (command==listado) { // Ver todos los records mostrarRecords(); 94 47 Programa: DBVRMSRecords (3/5) } else if (command==eliminar) { // Borrar todos los registros if (records!=null) { cerrarRecords(); } eliminarRegistros(); } else if (command==insertar) { // Añadir un nuevo registro desde el formulario guardarRecords(Integer.parseInt(fpuntos.getString()),fnombre.getString()); pantalla.setCurrent(principal); } } public void cargarRecords() { // Abre el RecordStore de los records o lo crea si no existe try { records = RecordStore.openRecordStore("records", true ); } catch (Exception error) { alert = new Alert("Error Creando", error.toString(), null, AlertType.WARNING); alert.setTimeout(Alert.FOREVER); pantalla.setCurrent(alert); } } public void cerrarRecords() { // Cierra el RecordStore try { records.closeRecordStore(); } catch (Exception error) { alert = new Alert("Error Cerrando", error.toString(), null, AlertType.WARNING); alert.setTimeout(Alert.FOREVER); pantalla.setCurrent(alert); } } 95 Programa: DBVRMSRecords (4/5) public void eliminarRegistros() {// Borrar todos los registros try { RecordStore.deleteRecordStore("records"); } catch (Exception error) { alert = new Alert("Error Borrando...", error.toString(), null, AlertType.WARNING); alert.setTimeout(Alert.FOREVER); pantalla.setCurrent(alert); } } public void guardarRecords(int numero,String nombre) {// Añadir un nuevo registro try { byte[] outputData; ByteArrayOutputStream outputStream=new ByteArrayOutputStream(); DataOutputStream outputDataStream=new DataOutputStream(outputStream); if (records==null) { cargarRecords(); } outputDataStream.writeInt(numero); outputDataStream.writeUTF(nombre); outputDataStream.flush(); outputData=outputStream.toByteArray(); records.addRecord(outputData, 0, outputData.length); outputDataStream.close(); outputStream.close(); } catch ( Exception error) { alert = new Alert("Error Escribiendo 1", error.toString(), null, AlertType.WARNING); alert.setTimeout(Alert.FOREVER); pantalla.setCurrent(alert); } } 96 48 Programa: DBVRMSRecords (5/5) public void mostrarRecords() {// Ver todos los records StringBuffer buffer=new StringBuffer(); byte[] byteInputData = new byte[30]; int puntos; String nombre; ByteArrayInputStream inputStream; DataInputStream inputDataStream; try { if (records==null) { cargarRecords(); } inputStream=new ByteArrayInputStream(byteInputData); inputDataStream=new DataInputStream(inputStream); buffer.append("Registros: " + records.getNumRecords()); for(int i=1;i<=records.getNumRecords();i++) { int length = records.getRecord(i, byteInputData, 0); buffer.append("\n" + inputDataStream.readInt() + " "+ inputDataStream.readUTF()); inputStream.reset(); } alert = new Alert("Records", buffer.toString(), null, AlertType.WARNING); alert.setTimeout(Alert.FOREVER); pantalla.setCurrent(alert); } catch (Exception error) { alert = new Alert("Error Leyendo", error.toString(), null, AlertType.WARNING); alert.setTimeout(3000); pantalla.setCurrent(alert); } } } 97 Problema: Versiones de Prueba • Cuando se quiere entregar una muestra de un programa antes de venderlo hay varias opciones: – Limitar opciones – Fecha lílímite de uso – Número limitado de ejecuciones • Se pide utilizando RMS hacer una aplicació aplicación que só sólo pueda ejecutarse 5 veces – Cada vez que se ejecuta debe decirnos cuantas pruebas nos quedan • Puede ser necesario otro programa que inicialice o modifique el nú número de evaluaciones posibles 98 49 Comunicaciones HTTP • J2ME permite hacer conexiones por HTTP • Esto permite hacer cualquier tipo de aplicació aplicación en la que • • • el dispositivo mó móvil será será el cliente Lo único que hay que hacer es definir un protocolo propio para interpretar los mensajes entre el cliente y el servidor Lo má más sencillo es que el servidor devuelva texto plano Por ejemplo, el telé teléfono pide por el mé método get la lista de citas del dí día, y el servidor las devuelve: Cliente (Teléfono abre esta dirección) El servidor devuelve texto plano http://miservidor.es/citas.jsp?idusuario=bueno&fecha=hoy Citas para hoy: 9:00-14:00 Curso de J2ME 16:00-20:00 Examen 1ºTeleco 99 Comunicaciones HTTP: Servidor • El servidor podrá podrá estar programado en cualquier lenguaje que permita conexió conexión http: – CGI – ASP Java: Servlets o JSP PHP, etc. • Como ejemplo se verá verá 2 aplicaciones del servidor que devuelven texto plano: plano: “Hola Mundo” Mundo”, una con Servlets y servlet otra en php import javax.servlet.http.*; php <?php // Enviaremos un texto plano header('Content-type: text/plain'); echo ("hola mundo"); jsp ?> <% response.setContentType("text/plain");%> <%=“Hola Mundo”%> import javax.servlet.*; import java.io.*; public class HitServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String message = "Hola Mundo"; response.setContentType("text/plain"); response.setContentLength(message.length()); PrintWriter out = response.getWriter(); out.println(message); } } 100 50 Comunicaciones HTTP: Cliente J2ME • Para no bloquear la ejecució ejecución, las conexiones deben • realizarse en una hebra independiente Se utiliza un objeto HttpConnection con la url y los pará parámetros del programa servidor HttpConnection conexion=(HttpConnection) Connector.open("http://localhost/j2mehola.php"); • Los datos se leen en un array de bytes que podrá podrá procesarse a voluntad, voluntad, igual que en RMS InputStream entrada=null; int longitud; byte[] texto; String cadena; entrada=conexion.openInputStream(); longitud=(int)conexion.getLength(); if (longitud==-1) { // A veces la longitud se lee incorrectamente longitud=255; } texto=new byte[longitud]; // Reserva array de bytes longitud=entrada.read(texto); entrada.close(); // Cierra el flujo de entrada conexion.close(); // Cierra la conexión cadena=new String(texto,0,longitud); // Convierte el array de bytes a String 101 Programa: DBVHTTPHolaMundo La luz verde Indica conexión Web 102 51 Programa: DBVHTTPHolaMundo (1/3) import import import import javax.microedition.midlet.*; javax.microedition.lcdui.*; javax.microedition.io.*; java.io.*; public class DBVHTTPHolaMundo extends MIDlet implements CommandListener, Runnable { private Alert alert; private Command salir,conectar; private Display pantalla; private Form principal; private TextField ftexto; public DBVHTTPHolaMundo() { principal=new Form("Conexion HTTP"); // Preparacion de la pantalla principal ftexto=new TextField("Texto Leido:","",30,TextField.ANY); conectar=new Command("Conectar", Command.SCREEN,0); salir = new Command("Salir", Command.EXIT, 0); principal.append(ftexto); principal.addCommand(conectar); principal.addCommand(salir); principal.setCommandListener(this); } public void startApp() { pantalla = Display.getDisplay(this); pantalla.setCurrent(principal); ftexto.setString("Pulse conectar..."); } public void pauseApp() { } 103 Programa: DBVHTTPHolaMundo (2/3) public void destroyApp( boolean unconditional ) { } public void commandAction(Command command, Displayable pantallaable) { if (command == salir) { destroyApp(true); notifyDestroyed(); } else if (command==conectar) { Thread t=new Thread(this); // Inicia la hebra de conexión t.start(); } } public void conectar() { HttpConnection conexion=null; InputStream entrada=null; int longitud; byte[] texto; String cadena; try { ftexto.setString("Conectando..."); conexion=(HttpConnection) Connector.open("http://localhost/j2mehola.php"); //conexion.setRequestProperty("Connection","close"); ftexto.setString("Abriendo..."); entrada=conexion.openInputStream(); longitud=(int)conexion.getLength(); if (longitud==-1) { longitud=255; } 104 52 Programa: DBVHTTPHolaMundo (3/3) texto=new byte[longitud]; ftexto.setString("Leyendo..."); longitud=entrada.read(texto); ftexto.setString("Cerrando..."); entrada.close(); conexion.close(); cadena=new String(texto,0,longitud); ftexto.setString(cadena); // Modifica el cuadro de texto con el texto recibido } catch (Exception error) { alert = new Alert("Error Conectando", error.toString(), null, AlertType.WARNING); alert.setTimeout(Alert.FOREVER); pantalla.setCurrent(alert); } } } public void run() { // Inicio de la hebra conectar(); } 105 Curiosidades • Para hacer vibrar el telé teléfono a partir de MIDP 2.0 – Display.vibrate(100) Display.vibrate(100) ..- Tiempo en Milisegundos – Display.flashBacklight(tiempo). Display.flashBacklight(tiempo).-- Enciende la luz del telé teléfono • Para probar estas caracterí características se va a mostrar un • • • programa que permitirá permitirá usar el telé teléfono para dar masajes o como una linterna ::-) En el emulador la vibració vibración se muestra con un zumbido La luz de fondo se muestra con un marco de color No todos los telé teléfonos permiten controlar la luz y el vibrador 106 53 Programa: DBVMasaje zumbido 107 Programa: DBVMasaje import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class DBVMasaje extends MIDlet implements CommandListener { private Display pantalla ; private TextBox tintro,tayuda ; private Command salir, masaje, linterna; public DBVMasaje(){ pantalla = Display.getDisplay(this); // Preparando la pantalla principal tintro = new TextBox("Mi linterna mágica", "Elije un masaje o una linterna", 40, 0); salir = new Command("Salir", Command.EXIT, 0); masaje = new Command("Masaje",Command.HELP,1); linterna= new Command("Linterna",Command.SCREEN,1); tintro .addCommand(salir); tintro.addCommand(masaje);tintro.addCommand(linterna); tintro.setCommandListener(this); } public void startApp() {pantalla .setCurrent(tintro); } public void pauseApp() { } public void destroyApp(boolean unconditional) { } public void commandAction(Command c, Displayable displayable) { if (c == salir) { destroyApp(false); notifyDestroyed(); } else if (c==linterna) { pantalla.flashBacklight(3000); // Enciende la luz 3 segundos } else if (c==masaje) { pantalla.vibrate(3000); // Activa la vibración 3 segundos } } } 108 54 Sonidos • Tonos en la clase Manager playTone(int nota, int duracion, int volumen) • Las notas se asocian igual que en un midi, midi, cada tecla del • • piano es una nota C(60C(60-440Hz)C#(61)D(62)D#(63)E(64)F(65)F#(66) G(67)G#(68)A(69) Volumen má máximo=100 Manager.playTone(60,1000,100) 109 Para ampliar… • Servicios Web (jsr172) • Bluetooth/OBEX (jsr82) – Para probar los ejemplos bluetooth es necesario en ktoolbar: ktoolbar: Edit Preferences Security Security Domain=trusted • Grá Gráficos 3D (jsr184) • Sonidos y Música. sica. MMAPI(Mobile Media APIAPI-jsr135) • Uso de push para iniciar aplicaciones desde un servidor • en el movil RecordEnumeration – Permite ordenar y filtrar los registros de un RecordStore • Location (jsr179) Localizació Localización geográ geográfica (CLDC 1.1) • Cada especificación jsrXYZ se puede encontrar en: http://jcp.org/en/jsr/detail?id=XYZ 110 55 Referencias Libros • Jonathan Knudsen, “Wireless Java: Developing with J2ME” J2ME”, 2nd edition. Ed. APress, APress, 2003. • James Keogh, “The Complete Reference: J2ME” J2ME”, Ed. Osborne, 2003. • Jason Lam, “J2ME & Gaming” Gaming”, 2004. http://www.jasonlam604.com Artí Artículos • Qusay H. Mahmoud, Mahmoud, “Wireless Application Programming with J2ME and Bluetooth” Bluetooth”, 2003 http://developers.sun.com/techtopics/mobility/midp/articles/bluetoot h1/ y /bluetooth2/ http://developers.sun.com/techtopics/mobility/midp/articles/bluetooth1/ • Jonathan Knudsen, “Creating 2D Action Games with the Game API” API”, 2003. http://developers.sun.com/techtopics/mobility/midp/articles/game/ http://developers.sun.com/techtopics/mobility/midp/articles/game/ Webs de Interé Interés Forum Nokia. http://www.forum.nokia.com http://www.forum.nokia.com// Java en sun. http://java.sun.com/ 111 Curiosidades • Para hacer vibrar el telé teléfono a partir de MIDP 2.0 – display.vibrate(100) display.vibrate(100) ..- Tiempo en Milisegundos – Display.flashBacklight(tiempo). Display.flashBacklight(tiempo).-- Enciende la luz del telé teléfono 112 56