El código Java, una vez compilado, puede llevarse sin modificación
alguna sobre cualquier máquina, y ejecutarlo. Esto se debe a que el código
se ejecuta sobre una máquina hipotética o virtual, la Java
Virtual Machine, que se encarga de interpretar el código (ficheros
compilados .class
) y convertirlo a código particular
de la CPU que se esté utilizando (siempre que se soporte dicha máquina
virtual).
Hemos visto que en el caso de los MIDs, este código intermedio Java se ejecutará sobre una versión reducida de la máquina virtual, la KVM (Kilobyte Virtual Machine), lo cual producirá determinadas limitaciones en las aplicaciones desarrolladas para dicha máquina virtual.
Cuando se programa con Java se dispone de antemano de un conjunto de clases ya implementadas. Estas clases (aparte de las que pueda hacer el usuario) forman parte del propio lenguaje (lo que se conoce como API (Application Programming Interface) de Java).
La API que se utilizará para programar las aplicaciones para MIDs será la API de MIDP, que contendrá un conjunto reducido de clases que nos permitan realizar las tareas fundamentales en estas aplicaciones. La implementación de esta API estará optimizada para ejecutarse en este tipo de dispositivos. En CLDC tendremos un subconjunto reducido y simplificado de las clases de J2SE, mientras que en MIDP tendremos clases que son exclusivas de J2ME ya que van orientadas a la programación de las características propias de los dispositivos móviles.
En este punto estudiaremos las clases que CLDC toma de J2SE, y veremos las diferencias y limitaciones que tienen respecto a su versión en la plataforma estándar de Java.
En CLDC desaparecen los tipos float
y double
que podíamos usar en otras ediciones de Java. Esto es debido a que
la KVM no tiene soporte para estos tipos, ya que las operaciones
con números reales son complejas y estos dispositivos muchas veces
no tienen unidad de punto flotante.
Las aplicaciones J2ME para dispositivos CDC si que podrán usar estos tipos de datos, ya que funcionarán con la máquina virtual CVM que si que soporta estos tipos. Por lo tanto la limitación no es de J2ME, sino de la máquina virtual KVM en la que se basan las aplicaciones CLDC.
NOTA: En CLDC 1.1 se incorporan los tipos de datos double
y
float
. En los dispositivos que soporten esta versión de
la API podremos utilizar estos tipos de datos.
En CLDC 1.0 echamos en falta el soporte para número de coma flotante
(float
y double
). En principio podemos pensar que
esto es una gran limitación, sobretodo para aplicaciones que necesiten
trabajar con valores de este tipo. Por ejemplo, si estamos trabajando con información
monetaria para mostrar el precio de los productos necesitaremos utilizar números
como 13.95€.
Sin embargo, en muchos casos podremos valernos de los números enteros
para representar estos números reales. Vamos a ver un truco con el que
implementar soporte para números reales de coma fija mediante datos de
tipo entero (int
).
Este truco consiste en considerar un número N fijo de decimales, por ejemplo en el caso de los precios podemos considerar que van a tener 2 decimales. Entonces lo que haremos será trabajar con números enteros, considerando que las N últimas cifras son los decimales. Por ejemplo, si un producto cuesta 13.95€, lo guardaremos en una variable entera con valor 1395, es decir, en este caso es como si estuviésemos guardando la información en céntimos.
Cuando queramos mostrar este valor, deberemos separar la parte entera y la fraccionaria para imprimirlo con el formato correspondiente a un número real. Haremos la siguiente transformación:
public String imprimeReal(int numero) { int entero = numero / 100; int fraccion = numero % 100; return entero + "." + (fraccion<10?"0":"") + fraccion; }
Cuando el usuario introduzca un número con formato real, y queramos
leerlo y guardarlo en una variable de tipo entero (int
) deberemos
hacer la transformación contraria:
public int leeReal(String numero) { int pos_coma = numero.indexOf('.'); String entero = numero.substring(0, pos_coma - 1); String fraccion = numero.substring(pos_coma + 1, pos_coma + 2); return Integer.parseInt(entero)*100 + Integer.parseInt(fraccion); }
Es posible que necesitemos realizar operaciones básicas con estos números reales. Podremos realizar operaciones como suma, resta, multiplicación y división utilizando la representación como enteros de estos números.
El caso de la suma y de la resta es sencillo. Si sumamos o restamos dos números con N decimales cada uno, podremos sumarlos como si fuesen enteros y sabremos que las últimas N cifras del resultado son decimales. Por ejemplo, si queremos añadir dos productos a la cesta de la compra, cuyos precios son 13.95€ y 5.20€ respectivamente, deberemos sumar estas cantidades para obtener el importe total. Para ello las trataremos como enteros y hacemos la siguiente suma:
1395 + 520 = 1915
Por lo tanto, el resultado de la suma de los números reales será 19.15€.
El caso de la multiplicación es algo más complejo. Si queremos multiplicar dos números, con N y M decimales respectivamente, podremos hacer la multiplicación como si fuesen enteros sabiendo que el resultado tendrá N+M decimales. Por ejemplo, si al importe anterior de 19.15€ queremos añadirle el IVA, tendremos que multiplicarlo por 1.16. Haremos la siguiente operación entera:
1915 * 116 = 222140
El resultado real será 22.2140€, ya que si cada operando tenía 2 decimales, el resultado tendrá 4.
Si estas operaciones básicas no son suficiente podemos utilizar una
librería como MathFP, que nos permitirá realizar
operaciones más complejas con números de coma fija representados
como enteros. Entre ellas tenemos disponibles operaciones trigonométricas,
logarítmicas, exponenciales, potencias, etc. Podemos descargar esta librería
de http://www.jscience.net/
e incluirla libremente en nuestra aplicaciones
J2ME.
Vamos a ver las características básicas del lenguaje Java (plataforma J2SE) que tenemos disponibles en la API CLDC de los dispositivos móviles. Dentro de esta API tenemos la parte básica del lenguaje que debe estar disponible en cualquier dispositivo conectado limitado.
Esta API básica se ha tomado directamente de J2SE, de forma que los programadores que conozcan el lenguaje Java podrán programar de forma sencilla aplicaciones para dispositivos móviles sin tener que aprender a manejar una API totalmente distinta. Sólo tendrán que aprender a utilizar la parte de la API propia de estos dispositivos móviles, que se utiliza para características que sólo están presentes en estos dispositivos.
Dado que estos dispositivos tienen una capacidad muy limitada, en CLDC sólo está disponible una parte reducida de esta API de Java. Vamos a ver en este punto qué características de las que ya conocemos del lenguaje Java están presentes en CLDC para programar en dispositivos móviles.
El manejo de las excepciones se realiza de la misma forma que en J2SE.
En la API de CLDC no están presentes los grupos de hilos. La clase ThreadGroup
de la API de J2SE no existe en la API de CLDC, por lo que no podremos utilizar esta característica desde los MIDs. Tampoco podemos ejecutar hilos como demonios (daemon).
En J2SE existe lo que se conoce como marco de colecciones, que comprende una serie de tipos de datos. Estos tipos de datos se denominan colecciones por ser una colección de elementos, tenemos distintos subtipos de colecciones como las listas (secuencias de elementos), conjuntos (colecciones sin elementos repetidos) y mapas (conjunto de parejas <clave, valor>). Tendremos varias implementaciones de estos tipos de datos, siendo sus operadores polimórficos, es decir, se utilizan los mismos operadores para distintos tipos de datos. Para ello se definen interfaces que deben implementar estos tipos de datos, una serie de implementaciones de estas interfaces y algoritmos para trabajar con ellos.
Sin embargo, en CLDC no tenemos este marco de colecciones. Al tener que utilizar
una API tan reducida como sea posible, tenemos solamente las clases Vector
(tipo lista), Stack
(tipo pila) y Hashtable
(mapa)
tal como ocurría en las primeras versiones de Java. Estas clases son
independientes, no implementan ninguna interfaz común.
En CLDC tenemos wrappers para los tipos básicos soportados: Boolean
,
Integer
, Long
, Byte
, Short
,
Character
.
NOTA: Dado que a partir de CLDC 1.1 se incorporan los tipos de datos float
y double
, aparecerán también sus correspondientes wrappers: Float
y Double
.
Vamos a ver ahora una serie de clases básicas del lenguaje Java que siguen estando en CLDC. La versión de CLDC de estas clases estará normalmente más limitada, a continuación veremos las diferencias existentes entre la versión de J2SE y la de CLDC.
Object
En J2SE la clase Object
tiene un método clone
que podemos utilizar para realizar una copia del objeto, de forma que tengamos
dos objetos independientes en memoria con el mismo contenido. Este método
no existe en CLDC, por lo que si queremos realizar una copia de un objeto deberemos
definir un constructor de copia, es decir, un constructor que construya un nuevo
objeto copiando todas las propiedades de otro objeto de la misma clase.
System
Podemos encontrar los objetos que encapsulan la salida y salida de error estándar. A diferencia de J2SE, en CLDC no tenemos entrada estándar.
Tampoco nos permite instalar un gestor de seguridad para la aplicación. La API de CLDC y MIDP ya cuenta con las limitaciones suficientes para que las aplicaciones sean seguras.
En CLDC no tenemos una clase Properties
con una colección
de propiedades. Por esta razón, cuando leamos propiedades del sistema
no podremos obtenerlas en un objeto Properties
, sino que tendremos
que leerlas individualmente. Estas son propiedades del sistema, no son los propiedades
del usuario que aparecen en el fichero JAD. En el próximo tema veremos
cómo leer estas propiedades del usuario.
Runtime
En J2SE podemos utilizar esta clase para ejecutar comandos del sistema con
exec
. En CLDC no disponemos de esta característica. Lo que
si podremos hacer con este objeto es obtener la memoria del sistema, y la memoria
libre.
Math
En CLDC 1.0, al no contar con soporte para números reales, esta clase contendrá muy pocos métodos, sólo tendrá aquellas operaciones que trabajan con números enteros, como las operaciones de valor absoluto, máximo y mínimo.
Random
En CLDC 1.0 sólo nos permitirá generar números enteros de forma aleatoria, ya que no tenemos soporte para reales.
Fechas y horas
Si miramos dentro del paquete java.util
, podremos encontrar una
serie de clases que nos podrán resultar útiles para determinadas
aplicaciones.
Entre ellas tenemos la clase Calendar
, que junto a Date
nos servirá cuando trabajemos con fechas y horas. La clase Date
representará un determinado instante de tiempo, en tiempo absoluto. Esta
clase trabaja con el tiempo medido en milisegundos desde el desde el 1 de enero
de 1970 a las 0:00, por lo que será difícil trabajar con esta
información directamente.
Podremos utilizar la clase Calendar
para obtener un determinado
instante de tiempo encapsulado en un objeto Date
, proporcionando
información de alto nivel como el año, mes, día, hora,
minuto y segundo.
Con TimeZone
podemos representar una determinada zona horaria,
con lo que podremos utilizarla junto a las clases anteriores para obtener diferencias
horarias.
Temporizadores
Los temporizadores nos permitirán planificar tareas para ser ejecutadas
por un hilo en segundo plano. Para trabajar con temporizadores tenemos las clases
Timer
y TimerTask
.
Lo primero que deberemos hacer es crear las tareas que queramos planificar.
Para crear una tarea crearemos una clase que herede de TimerTask
,
y que defina un método run
donde incluiremos el código
que implemente la tarea.
public class MiTarea extends TimerTask { public void run() { // Código de la tarea } }
Una vez definida la tarea, utilizaremos un objeto Timer
para planificarla.
Para ello deberemos establecer el tiempo de comienzo de dicha tarea, cosa que
puede hacerse de dos formas diferentes:
Tenemos diferentes formas de planificación de tareas, según el número de veces y la periodicidad con la que se ejecutan:
Deberemos como primer paso crear el temporizador y la tarea que vamos a planificar:
Timer t = new Timer(); TimerTask tarea = new MiTarea();
Ahora podemos planificarla para comenzar con un retardo, o bien a una determinada fecha y hora. Si vamos a hacerlo por retardo, utilizaremos uno de los siguientes métodos, según la periodicidad:
t.schedule(tarea, retardo); // Una vez t.schedule(tarea, retardo, periodo); // Retardo fijo t.scheduleAtFixedRate(tarea, retardo, periodo); // Frecuencia constante
Si queremos comenzar a una determinada fecha y hora, deberemos utilizar un
objeto Date
para especificar este tiempo de comienzo:
Calendar calendario = Calendar.getInstance(); calendario.set(Calendar.HOUR_OF_DAY, 8); calendario.set(Calendar.MINUTE, 0); calendario.set(Calendar.SECOND, 0); calendario.set(Calendar.MONTH, Calendar.SEPTEMBER); calendario.set(Calendar.DAY_OF_MONTH, 22); Date fecha = calendario.getTime();
Una vez obtenido este objeto con la fecha a la que queremos comenzar la tarea (en nuestro ejemplo el día 22 de septiembre a las 8:00), podemos planificarla con el temporizador igual que en el caso anterior:
t.schedule(tarea, fecha); // Una vez t.schedule(tarea, fecha, periodo); // Retardo fijo t.scheduleAtFixedRate(tarea, fecha, periodo); // Frecuencia constante
Los temporizadores nos serán útiles en las aplicaciones móviles para realizar aplicaciones como por ejemplo agendas o alarmas. La planificación por retardo nos permitirá mostrar ventanas de transición en nuestras aplicaciones durante un número determinado de segundos.
Si queremos que un temporizador no vuelva a ejecutar la tarea planificada, utilizaremos su método cancel para cancelarlo.
t.cancel();
Una vez cancelado el temporizador, no podrá volverse a poner en marcha de nuevo. Si queremos volver a planificar la tarea deberemos crear un temporizador nuevo.
En las aplicaciones CLDC, normalmente utilizaremos flujos para enviar o recibir datos a través de la red, o para leer o escribir datos en algún buffer de memoria.
En CLDC no encontramos flujos para acceder directamente a ficheros, ya que no podemos contar con poder acceder al sistema de ficheros de los dispositivos móviles, esta característica será opcional. Tampoco tenemos disponible ningún tokenizer, por lo que la lectura y escritura deberá hacerse a bajo nivel como acabamos de ver, e implementar nuestro propio analizador léxico en caso necesario.
Serialización de objetos
Otra característica que no está disponible en CLDC es la serialización automática de objetos, por lo que no podremos enviar directamente objetos a través de los flujos de datos. No existe ninguna forma de serializar cualquier objeto arbitrario automáticamente en CLDC, ya que no soporta reflection.
Sin embargo, podemos hacerlo de una forma más sencilla, y es haciendo que cada objeto particular proporcione métodos para serializarse y deserializarse. Estos métodos los deberemos escribir nosotros, adaptándolos a las características de los objetos.
Por ejemplo, supongamos que tenemos una clase Punto2D
como la
siguiente:
public class Punto2D {
int x; int y; String etiqueta; ... }
Los datos que contiene cada objeto de esta clase son las coordenadas (x,y) del punto y una etiqueta para identificar este punto. Si queremos serializar un objeto de esta clase esta será la información que deberemos codificar en forma de serie de bytes.
Podemos crear dos métodos manualmente para codificar y descodificar esta información en forma de array de bytes, como se muestra a continuación:
public class Punto2D { int x; int y; String etiqueta; ... public void serialize(OutputStream out) throws IOException { DataOutputStream dos = new DataOutputStream( out );
dos.writeInt(x); dos.writeInt(y); dos.writeUTF(etiqueta);
dos.flush(); } public static Punto2D deserialize(InputStream in) throws IOException { DataInputStream dis = new DataInputStream( in );
Punto2D p = new Punto2D(); p.x = dis.readInt(); p.y = dis.readInt(); p.etiqueta = dis.readUTF();
return p;
} }
Hemos visto como los flujos de procesamiento DataOutputStream
y DataInputStream
nos facilitan la codificación de distintos
tipos de datos para ser enviados a través de un flujo de datos.
Acceso a los recursos
Hemos visto que no podemos acceder al sistema de ficheros directamente como hacíamos en J2SE. Sin embargo, con las aplicaciones MIDP podemos incluir una serie de recursos a los que deberemos poder acceder. Estos recursos son ficheros incluidos en el fichero JAR de la aplicación, como por ejemplo sonidos, imágenes o ficheros de datos.
Para acceder a estos recursos deberemos abrir un flujo de entrada que se encargue
de leer su contenido. Para ello utilizaremos el método getResourceAsStream
de la clase Class
:
InputStream in = getClass().getResourceAsStream("datos.txt");
De esta forma podremos utilizar el flujo de entrada obtenido para leer el contenido del fichero que hayamos indicado. Este fichero deberá estar contenido en el JAR de la aplicación.
Salida y salida de error estándar
En J2SE la entrada estándar normalmente se refiere a lo que el usuario escribe en la consola, aunque el sistema operativo puede hacer que se tome de otra fuente. De la misma forma la salida y la salida de error estándar lo que hacen normalmente es mostrar los mensajes y los errores del programa respectivamente en la consola, aunque el sistema operativo también podrá redirigirlas a otro destino.
En los MIDs no tenemos consola, por lo que los mensajes que imprimamos por la salida estándar normalmente serán ignorados. Esta salida estará dirigida a un dispositivo null en los teléfonos móviles. Sin embargo, imprimir por la salida estándar puede resultarnos útil mientras estemos probando la aplicaciones en emuladores, ya que al ejecutarse en el ordenador estos emuladores, estos mensajes si que se mostrarán por la consola, por lo que podremos imprimir en ellos información que nos sirva para depurar las aplicaciones.
Además de las diferencias que hemos visto en los puntos anteriores, tenemos APIs que han desaparecido en su totalidad, o prácticamente en su totalidad.
Reflection
En CLDC no está presente la API de reflection. Sólo
está presente la clase Class
con la que podremos cargar
clases dinámicamente y comprobar la clase a la que pertenece un objeto
en tiempo de ejecución. Tenemos además en esta clase el método
getResourceAsStream
que hemos visto anteriormente, que nos servirá
para acceder a los recursos dentro del JAR de la aplicación.
Red
La API para el acceso a la red de J2SE es demasiado compleja para los MIDs.
Por esta razón se ha sustituido por una nueva API totalmente distinta,
adaptada a las necesidades de conectividad de estos dispositivos. Desaparece
la API java.net
, para acceder a la red ahora deberemos utilizar
la API javax.microedition.io
incluida en CLDC que veremos en detalle
en el próximo tema.
AWT/Swing
Las librerías para la creación de interfaces gráficas,
AWT y Swing, desaparecen totalmente ya que estas interfaces no son adecuadas
para las pantallas de los MIDs. Para crear la interfaz gráfica de las
aplicaciones para móviles tendremos la API javax.microedition.lcdui
perteneciente a MIDP.
Hasta ahora hemos visto la parte básica del lenguaje Java que podemos utilizar en los dispositivos móviles. Esta parte de la API está basada en la API básica de J2SE, reducida y optimizada para su utilización en dispositivos de baja capacidad. Esta es la base que necesitaremos para programar cualquier tipo de dispositivo, sin embargo con ella por si sola no podemos acceder a las características propias de los móviles, como su pantalla, su teclado, reproducir tonos, etc.
Vamos a ver ahora las APIs propias para el desarrollo de aplicaciones móviles.
Estas APIs ya no están basadas en APIs existentes en J2SE, sino que se
han desarrollado específicamente para la programación en estos
dispositivos. Todas ellas pertenecen al paquete javax.microedition
.
Los MIDlets son las aplicaciones para MIDs, realizadas con la API de MIDP. La clase principal de cualquier aplicación MIDP deberá ser un MIDlet. Ese MIDlet podrá utilizar cualquier otra clase Java y la API de MIDP para realizar sus funciones.
Para crear un MIDlet deberemos heredar de la clase MIDlet
. Esta
clase define una serie de métodos abstractos que deberemos definir en
nuestros MIDlets, introduciendo en ellos el código propio de nuestra
aplicación:
protected abstract void startApp();
protected abstract void pauseApp(); protected abstract void destroyApp(boolean incondicional);
A continuación veremos con más detalle qué deberemos introducir en cada uno de estos métodos.
Numerosas veces encontramos dentro de las tecnologías Java el concepto de componentes y contenedores. Los componentes son elementos que tienen una determinada interfaz, y los contenedores son la infraestructura que da soporte a estos componentes.
Por ejemplo, podemos ver los applets como un tipo de componente, que para poderse ejecutar necesita un navegador web que haga de contenedor y que lo soporte. De la misma forma, los servlets son componentes que encapsulan el mecanismo petición/respuesta de la web, y el servidor web tendrá un contenedor que de soporte a estos componentes, para ejecutarlos cuando se produzca una petición desde un cliente. De esta forma nosotros podemos deberemos definir sólo el componente, con su correspondiente interfaz, y será el contenedor quien se encargue de controlar su ciclo de vida (instanciarlo, ejecutarlo, destruirlo).
Cuando desarrollamos componentes, no deberemos crear el método main
,
ya que estos componentes no se ejecutan como una aplicación independiente
(stand-alone), sino que son ejecutados dentro de una aplicación
ya existente, que será el contenedor.
El contenedor que da soporte a los MIDlets recibe el nombre de Application Management Software (AMS). El AMS además de controlar el ciclo de vida de la ejecución MIDlets (inicio, pausa, destrucción), controlará el ciclo de vida de las aplicaciones que se instalen en el móvil (instalación, actualización, ejecución, desinstalación).
Durante su ciclo de vida un MIDlet puede estar en los siguientes estados:
Será el AMS quién se encargue de controlar este ciclo de vida, es decir, quién realice las transiciones de un estado a otro. Nosotros podremos saber cuando hemos entrado en cada uno de estos estados porque el AMS invocará al método correspondiente dentro de la clase del MIDlet. Estos métodos son los que se muestran en el siguiente esqueleto de un MIDlet:
import javax.microedition.midlet.*;
public class MiMIDlet extends MIDlet {
protected void startApp()
throws MIDletStateChangeException { // Estado activo -> comenzar }
protected void pauseApp() { // Estado pausa -> detener hilos
}
protected void destroyApp(boolean incondicional)
throws MIDletStateChangeException { // Estado destruido -> liberar recursos
}
}
Deberemos definir los siguientes métodos para controlar el ciclo de vida del MIDlet:
startApp()
: Este método se invocará
cuando el MIDlet pase a estado activo. Es aquí donde insertaremos el
código correspondiente a la tarea que debe realizar dicho MIDlet. Si ocurre un error que impida que el MIDlet empiece a ejecutarse deberemos notificarlo. Podemos distinguir entre errores pasajeros o errores permanentes. Los errores pasajeros impiden que el MIDlet se empiece a ejecutar ahora, pero podría hacerlo más tarde. Los permanentes se dan cuando el MIDlet no podrá ejecutarse nunca.
Pasajero: En el caso de que el error sea pasajero, lo notificaremos lanzando una excepción de tipo
MIDletStateChangeException
, de modo que el MIDlet pasará a estado pausado, y se volverá intentar activar más tarde.Permanente: Si por el contrario el error es permanente, entonces deberemos destruir el MIDlet llamando a
notifyDestroyed
porque sabemos que nunca podrá ejecutarse correctamente. Si se lanza una excepción de tipoRuntimeException
dentro del métodostartApp
tendremos el mismo efecto, se destruirá el MIDlet.
pauseApp()
: Se invocará cuando se pause
el MIDlet. En él deberemos detener las actividades que esté
realizando nuestra aplicación.Igual que en el caso anterior, si se produce una excepción de tipo
RuntimeException
durante la ejecución de este método, el MIDlet se destruirá.
destroyApp(boolean incondicional)
: Se invocará
cuando se vaya a destruir la aplicación. En él deberemos incluir
el código para liberar todos los recursos que estuviese usando el MIDlet.
Con el flag que nos proporciona como parámetro indica si la
destrucción es incondicional o no. Es decir, si incondicional
es true
, entonces se destruirá siempre. En caso de que
sea false
, podemos hacer que no se destruya lanzando la excepción
MIDletStateChangeException
desde dentro de este método.Figura 1. Ciclo de vida de un MIDlet
Hemos visto que el AMS es quien realiza las transiciones entre distintos estados. Sin embargo, nosotros podremos forzar a que se produzcan transiciones a los estados pausado o destruido:
notifyDestroyed()
: Destruye el MIDlet. Utilizaremos
este método cuando queramos finalizar la aplicación. Por ejemplo,
podemos ejecutar este método como respuesta a la pulsación del
botón "Salir" por parte del usuario.NOTA: La llamada a este método notifica que el MIDlet ha sido destruido, pero no invoca el método
destroyApp
para liberar los recursos, por lo que tendremos que invocarlo nosotros manualmente antes de llamar anotifyDestroyed
.
notifyPause()
: Notifica al AMS de que el MIDlet
ha entrado en modo pausa. Después de esto, el AMS podrá realizar
una llamada a startApp
para volverlo a poner en estado activo.resumeRequest()
: Solicita al AMS que el MIDlet
vuelva a ponerse activo. De esta forma, si el AMS tiene varios MIDlets candidatos
para activar, elegirá alguno de aquellos que lo hayan solicitado. Este
método no fuerza a que se produzca la transición como en los
anteriores, simplemente lo solicita al AMS y será éste quién
decida.La aplicación puede ser cerrada por el AMS, por ejemplo si desde el
sistema operativo del móvil hemos forzado a que se cierre. En ese caso,
el AMS invocará el método destroyApp
que nosotros
habremos definido para liberar los recursos, y pasará a estado destruido.
Si queremos hacer que la aplicación termine de ejecutarse desde dentro
del código, nunca utilizaremos el método System.exit
(o Runtime.exit
), ya que estos métodos se utilizan para
salir de la máquina virtual. En este caso, como se trata de un componente,
si ejecutásemos este método cerraríamos toda la aplicación,
es decir, el AMS. Por esta razón esto no se permite, si intentásemos
hacerlo obtendríamos una excepción de seguridad.
La única forma de salir de una aplicación MIDP es haciendo pasar
el componente a estado destruido, como hemos visto en el punto anterior, para
que el contenedor pueda eliminarlo. Esto lo haremos invocando notifyDestroyed
para cambiar el estado a destruido. Sin embargo, si hacemos esto no se invocará
automáticamente el método destroyApp
para liberar
los recursos, por lo que deberemos ejecutarlo nosotros manualmente antes de
marcar la aplicación como destruida:
public void salir() { try {
destroyApp(true); } catch(MIDletStateChangeException e) { } notifyDestroyed(); }
Si queremos implementar una salida condicional, para que el método destroyApp
pueda decidir si permitir que se cierre o no la aplicación, podemos hacerlo
de la siguiente forma:
public void salir_cond() { try { destroyApp(false); notifyDestroyed(); } catch(MIDletStateChangeException e) { } }
Podemos añadir una serie de propiedades en el fichero descriptor de la aplicación (JAD), que podrán ser leídas desde el MIDlet. De esta forma, podremos cambiar el valor de estas propiedades sin tener que rehacer el fichero JAR.
Cada propiedad consistirá en una clave (key) y en un valor. La clave será el nombre de la propiedad. De esta forma tendremos un conjunto de parámetros de configuración (claves) con un valor asignado a cada una. Podremos cambiar fácilmente estos valores editando el fichero JAD con cualquier editor de texto.
Para leer estas propiedades desde el MIDlet utilizaremos el método:
String valor = getAppProperty(String key)
Que nos devolverá el valor asignado a la clave con nombre key.
A partir de MIDP 2.0 se incorpora una nueva función que nos permite realizar peticiones que se encargará de gestionar el dispositivo, de forma externa a nuestra aplicación. Por ejemplo, con esta función podremos realizar una llamada a un número telefónico o abrir el navegador web instalado para mostrar un determinado documento.
Para realizar este tipo de peticiones utilizaremos el siguiente método:
boolean debeSalir = platformRequest(url);
Esto proporcionará una URL al AMS, que determinará, según el tipo de la URL, qué servicio debe invocar. Además nos devolverá un valor booleano que indicará si para que este servicio sea ejecutado debemos cerrar el MIDlet antes. Algunos servicios de determinados dispositivos no pueden ejecutarse concurrentemente con nuestra aplicación, por lo que en estos casos hasta que no la cerremos no se ejecutará el servicio.
Los tipos servicios que se pueden solicitar dependen de las características
del móvil en el que se ejecute. Cada fabricante puede ofrecer un serie
de servicios accesibles mediante determinados tipos de URLs. Sin intentamos
acceder a un servicio que no está disponible en el móvil, se producirá
una excepción de tipo ConnectionNotFoundException
.
En el estándar de MIDP 2.0 sólo se definen URLs para dos tipos de servicios:
tel:<numero>
Por ejemplo, podríamos poner:
tel:+34-965-123-456.
http://www.jtech.ua.es/prueba/aplic.jad
Si como URL proporcionamos una cadena vacía (no null
),
se cancelarán todas las peticiones de servicios anteriores.