Tutorial: herramientas de programación JAVA de dispositivos móviles
Versión: 1.0, Septiembre, 2006

Autor: Manuel Gómez Olmedo
Web: http://decsai.ugr.es/~mgomez, Mail: mgomez@decsai.ugr.es
Decsai

(C) Dpto. de CCIA
Web: http://decsai.ugr.es

Introducción

En este tutorial se inicia al alumno en el uso de la herramienta KToolbar, una de las herramientas disponibles para la realización de aplicaciones para dispositivos móviles. En ella, una vez escrito el código fuente, puede crearse un proyecto que permite compilar y preparar el código para su ejecución en un emulador apropiado. De hecho, el producto final de esta herramienta puede ser instalado, de forma directa, en el dispositivo móvil en que vaya a emplearse. Así no tendremos que descargar nada, ni pagar....

Índice

  1. Herramientas necesarias. Instalación

  2. MIDlets y MIDlet suites

  3. Ejecución de MIDlets

  4. Uso de KToolbar

Herramientas necesarias. Instalación

Para poder compilar y ejecutar aplicaciones para dispositivos móviles necesitamos disponer de las siguientes herramientas:

MIDlets y MIDlet suites

Las aplicaciones JAVA que se ejecutan en dispositivos que implementan MIDP se denominan MIDlets. Un MIDlet consta de al menos una clase JAVA, que debe derivar de la clase base abstracta javax.microedition.midlet.MIDlet. El tiempo de ejecución de un MIDlet viene controlado por una serie de métodos definidos en dicha clase y que, forzosamente, todas las clases derivadas deben implementar.

Un grupo de MIDlets relacionados pueden agruparse en un MIDlet suite. Todos los MIDlets de un suite se agrupan e instalan en un dispositivo como si fuesen un único elemento, de forma que sólo pueden desinstalarse y eliminarse en conjunto. Los MIDlets agrupados en un suite comparten tanto recursos estáticos como dinámicos:

Como ejemplo de la forma en que los MIDlets de un suite comparten recursos, supongamos un suite que contiene una clase denominada Contador, destinada a mantener la cuenta del número de instancias de MIDlets del suite que se ejecutan en cada momento.

public class Contador {

    // Contador del numero de instancias

    private static int instancias;

    public static synchronized void incrementar(){
       instancias++;
    }

    public static synchronized void decrementar(){
       instancias--;
    }

    public static int obtener(){
      return instancias;
    } 
}

Una única instancia de esta clase se cargará en la máquina virtual, sin importar cuántos MIDlets de los que integren el suite están en ejecución en la máquina virtual. Esto significa que el mismo dato miembro estático instancias será usado por todos los MIDlets, por lo que los métodos incrementar y decrementar afectarán al mismo contador. En este caso es necesaria la sincronización para asegurar que las operaciones de incremento y decremento sean realmente atómicas.

Es importante indicar que los MIDlets deben empaquetarse antes de poderse instalar en los dispositivos de destino. Todo lo necesario de un suite debe empaquetarse en un archivo JAR. La información del paquete debe incluirse en un archivo de manifiesto. Esta información también estará especificada en otro archivo denominado descriptor de aplicaciones JAVA (JAD: java application descriptor), que se mantiene separado del archivo JAR.

Los archivos de manifiesto y JAD son archivos de texto con la siguiente estructura:

  nombre_atributo: valor_atributo

El nombre del atributo y su valor están separados por dos puntos, siendo el espacio adicional opcional. Todos los atributos que son relevantes para la instalación de MIDlets comienzan por el prefijo "MIDlet-". Una lista completa de atributos, junto con una breve descripción de sus valores asociados, aparece en la tabla siguiente. Los valores en las columnas JAD y JAR indican que el atributo es obligatorio (M), opcional (O) o ignorado (I).

Nombre de atributo
JAR
JAD
Valor y significado
MIDlet-Name
M
M
El nombre del suite integrado en el archivo JAR. El usuario podrá ver este nombre
MIDlet-Version
M
M
El número de versión del suite empaquetado en el archivo JAR. Los número de versión tienen la forma a.b.c, donde valores mayores indican versiones más recientes, teniendo los números de la izquierda mayor precedencia
MIDlet-Vendor
M
M
El nombre del fabricante del suite. Se trata de texto libre.
MIDlet-n
M
I
Atributo que describe cada uno de los MIDlets que integran el suite. El valor numérico n comienza en 1 y sirve para identidicar cada uno de los MIDlets
MIDlet-Description
O
O
Una descripción del suite, para servir de información a los usuarios
MIDlet-Icon
O
O
Un icono que representa al suite durante la instalación o configuración. Se debe tratar de una imagen en formato png (Portable Network Graphics)
MIDlet-Info-URL
O
O
Dirección URL de un archivo que contiene información adicional sobre el suite. El contenido del archivo se mostrará al usuario, de forma que éste pueda decidir si desea o no instalar dicho suite.
MIDlet-Data-Size
O
O
Mínima cantidad de espacio de almacenamiento persistente para que el suite funcione de forma correcta. Se trata de espacio usado por el suite para almacenar datos de larga duración. Se espcifica en bytes. Si no se proporciona este atributo, se asume que el suite no precisa almacenamiento persistente
MIDlet-Jar-URL
I
M
La dirección URL del archivo JAR que contiene el suite
MIDlet-Jar-Size
I
M
El tamaño del JAR que contiene el suite, en bytes
MIDlet-Install-Notify
I
O
Una dirección URL usada para indicar condiciones de error o de éxito en la instalación del suite. Se trata de un atributo no incluido en la especificación MIDP, pero soportado por J2ME.
MIDlet-Delete-Confirm
I
O
Mensaje a mostrar en caso de que el suite vaya a ser borrado del dispositivo. Con este atributo ocurre lo mismo que se indicó respecto al anterior: se trata de un atributo no incluido en la especificación de MIDP
MIDlet-specific attributes
O
O
Los desarrollados de MIDlets pueden proporcionar atributos específicos
MicroEdition-Profile
M
I
La versión de especificación MIDP con la que el suite puede trabajar. Cuando aparecen varias versiones deben separarse mediante espacios. Las versiones especificadas en este atributo se comparan con el valor de la propiedad microedition.profiles para determinar la compatibilidad del suite con la versión de MIDP disponible
MicroEdition-Configuration
M
I
La configuración J2ME precisada por el suite. El valor de este atributo se compara con la propiedad microedition.configuration para determinar la compatibilidad

Se aprecia que alguna información aparece duplicada en el archivo de manifiesto del JAR y en el archivo JAD. Veremos por qué esto es necesario. La función del archivo de manifiesto es indicar al dispositivo el nombre y versión del suite almacenado en el JAR, así como indicar cuáles de los archivos empaquetados se corresponden con cada uno de los MIDlets. Para usar esta información el dispositivo debe descargar el JAR y extraer el archivo de manifiesto. Una vez hecho esto, se pueden mostrar los valores de los atributos MIDlet-Name, MIDlet-Version, MIDlet-Vendor. También de los atributos opcionales MIDlet-Description y MIDlet-Icon. Estos atributos permiten al usuario decidir sobre la descarga. No obstante, el archivo JAR podría ser muy grande, por lo que llevaría mucho tiempo recuperarlo en situaciones de conexiones lentas, por ejemplo. Para evitar este problema, algunos de los atributos del manifiesto, con información extra, se duplica en el archivo JAD. Esto permite descargar inicialmente el archivo JAD en lugar del JAR. De esta forma, se puede mostrar al usuario la información del suite antes de haber descargado el archivo de sus clases. El archivo JAD contendrá información contenida en el manifiesto y alguna particular. Típicamente, los atributos que aparecen en él son:

    MIDlet-Name
    MIDlet-Vendor
    MIDlet-Version
    MIDlet-Description
    MIDlet-Icon
    MIDlet-Info-URL
    MIDlet-Data-Size
    MIDlet-Jar-Size
    MIDlet-Jar-URL

El objetivo de estos atributos, como se indicó con anterioridad, es ofrecer información sobre los MIDlets incluidos en el suite. Supongamos que desarrollamos un suite denominado Notas que permiten al usuario acceso directo a sus notas desde un dispositivo móvil. El suite contiene dos MIDlets: uno para ver las notas y otro para enviar información sobre posibles errores en ellas. Para desarrollar la clase se han usado métodos de utilidad, pertenecientes a la clase Utilidades. En definitiva, las clases involucradas serían las siguientes (teniendo en cuenta que forman parte del paquete desarrollo.moviles):

El manifiesto para este suite podría ser el siguiente:

    MIDlet-Name: Notas
    MIDlet-Vendor: Programas Pepe
    MIDlet-Version: 1.0.1
    MIDlet-Description: Conjunto de midlets para ver notas
    MIDlet-Icon: /desarrollo/moviles/iconos/notas.png
    MIDlet-Info-URL: http://www.programaspepe.com/notas/info.html
    MIDlet-Data-Size: 512
    MicroEdition-Profile: MIDP-1.0
    MicroEdition-Configuration: CLDC
    MIDlet-1: Visualizador,/desarrollo/moviles/iconos/ver.png,desarrollo.moviles.VerNotas
    MIDlet-2: Errores,/desarrollo/moviles/iconos/error.png,desarrollo.moviles.ErrorNotas

En el archivo JAR correspondiente, el archivo de manifiesto aparecería como META.INF/MANIFEST.mf. El archivo JAR también contendría los siguientes archivos:

Conviene hacer algunos comentarios en relación al valor de los atributos y al contenido del archivo JAR:

El archivo JAD asociado a este MIDlet contendrá la siguiente información:

    MIDlet-Name: Notas
    MIDlet-Vendor: Programas Pepe
    MIDlet-Version: 1.0.1
    MIDlet-Description: Conjunto de midlets para ver notas
    MIDlet-Info-URL: http://www.programaspepe.com/notas/info.html
    MIDlet-Data-Size: 512
    MicroEdition-Jar-Size: 10132 
    MIDlet-Jar-URL: http://www.programaspepe.com/notas/Notas.jar

Este archivo contiene la información que la pantalla del dispositivo mostrará al usuario, junto con la dirección URL del archivo JAR. En este caso, los atributos comunes tienen el mismo valor tanto en el archivo de manifiesto como en el archivo JAD. Para que el suite sea portable, es preciso que el archivo JAR esté codificado usando la codificación iso-8859-1, ya que se precisa que todas las implementaciones de MIDP soporten esta codificación.

En tiempo de ejecución los MIDlets pueden acceder a los archivos del JAR asociado al suite y obtener información sobre los valores de los atributos.

Ejecución de MIDlets

Simplemente, como comentario inicial, aunque se comentará más tarde, los MIDlets deben derivar de la clase abstracta javax.microedition.midlet.MIDlet, que contiene métodos destinados a controlar el tiempo de ejecución de los MIDlets. Todos los MIDlets deben tener un constructor público predeterminado (es decir, que no requiera argumentos). La estructura básica de un MIDlet se muestra a continuación:

    public class EjemploMidlet extends MIDlet{
       // Constructor: opcional. Bastaría con el constructor por
       // defecto

       public EjemploMidlet(){
       }

       // Método que iniciará la ejecución del MIDlet

       public void startApp() throws MIDletStateChangeException{
       }

       // Método que interrumpe la ejecución del MIDlet

       public void pauseApp(){
       }

       // Método para finalización del MIDlet

       public void destroyApp(boolean unconditional) 
                throws MIDletStateChangeException{
       }
    }

En todo momento, los posibles estados en que podría estar un MIDlet son:

Inicialmente, cuando se produce la carga de un MIDlet, éste estará en estado pausa. Cuando se produce la llamada al método startApp() se produce el paso al estado activo. En cualquier instante, la plataforma MIDP podría poner a un MIDlet en estado de pausa. Por ejemplo, en los teléfonos móviles esto ocurrirá cuando se detecta una llamada entrante. Esto se realiza mediante una llamada al método pauseApp(). La vuelta al estado activo precisa de una nueva llamada a startApp(). Cuando se precisa la finalización de un MIDlet se usará una llamada a destroyApp(...). Al producirse esta llamada se liberan los recursos que el MIDlet pudiera estar usando, siempre que el argumento de este método sea true. Pero pudiera haber algunas situaciones en que este tipo de finalización no es conveniente; por ejemplo, por haber datos que aún no se han almacenado. En este caso, la llamada al método debería hacerse usando como argumento false. Esto produce el lanzamiento de la excepción MIDletStateChangeException. El código debería estar preparado para capturar la excepción y actuar en consecuencia.

Para ilustrar el ciclo de vida un MIDlet y la forma en que se controla, a la vez que mostrar el funcionamiento de las herramientas de generación de MIDlets, vamos a considerar un ejemplo sencillo de MIDlet, caracterizado por:

Como no se sabe aún lo suficiente como para construir interfaces de usuario (aunque pronto se sabrá), todos los mensajes se enviarán a la salida estándar. Se incluye a continuación el código correspondiente, y seguidamente se verá cómo generar el suite, tanto de forma manual como automática.


import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class EjemploMIDlet1 extends MIDlet{
  Tarea tarea;
  boolean primeraVez;
		
  // Constructor por defecto de la clase
  public EjemploMIDlet1(){
    String color=getAppProperty("color");
    System.out.println("\n\n\nConstruido MIDlet con color: "+color);
    primeraVez=true;
  }

  // Metodo para iniciar el funcionamiento del MIDlet
  public void startApp() throws MIDletStateChangeException{
    System.out.println("Metodo startApp");

    if (primeraVez == true){
      System.out.println("Ejecucion la primera vez.......");
			System.out.println("Se inicia cuenta larga para permitir pausar");
      tarea=new Tarea();
      tarea.start();
      primeraVez=false;
    }
    else{
      // Se reanuda la ejecucion tras salir de la pausa
      System.out.println("Llamada tras pausa..... Valor de i: "+tarea.getI());
      tarea.continuar();

      // Se destruye el midlet cuando la tarea termine
      if (tarea.getFinalizado() == true){
         destroyApp(true);
      }
    }
  }

  // Metodo para detener el funcionamiento del MIDlet
  public void pauseApp(){
    System.out.println("Metodo pauseApp");
    System.out.println("Valor de i en el momento de la pausa: "+tarea.getI());
    tarea.interrumpir();
  }

  // Metodo para destruir el MIDlet
  public void destroyApp(boolean condition){
    System.out.println("Metodo destroyApp. Condicion: "+condition);
    
    if (condition == true){
       // Se indica la finalizacion del midlet
       notifyDestroyed();
    }
  }
}

class Tarea extends Thread{
  int i;
  boolean interrumpido;
  boolean finalizado;

  Tarea(){
    i=0;
    interrumpido=false;
    finalizado=false;
  }

  public void run(){
    for(; i < 9000000; ){
      if (interrumpido == false){
        i++;
      }
      else{
        try{
          sleep(100);
        }
        catch(InterruptedException e){
          System.out.println("Problema al dormir hebra....");
        }
      }
      //System.out.println("Valor de i: "+i);
    }

    // Se finaliza la tarea
    finalizado=true;
  }

  // Metodo para obtener el valor de i
  int getI(){
    return i;
  }

  // Metodo para interrumpir
  void interrumpir(){
    interrumpido=true;
  }

  // Metodo para continuar
  void continuar(){
    interrumpido=false;
  }

  // Metodo para acceder al valor de finalizado
  boolean getFinalizado(){
    return finalizado;
  }
}

El código completo de este MIDlet puede descargarse directamente desde aquí: EjemploMIDlet1.java Se trata de un MIDlet muy sencillo, que permitirá practicar con los procedimientos de generación de MIDlets, así como los posibles estados en que puede encontrarse un MIDlet en ejecución: pausa, activo y destruido.

Uso de KToolbar

Seguiremos el proceso completo de trabajo sobre el MIDlet incluido anteriormente. El primer paso consistirá en ejecutar la herramienta KToolbar. Esta aplicación se encuentra en la instalación de Wireless Toolkit. Para ejecutarla haremos lo siguiente (si no funciona, avisad al profesor.......).

ktoolbar

La ventana principal de KToolbar tiene la siguiente aparencia:

Ventana principal de KToolbar

Crearemos un proyecto de suite, en el que incluiremos el MIDlet generado con anterioridad. Para ello se pulsa en el botón New Project. Al pulsar sobre él aparecerá la siguiente ventana:

Generación de nuevo proyecto

Como se ve, hemos rellenado la información de ambos campos de texto. Como nombre del proyecto podemos elegir cualquiera, sin necesidad de coincidir con el nombre de la clase principal del MIDlet. En el segundo campo de texto hemos de especificar el nombre de la clase principal del suite que queremos ejecutar en el emulador. En este caso es EjemploMIDlet1. En cuanto se pulsa el botón Create Project aparecerá una nueva ventana donde aparecen informaciones relativas al MIDlet.

Ventana de propiedades

Al mismo tiempo, en la ventana principal de KToolbar han aparecido varios mensajes:

Ventana principal, mensajes de advertencia

Estos mensajes indican que se ha creado la estructura de directorios necesaria para el proyecto, y que las clases que queramos formen parte del MIDleet habrá que ubicarlas en el directorio src; recursos adicionales (como iconos) irán en el directorio res y las librerías (si las hubiera) en el directorio lib. Como se ve, el directorio del proyecto se crea en el directorio de instalación de la herramienta, bajo el subdirectorio apps. En nuestro caso, la ruta completa será $HOME/java/WTK2.2/apps/ejemplo1. Nuestro primer MIDlet usará un icono muy sencillo (descargadlo aquí: iconoPeq.png).

Por tanto, lo primero que haremos será copiar el archivo con la clase asociada al MIDlet en el directorio $HOME/java/WTK2.2/apps/ejemplo1/src/. El icono se ubicará en el directorio $HOME/java/WTK2.2/apps/ejemplo1/res/. Dentro de este directorio crearemos a su vez el directorio icons. Y en él será donde se guarde el archivo png del icono. Para indicar que el icono del MIDlet es este, debe modificarse la información sobre el icono. Para ello se pulsa el botón Settings de KToolbar. Aparecerá la venta siguiente:

Ventana de propiedades del proyecto

Aprovechamos ahora para indicar que deseamos que el MIDlet sea conforme a MIDP1.0. Para ello se selecciona esta opción de las disponibles al pulsar sobre Target Platform. Para indicar el icono asociado, se selecciona la pestaña MIDlets. Una vez hecho esto aparecerá

Ventana de propiedades del proyecto, pestaña MIDlets

Para poder cambiar el icono (el valor presente en la figura anterior se introduce por defecto), basta con seleccionar la línea con los datos (la fila que aparece rellena), con lo que se marcará con un color diferente para indicarnos que está seleccionada:

Ventana de propiedades del proyecto, fila seleccionada

Ahora basta con pulsar sobre el botón Edit, lo que dará lugar a la aparición de la ventana de edición:

Ventana de propiedades del proyecto, editar datos

Sobre esta ventana, en el recuadro Icon escribiremos /icons/iconoPeq.png, tal y como se aprecia a continuación:

Ventana de propiedades del proyecto, icono cambiado

Al finalizar se pulsa sobre el botón Aceptar de la ventana de edición. Con esto queda registrado el cambio de la propiedad correspondiente al icono del MIDlet:

Ventana de propiedades del proyecto, propiedad fijada

Y pulsando sobre el botón OK desaparece la ventana de propiedades. Esto es todo lo necesario para poder generar y ejecutar el MIDlet. En primer lugar, procedemos a generar las clases a partir del código fuente. Para ello se pulsa el botón Build. Si todo ha ido bien, aparecerá un mensaje indicado que la generación se realizó de forma correcta. En caso de haber errores, se mostrarán en la ventana principal de KToolbar.

generación completa

En cuanto se ha generado el MIDlet, podemos ejecutar mediante el botón Run. Al pulsarlo, aparecerá el emulador con el MIDlet listo para ejecución.

MIDlet ejecutando

Para lanzar la ejecución del MIDlet se pulsa sobre la tecla bajo el mensaje Launch. En cuanto se produce esto el MIDlet se crea y el dispositivo invoca al método startApp(). Esto explica los mensajes que se aprecian en la consola de KToolbar:

MIDlet ejecutando

Se muestra el mensaje de traza puesto en el constructor, indicando que la propiedad color no está definida. Arreglar esto se deja para más adelante. También se aprecia la traza ubicada a la entrada del método startApp(), que ha sido invocado por el sistema de gestión de ejecución de MIDlets del dispositivo móvil. Los dos mensajes que aparecen a continuación son trazas que muestran que el MIDlet se ejecuta por primera vez.

Para pausar el MIDlet, tras unos segundos de funcionamiento, basta con actuar sobre el menú MIDlet del emulador telefónico y seleccionar la opción Pause. Al hacerlo el emulador queda de la forma siguiente:

MIDlet pausado

A su vez, en la consola de KToolbar veremos los siguientes mensajes:

MIDlet pausado

Las nuevas líneas de traza indican que se ha invocado al método pauseApp(). Como comentamos previamente, esto ocurrirá al producirse una llamada de teléfono sobre el móvil, por ejemplo. Se observa el valor de la cuenta en el momento en que se produjo la pausa. Para continuar ejecutando el MIDlet (lo que ocurriría al finalizar la llamada), forzamos la salida del modo de pausa. Para ello volvemos a actuar sobre el menú MIDlet, seleccionado la opción Resume. Esto hace que aparezcan nuevas líneas de traza indicando que se ha producido una nueva llamada al método startApp(), pero sin tratarse de la primera llamada al mismo.

MIDlet activado de nuevo

Por su parte, del emulador desaparece la indicación de llamada entrante:

MIDlet activado de nuevo, terminal ok

Si se observa el código del método startApp(), se aprecia que la destrucción del MIDlet sólo se producirá en el caso en que ya haya finalizado la tarea de cuenta. Para asegurarnos que esto ocurre, tras la salida del modo de pausa esperaremos unos segundos, se genera una nueva pausa (pulsando, como se indicón previamente, sobre la opción Pause del menú MIDlet) para, a continuación forzar, mediante Resume (en el menú MIDlet), una nueva llamada a startApp() una vez finalizada la cuenta. Cuando esto ocurra, tendremos:

MIDlet destruido

Sólo cuando el MIDlet haya sido destruido de forma completa podrá volver a lanzarse de forma correcta.

EJERCICIO 1: probad qué ocurre cuando se intenta lanzar de nuevo la ejecución del MIDlet antes de que haya finalizado completamente la tarea de cuenta. Justificad el comportamiento observado.

Cuando se usa destroyApp(false) permite que el método compruebe si se cumplen las condiciones necesarias para finalizar o no. En caso de no cumplirse, puede generar una excepción del tipo MIDletStateChangeException, lo que genera a su vez una nueva llamada startApp().

EJERCICIO 2: se propone que modifiquéis el código del MIDlet anterior de la forma siguiente:

En nuestro caso, el MIDlet usa un valor de atributo (color). Si se observa el código, se comprobará que al imprimir su valor se muestra null. Si se intenta manejar esta propiedad, sin haberla especificado de forma explícita, puede ocurrir un error como el mostrado a continuación:

Error de ejecución

Pero, ¿cómo se define este atributo?. Para ello usaremos el botón Settings. Cuando aparezca la ventana de propiedades del MIDlet, seleccionaremos la opción User Defined, que permitirá definir atributos propios. Sobre esta ventana, pulsaremos el botón Add.

Agregar propiedad

Al pulsar sobre Add aparecerá la ventana que hemos de usar para definir el valor de la propiedad deseada:

Nueva propiedad

Se escribe el nombre de la propiedad (color) y se pulsa sobre Aceptar. Con esto la ventana de propiedades queda de la siguiente forma:

Definir valor para nueva propiedad

Se pincha sobre el campo de texto en blanco y se introduce el valor deseado:

Valor definido

Pulsamos OK y ya podemos volver a ejecutar el MIDlet. Ahora la traza inicial indica:

Mensajes en ventana principal

EJERCICIO 3: probad con los MIDlets de demo que vienen con la herramienta. Activad la monitorización, activando las preferencias (Edit + Preferences + Monitor) deseadas. Con esto podéis haceros una idea de la capacidad de la plataforma de desarrollo. Haced un pequeño guión con indicaciones sobre vuestra opinión al respecto: demos más interesantes, limitaciones, puntos fuertes, etc.