10. Introducción a Java para MIDs

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.

10.1. Tipos de datos

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.

10.2. Números reales

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.

10.3. Características básicas de CLDC

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.

10.3.1. Excepciones

El manejo de las excepciones se realiza de la misma forma que en J2SE.

10.3.2. Hilos

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).

10.3.3. Colecciones

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.

10.3.4. Wrappers de tipos básicos

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.

10.3.5. Clases útiles

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.

10.3.6. Flujos de entrada/salida

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.

10.3.7. Características ausentes

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.

10.4. MIDlets

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.

10.4.1. Componentes y contenedores

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).

10.4.2. Ciclo de vida

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:

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 tipo RuntimeException dentro del método startApp tendremos el mismo efecto, se destruirá el MIDlet.

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á.

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:

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 a notifyDestroyed.

10.4.3. Cerrar la aplicación

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) {
    }

}

10.4.4. Parametrización de los MIDlets

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.

10.4.5. Peticiones al dispositivo

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:

Por ejemplo, podríamos poner:

tel:+34-965-123-456.

Si como URL proporcionamos una cadena vacía (no null), se cancelarán todas las peticiones de servicios anteriores.