miércoles, 19 de noviembre de 2014

J2ME Tutorial Parte VI

Desarrollando UI con el API de bajo nivel

 OBJETIVOS
Este API es el favorito de los desarrolladores de juegos porque permite tener control total sobre lo que dibujas en la pantalla, a nivel de pixel, y además puedes definir tus propios eventos de entrada. Lo que se gana en flexibilidad, se pierde en portabilidad ya que el resultado final dependerá del dispositivo concreto en el que se implemente.


 Canvas
La clase Canvas es una subclase de Displayable.

Cuando trabajamos a bajo nivel es importante saber las dimensiones del display, ya que éstas varían de dispositivo a dispositivo y por lo tanto, nuestras apliaciones no deben de asumir un determinado tamaño por defecto. La clase Canvas proporciona los métodos getHeight() y getWidth() para obtener el tamaño del display.

La clase Canvas proporciona un método paint(Graphics g) y varios métodos para la gestión de eventos a bajo nivel que las aplicaciones pueden sobreescribir. La clase Canvas es abstracta y por lo tanto una aplicación debe obligatoriamente extender la clase Canvas para poder utilizarla. La clase que extiende la clase Canvas debe proporcionar obligatoriamente una implementación del método paint()

A continuación se muestra el código de un MIDlet que extiende la clase Canvas (EjemploCanvas.java):

import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;


public class EjemploCanvas extends MIDlet {

    private Display  display;
    private MyCanvas canvas;
   
    public EjemploCanvas(){
        display = Display.getDisplay( this );
        canvas  = new MyCanvas();
    }
   
    protected void startApp(){
        display.setCurrent(canvas);
    }
   
    protected void pauseApp(){
    }
  
    protected void destroyApp( boolean unconditional ){
    }
   
    public void exit(){
        destroyApp( true );
        notifyDestroyed();
    }

    // extiende la clase Canvas
    public class MyCanvas extends Canvas {
    
       public MyCanvas(){
       } 
 
       protected void paint(Graphics g){
         g.setColor( 255, 255, 255 );
         g.fillRect( 0, 0, getWidth(), getHeight() );
         g.setColor( 0, 0, 0 );
         g.drawString( "¡Hola!", getWidth()/2, 0, g.TOP | g.HCENTER );
       }
    }
}


El MIDlet anterior funciona, pero si os fijais para finalizar su ejecución no espera ninguna interacción del usuario. Para ello, vamos a ver ahora como se gestionan los eventos con el API de bajo nivel.

La clase Canvas proporciona algunos métodos para gestionar eventos, que las aplicaciones pueden sobreescribir, estos métodos son:


Por ejemplo, si sobreescribimos el método keyPressed, en MyCanvas:
protected void keyPressed( int keyCode ){
  exit();
}

El resultado será que cuando se presione cualquier tecla, finalizará la aplicación.

Del mismo modo que en el API a alto nivel, se puede definir Commands, que se pueden asociar a un objeto de un clase que extiende la clase Canvas, utilizando el método addCommand.


 Graphics
La clase Graphics es similar a la clase java.awt.Graphics de J2SE, y proporciona métodos para geometría en dos dimensiones. Se definen métodos para dibujar texto, imágenes y líneas, y para dibujar y rellenar rectángulos y arcos, enumero a continuación alguno de ellos:

Si os fijais en los ejemplos anteriores, el método paint de la clase Canvas, acepta como único parámetro un objeto de la clase Graphics.

La clase Graphics también nos da soporte para colores a través de varios métodos:
A continuación, se incluye un MIDlet que dibuja dos líneas (LineTest.java)

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class LineTest extends MIDlet {
    private Display display;

    public LineTest() {
        display=Display.getDisplay(this);
    }

    public void startApp() throws MIDletStateChangeException {
        display.setCurrent(new LineTestCanvas());
    }

    public void pauseApp() {
    }

    public void destroyApp(boolean unconditional) {
    }

    class LineTestCanvas extends Canvas {
        public void paint(Graphics g) {
            // Tamaño del área de dibujo
            int width=this.getWidth();
            int height=this.getHeight();

            // Fondo de la pantalla blanco
            g.setColor(0xFFFFFF);
            g.fillRect(0,0,width,height);

            // Líneas en negro
            g.setColor(0x000000);
            // Dibujamos dos líneas (ejes)
            g.drawLine(0,height,0,0);
            g.drawLine(0,height-1,width,height-1);
        }
    }
}

 Imágenes ARGB
Una de los grandes avances que introduce MIDP 2.0 respecto a la versión 1.0, en lo relacionado con el interfaz de usuario a bajo nivel, es la posibilidad de representar imágenes como arrays de enteros. Esto permite a los MIDlets manipular directamente los datos de una imagen. La idea consiste en representar cada pixel de la imagen como un entero. Esto hace un total de 8 bits por pixel empleados de la siguiente manera:
  • alpha (opacidad): 2 bits
  • Nivel de Rojos: 2 bits
  • Nivel de Verdes: 2 bits
  • Nivel de Azules: 2 bits
Ejemplo:(En notación hexadecimal)
0xff00ff00 - Verde totalmente opaco

   0x80ff0000 - Rojo semitransparente
La forma en que MIDP 2.0 ofrece la posibilidad de trabajar con imágenes a tan bajo nivel es a través de la implementación de un nuevo método en la clase Graphics, el método drawRGB(int[] rgbData,int offset,int scanlength,int x,int y,int width,int height,boolean processAlpha). Este método dibuja una serie de puntos definidos cada uno de ellos por un entero (que indica su color y su nivel de transparencia) en una zona determinada de la pantalla. Todos los datos necesarios para dicha representación están contenidos en los parámetros que recibe el método:
  • rgbData: array de enteros en el que está contenida la información de todos los puntos a dibujar. Para obtener este array de enteros a partir de una imagen disponemos del método Image.getRGB(int[], int, int, int, int, int, int)
  • offset: indica la posición del array en la que se encuentra el primer valor ARGB.
  • scanlength: offset entre las posiciones de los puntos pertenecientes a dos filas consecutivas en el array rgbData.
  • x: posición horizontal de la zona donde dibujar la imagen.
  • y: posición vertical de la zona donde dibujar la imagen.
  • width: anchura de la zona en la que dibujar
  • height: altura de la zona en la que dibujar.
  • processAlpha: indica si la imagen tiene "canal Alpha" (true) o si por el contrario todos los pixels son totalmente opacos (false).
MIDlet ejemplo: EjemploARGB.java

lunes, 17 de noviembre de 2014

J2ME Tutorial Parte VII

Multimedia en MIDP 2.0

 INTRODUCCIÓN
En este tutorial estudiamos uno de los aspectos más interesantes de MIDP 2.0: las nuevas capacidades multimedia que ofrece a través de los paquetes javax.microedition.media y javax.microedition.media.control. El nuevo API multimedia que definen estos paquetes es un subconjunto del MMAPI (Mobile Media API) enteramente compatible con el API completo. El API Multimedia constituye una interfaz flexible, simple y potente para el manejo de capacidades multimedia. Mientras que MMAPI permite reproducir y grabar tanto datos de audio como de video, los paquetes aquí estudiados ofrecen un número reducido de acciones relacionados con la reproducción de audio.
Como ya se ha comentado, el hecho de poder trabajar con datos multimedia constituye una de los aspectos más novedosos y ventajosos de MIDP 2.0 frente a la versión 1.0. En los últimos tiempos hemos podido constatar la gran evolución que han experimentado los terminales móviles. Es posible desde cualquier terminal móvil reproducir secuencias de tonos con una calidad infinitamente superior a cualquier dispositivo con algunos meses de antigüedad. Por tanto, el hecho de que MIDP 2.0 responda a esta demanda de una herramienta adecuada para poder explotar estas nuevas capacidades, supone un gran acierto y abre nuevas vías al programador.
Sin embargo, como veremos a continuación, el conjunto de acciones que podemos llevar a cabo puede parecer excesivamente limitado. En este punto debemos recordar que la definición de MIDP 2.0 abarca un rango de dispositivos considerable y que no todos ellos tienen la misma capacidad para reproducir audio o vídeo. Por este motivo MIDP 2.0 se limita a definir cuál es el mínimo que todos ellos deben satisfacer pudiendo, individualmente, en función del tipo de dispositivo y del fabricante, utilizar el MMAPI completo.
Para definir la arquitectura del API multimedia debemos saber que su funcionamiento se basa en los siguientes conceptos básicos:

  • Reproductor (Player): sabe cómo reproducir unos datos multimedia concretos. Por ejemplo necesitaremos uno para reproducir archivos de audio, otro para tonos simples, etc.
  • Controlador (Control): utilizado para modificar el comportamiento de un reproductor concreto. A partir de una instancia de un reproductor podemos obtener su controlador asociado.
  • Gestor (Manager): es el objeto que cohesiona todos los anteriores y sirve como punto de entrada al sistema ya que dispone de métodos estáticos que permiten crear reproductores y/o fuentes de datos.
Figura1
Figura 1: Arquitectura del API Multimedia
Así, una vez expuesta la filosofía básica de este API podemos pasar a analizar cuáles son sus clases y métodos.


 CLASE Manager
Manager es como ya se ha dicho el punto de partida para procesar o reproducir datos multimedia. Se nos ofrecen dos opciones:
  • Reproducción de tonos simples La clase Manager nos ofrece el método playTone que a partir de una serie de parámetros reproduce un tono simple. Este método se explicará con más detalle posteriormente, cuando se vean todos los métodos de la clase Manager.
  • Creación de objetos Player Es este mecanismo el que permite aprovechar toda la funcionalidad que la clase Manager nos ofrece. Podremos obtener objetos Player a partir de un InputStream (cuando tengamos un flujo de bits) o a partir de un localizador multimedia (Media Locator). Los métodos disponibles para realizar estas acciones son los siguientes:


    Manager.createPlayer(java.io.InputStream is, String type)
    Manager.createPlayer(String locator)
    
    Los objetos Player así creados serán utilizados para el control y la reproducción multimedia con la única restricción de que los datos manipulados deben pertenecer específicamente al tipo sobre el que se creó el objeto. Esto es conocido como el content-type de los datos. Algunos ejemplos son:


    • Ficheros "wave audio": audio/x-wav
    • Ficheros AU:audio/basic
    • Ficheros MP3:audio/mpeg
    • Ficheros MIDI:audio/midi
    • Secuencias de tonos simples:audio/x-tone-seq
    Si observamos los dos casos anteriores de creación de objetos Player vemos que en el primero de ellos se indica EXPLÍCITAMENTE el content-type de los datos sobre los que se crea el reproductor. En el segundo de los casos la declaración del tipo de datos es IMPLÍCITA, está incluida en el localizador.
    Más adelante se entrará a analizar más a fondo el interfaz Player.
API de la clase Manager Los métodos de los que dispone esta clase son:
  • createPlayer(InputStream is, String contentType): crea un objeto Player para la reproducción multimedia de los datos ofrecidos por un flujo de bits de entrada. Como ya se ha comentado, el parámetro contentType define el tipo de los datos recibidos. Si tiene un valor null, Manager tratará de determinar el tipo más adecuado. Sin embargo esta operación no resulta trivial en muchos casos, lo que puede significar que no sea posible llevarla a cabo. En estos casos se producirá una MediaException.

  • createPlayer(String locator):crea un objeto Player a partir de un localizador que tendrá el siguiente formato:

    < protocol >:< location >
    
    Donde,

    • protocol: indica el protocolo utilizado para recibir los datos multimedia
    • location: indica la localización de los datos multimedia. Es aquí donde se indica implícitamente el content type de los datos.

    Ejemplo:



      Player p = Manager.createPlayer("http://webserver/music.mp3");


  • getSupportedContentTypes(String protocol): devuelve una lista con los distintos content types soportados por un determinado protocolo. Si el protocolo indicado no es válido el resultado será una lista vacía. Si por el contrario no se especifica ningún protocolo (pasamos un String de valor null) se devuelven todos los tipos soportados por la implementación.




  • getSupportedProtocols(String contentType): devuelve una lista con los protocolos soportados por un determinado content type. Los protocolos devueltos se pueden utilizar directamente en el localizador para crear un objeto Player. Si el argumento contentType es null se devolverán todos los protocolos soportados por la implementación. Si el content type indicado no es válido o no es soportado se devolverá una lista de protocolos vacía.





  • playTone(int nota, int duracion, int volumen): reproduce un tono con una nota, una duración y un volumen determinados. A la hora de utilizar este método debemos tener en cuenta que puede conducir a un considerable consumo de recursos en dispositivos sin soporte hardware para la reproducción de tonos.


  •  EL INTERFAZ Player
    Dado que Player es un interfaz no podemos instanciar objetos directamente sino que, como ya hemos podido ver, debemos obtenerlos a partir de un objeto Manager. Ya sabemos cómo podemos hacer esto así que a partir de ahora nos centraremos en conocer que funcionalidad nos ofrece este interfaz. La acción más sencilla que podemos llevar a cabo es ejecutar el método start() que dará comienzo a la reproducción lo antes posible. Veremos más adelante todos los pormenores de este y del resto de métodos del interfaz.

    Player nos permite no sólo reproducir audio sino que además permite controlar dicha reproducción e incluso su propio ciclo de vida.


    CICLO DE VIDA DEL REPRODUCTOR

    Resulta de gran utilidad definir un ciclo de vida para los reproductores ya que de esta manera el programador será capaz de tener cierto control sobre una serie de operaciones que son susceptibles de consumir gran cantidad de recursos.
    El ciclo de vida de un objeto Player consta de cinco estados:
    • UNREALIZED
    • REALIZED
    • PREFETCHED
    • STARTED
    • CLOSED
    Cuando un Player es creado se encuentra en el estado UNREALIZED. Es este estado el reproductor no dispone de información suficiente para localizar los recursos necesarios para comenzar la reproducción. Una vez haya localizado los datos pasará al estado REALIZED. Por ejemplo, en caso de querer reproducir un fichero de audio de un servidor a través de una conexión http, la transición se produciría al recibir la respuesta del servidor a la petición que previamente se cursó ya que en este punto el reproductor estaría en condiciones de empezar a recibir los datos a reproducir.
    El siguiente estado por el que pasaría es el de PREFETCHED. Se llega a él cuando el reproductor ha recibido suficiente cantidad de datos para comenzar con la reproducción. La utilidad de definir este estado radica en que llegan a él los reproductores que están listos para empezar con la reproducción de datos. De esta manera se reduce el tiempo de latencia desde que se indica que se inicie la reproducción y el instante en que realmente comienza. En este punto, cuando haya dado comienzo la reproducción se llegaría al estado STARTED.

    En la secuencia de estados que acabamos de describir no aparece el estado CLOSED. El reproductor puede llegar a este estado desde cualquiera de los anteriores cuando se indique que no va a ser utilizado nunca más. En este caso el reproductor liberará la mayoría de los recursos que tenía reservados.

    Figura2
    Figura 2: Ciclo de vida de un objeto Player


    El interfaz Player ofrece una serie de métodos que permiten realizar las transiciones anteriormente descritas (en ambos sentidos) a nivel de programador. Además de estos métodos encontramos otros que permiten acceder a la información del reproductor. A la hora de utilizar estos métodos debemos observar cuidadosamente el estado en que se encuentre el objeto Player ya que la información disponible en cada uno de los estados no es la misma.


    API DEL INTERFAZ Player

    Los métodos que en este caso tenemos a nuestra disposición son:

    • addPlayerListener(PlayerListener listener): añade un objeto PlayerListener a este Player de manera que se puedan capturar los eventos que este genere.
      NOTA: la clase PlayerListener se explicará más adelante.

    • close(): cierra el objeto Player y libera los recursos que éste tenía asignados. Cuando el método regresa de su ejecución el reproductor estará en estado CLOSED y no podrá ser usado más. Además se generará un evento que podrá ser capturado por los PlayerListener registrados por el reproductor.

    • deallocate(): libera los recursos escasos o exclusivos que hayan sido asignados al reproductor, como el dispositivo de audio. Al regresar de la ejecución de este método el reproductor estará en el estado UNREALIZED (si el reproductor se encontraba bloqueado en la llamada realize()) o UNREALIZED (en el resto de casos). Por este motivo, si al invocar este método ya se encontrase en uno de esos estados, la petición sería ignorada.

    • getContentType(): indica de qué tipo es el contenido de los datos multimedia asociados a este Player.

    • getDuration(): devuelve la duración de la reproducción. Si no pudiese ser determinada (por ejemplo si se estuviésen reproduciendo datos en tiempo real) se devolvería la constante TIME_UNKNOWN.

    • getMediaTime(): devuelve el media time actual del reproductor. En caso de no poder determinarlo devolvería TIME_UNKNOWN.

    • getState(): devuelve el estado actual del reproductor.

    • prefetch(): el reproductor adquiere los recursos necesarios para la reproducción y procesa la máxima cantidad de datos posible para reducir el tiempo de latencia al realizar el start. Esta última acción será llevada a cabo incluso cuando se invoque este método estando ya en el estado PREFETCHED. De esta manera aseguramos que el tiempo de latencia siempre será mínimo.
      Cuando el método regresa de la ejecución el reproductor se encuentra en estado PREFETCHED. Si al llamar a este método el objeto Player se encontrase en estado UNREALIZED se produciría una llamada implícita a realize(). En cambio, si el estado inicial fuese STARTED la petición sería ignorada.

    • realize(): construye porciones del reproductor sin asignarle en exclusiva los recursos necesarios para la reproducción. Al regresar de la ejecución de este método el reproductor se encontrará en estado REALIZED. Si se ejecuta este método desde los estados REALIZED, STARTED o PREFETCHED la petición será ignorada.

    • removePlayerListener(PlayerListener listener): elimina un objeto PlayerListener asociado al reproductor.

    • setLoopCount(int count): permite indicar el número de veces que un Player debe reproducir su contenido en forma de bucle (al llegar al final de los datos se continúa sin interrupción por el principio). El valor por defecto de todo reproductor es 1. El valor 0 es un argumento no permitido que producirá una excepción en caso de ser utilizado y el valor -1 hará que la reproducción tenga una duración indefinida.

    • setMediaTime(int time): fija el tiempo de reproducción o "media time". Indica en que punto de los datos debe comenzar la reproducción referido al instante inicial (tiempo de reproducción = 0 para el inicio de los datos).

    • start(): inicia la reproducción lo antes posible. Si el reproductor fue detenido con anterioridad por una llamada a stop() al invocar este método se continuará con la reproducción en el punto que se detuvo.
      Al terminar la ejecución de este método el reproductor debe haber comenzado a reproducir los datos y se debe haber generado un evento STARTED. No necesariamente se producirá que el Player se encuentre en el estado STARTED ya que puede ocurrir que la duración sea muy corta o haya muy pocos datos por reproducir.

    • stop(): detiene el reproductor en el instante de reproducción actual (como se dijo anteriormente es a partir de este punto desde donde se reaunudaría la reproducción si invocásemos start()). A la vuelta de este método el reproductor estará en estado PREFETCHED y se habrá generado un evento STOPPED
     EL INTERFAZ PlayerListener
    En el apartado anterior en el que analizamos con detalle el interfaz Player apareció el concepto de PlayerListener. Este interfaz implementa un mecanismo de detección de eventos síncronos generados por los reproductores que nos permiten tener acceso a su estado y control sobre las acciones que ejecuta. Es un mecanismo muy útil ya que de no disponer de él no podríamos saber cómo y cuándo se produce la transición entre estados de un reproductor.

    Para que una aplicación pueda trabajar con este tipo de eventos debe implementar el interfaz PlayerListener y debe asociar un listener a un Player concreto utilizando el método addPlayerListener(PlayerListener listener) que vimos anteriormente. Este interfaz define una serie de eventos definidos como Strings. Sin embargo se permite la definición de eventos propietarios, es decir, eventos definidos por una implementación concreta, aunque estos tendrán una definición diferente para evitar colisiones con los anteriores. Este interfaz consta de un único método:

     EL INTERFAZ Controllable
    Ya pudimos ver en la introducción de este tutorial que uno de los elementos básicos de la arquitectura del API multimedia es el Controlador o Control. Por tanto necesitamos una entidad que nos permita acceder a dicho controlador y dicha entidad será la interfaz Controllable, que nos permite acceder a los controles de los objetos que la implementen (en nuestro caso se tratará del objeto Player).

    Los métodos que encontramos en esta clase son:
    • getControl(String controlType): devuelve el controlador (objeto Control) del objeto que implemente el interfaz. Este método nos permite indicar el tipo de controlador al que queremos acceder. Esto es necesario ya que será más de un tipo de controlador el que implemente la interfaz genérica Control (que veremos más adelante).

    • getControls(): devuelve una lista con todos los controladores del objeto que implemente este interfaz. Una vez devuelta la lista, antes de empezar a trabajar con ellos debemos comprobar de qué tipo es cada controlador.

     EL INTERFAZ Control

    Este interfaz nos permite definir unos objetos que proporciona una agrupación lógica de una serie de funciones de procesado multimedia. Como pudimos ver en la introducción este objeto controlador estará asociado a un reproductor concreto y será utilizado para extender sus funciones de procesado multimedia. La manera de acceder a los controladores es a partir del interfaz Controllable. Así, Player heredará de Controllable de manera que podamos obtener como hemos dicho los controladores asociados al reproductor. Además un mismo reproductor puede implementar más de un tipo de Control.

    MIDP 2.0 define dos subinterfaces de Control:
    Estos dos nuevos interfaces pertenecen al paquete javax.microedition.media.control (que junto al paquete javax.microedition.media conforma el API multimedia de MIDP 2.0).

     EL INTERFAZ ToneControl

    ToneControl es un interfaz que permite la reproducción de secuencias de tonos definidas por el programador. Estas secuencias se definen como una lista de pares (nota - duración). Esta lista se codifica como un array de bytes. Al tratarse de una interfaz no disponemos de un método constructor sino que se debe obtener utilizando el método getControl(String controlType) del interfaz Controllable (o de Player como subinterfaz del anterior )

    El método que nos ofrece este interfaz para el manejo de secuencias de tonos es:
    • setSequence(byte[] sequence): determina el contenido de la secuencia asociada al objeto ToneControl.



      Ejemplo:
      
        ToneControl c = (ToneControl)player.getControl("ToneControl");
        c.setSequence(mySequence);
      
      

     EL INTERFAZ VolumeControl
    Este interfaz que permite manipular el volumen del audio reproducido por un Player. Los aspectos más destacados de VolumeControl son:

    • Fijación del volumen a un valor determinado. Se indicará dicho nivel con un entero entre 0 (silencio) y 100 (volumen máximo).
    • Mute on/off. Sin modificar el nivel de volumen puede o no silenciar la reproducción.
    • Eventos de cambio de volumen. Cada vez que cambie el nivel de volumen de la reproducción se producirá un evento VOLUME_CHANGED que podrá ser capturado por el listener asociado al Player.
    Los métodos que nos ofrece este interfaz para implementar estas acciones son los siguientes:
    • getLevel(): devuelve el nivel de volumen del reproductor. Puede devolver -1 si y sólo si el reproductor está en estado REALIZED y aún no se ha fijado un volumen determinado.

    • isMuted(): indica si el reproductor ha sido forzado o no a permanecer en silencio.

    • setLevel(int level): fija el volumen a un nivel determinado. El nivel indicado debe estar comprendido entre 0 y 100. Si superase por arriba o por abajo estos márgenes se fijaría automáticamente a 100 o 0 respectivamente. La invocación de este método provoca la generación de un evento si se produce correctamente el cambio de volumen.

    • setMute(boolean mute): fuerza al reproductor a estar o no en silencio. Este método no modifica el nivel de volumen pero sí genera un evento VOLUME_CHANGED.

    sábado, 15 de noviembre de 2014

    J2ME Tutorial Parte VIII

    Programación en red en MIDP2.0

     OBJETIVOS
    El soporte que da MIDP2.0 a la programación en red se basa, como en MIDP1.0, en el Generic Connection Framework de CLDC. CLDC Generic Connection Framework define una serie de interfaces para dar soporte a la variedad de tipos de conexiones que nos podemos encontrar en dispositivos móviles, pero no implementa ninguna de ellas. Es en los perfiles donde se debe realizar esta implementación. En MIDP1.0, se daba soporte únicamente a conexiones HTTP, a través de la implementación del interfaz HttpConnection.Sin embargo, MIDP2.0 ofrece nuevas interfaces de conexión de red, lo que supone un conjunto de abstracciones más completo que es de gran utilidad para el programador.

    Este cambio sustancial de MIDP2.0 respecto a MIDP1.0 surge como respuesta a la constante evolución que experimentan tanto los terminales móviles (tecnologías GPRS y UMTS) como las aplicaciones que se diseñan para dichos terminales. Se imponen nuevos requisitos a los mecanismos de conexión que se traducen en la necesidad de un nuevo conjunto de abstracciones que pueda ser utilizado a nivel de programador.
    • Los dispositivos que trabajan en conmutación de circuitos necesitan conexiones basadas en flujo de bits, como por ejemplo el protocolo TCP.
    • Los dispositivos que trabajan en conmutación de paquetes necesitan conexiones basadas en datagramas así que requerirán conexiones como las que ofrece el protocolo UDP.
    • Habrá dispositivos con mecanismos específicos de conexión.
    Como consecuencia MIDP2.0 responde al reto planteado ofreciendo multitud de formas de conexión manejables a nivel del programador y que no introducen un mayor grado de complejidad ya que todas ellas están englobadas dentro del mismo conjunto de abstracciones mencionado anteriormente. 


     CLDC Generic Connection Framework

    En el CLDC Generic Connection Framework, todas las conexiones se crean utilizando el método estático open de la clase Connector. Si no se produce ningún error, este método devuelve un objeto que implementa una de las interfaces definidas en el CLDC Generic Connection Framework e implementadas por MIDP2.0:

    Figura1
    Figura 1: Jeraquía de Intefaces del CLDC Generic Connection Framework

     La interfaz Connection es el nodo raíz en este arbol de jerarquía por lo que el resto de interfaces serán subinterfaces de Connection. En la figura, los interfaces en un recuadro amarillo son parte de CLDC 1.0. El interfaz HttpConnection fue añadido por MIDP 1.0. Los interfaces dentro de los recuadros azules son interfaces añadidos por MIDP 2.0.
    Todas estas interfaces estan contenidas en el paquete javax.microedition.io

    Como vemos hay un número considerable de interfaces y cada una de ellas nos permite establecer un tipo de conexión específico y con un protocolo determinado. Sin embargo J2ME nos permite trabajar con estas interfaces de la manera más sencilla posible. Para ello utilizaremos la clase Connector.



     LA CLASE Connector

    Esta clase nos ofrece un conjunto de métodos que permiten abrir los distintos tipos de conexiones que definen los interfaces de los que hablamos anteriormente.
    • open(String name): crea y abre una conexión con la URL especificada.

    • open(String name,int mode): crea y abre una conexión con la URL especificada con un determinado modo de acceso.

    • open(String name,int mode,boolean timeouts): crea y abre una conexión con la URL especificada con un determinado modo de acceso y especificando si el usuario desea que se produzcan excepciones de timeout.

    • openDataInputStream(String name)/ openDataOutputStream(String name): crea y abre una conexión para un flujo de datos de entrada / salida.

    • openInputStream(String name)/ openOutputStream(String name): crea y abre una conexión para un flujo de datos de entrada / salida.


      El parámtero String name que aparece en la definición de los métodos tiene el formato "protocol:address;parameters".

      Algunos ejemplos de utilización de estos métodos:

      • Conexión HTTP:
        Connector.open("http://www.it.uc3m.es/pervasive");
        
      • Conexión por datagramas:
        Connector.open("datagram://address:port#");
        
      • Conexión por Sockets:
        Connector.open("socket://address:port#");
        
      • Conmunicación con puerto serie:
        Connector.open("comm:0;baudrate=9600");
        
      • Acceso a ficheros:
        Connector.open("file:/miFichero.txt");
        

    El objetivo de tener esta sintaxis, es abstraer al programador de las diferencias que existen entre los diferentes protocolos, de manera que la mayoría del código de una aplicación no se modifica cuando se cambia el protocolo que se está usando.

    IMPORTANTE: Los ejemplos de conexiones anteriores son sólo ilustrativos. CLDC en sí mismo no proporciona ninguna implementación de ningún protocolo, éstas deben de proporcionarse a nivel de perfil. Además un determinado perfil no tiene porque implementar todos los protocolos. Así por ejemplo MIDP 1.0 sólo proporcionaba una implementación del protocolo HTTP, pero no implementaba ni sockets, ni datagramas (aunque algunos fabricantes pudieran incorporar esta posibilidad).
    MIDP 2.0 en cambio añade la implementación de protocolos para establecer conexiones HTTP seguras, por Datagramas y mediante Sockets.

    A continuación vemos los distintos interfaces que implementa MIDP 2.0.



     HttpConnection
    HTTP puede implementarse utilizando protocolos IP (como TCP/IP) o protocolos no-IP (como WAP o i-mode), por este motivo se seleccionó como uno de los protocolos para comunicaciones de red en MIDP (en MIDP 1.0 era el único implementado). Todas las implementaciones de MIDP (tanto 1.0 como 2.0) deben soportarlo, garantizando de esta manera la portabilidad de las aplicaciones, que utilizan este protocolo, entre diferentes dispositivos.

    Se define un nuevo interfaz dentro de la jerarquía del CLDC Generic Connection Framework, el interfaz HttpConnection para el soporte a conexiones HTTP. Este interfaz extiende del interfaz ContentConnection.

    El protocolo HTTP es un protocolo a nivel de aplicación del tipo petición/respuesta, en el que los parámetros de una petición deben establecerse antes de que se envíe la petición. La conexión puede estar en uno de los siguientes tres posibles estados:

    • "Setup": No se ha establecido todavía la conexión.

    • "Connected": Se ha establecido la conexión, la petición se ha enviado y se está esperando por una respuesta.

    • "Closed": La conexión se ha cerrado.

    En el interfaz HttpConnection se proporcionan una serie de métodos que pueden invocarse en cada uno de los estados anteriores:

    En el estado "setup" se pueden invocar los siguientes métodos:

    • setRequestMethod(String method) que establece el método de la petición, que puede ser POST, GET o HEAD. El método por defecto es GET y si el método no es HTTP o la implementación de HTTP está recortada y no lo soporta, se produce una IOException.

    • setRequestProperty(String key, String value) que permite indicar el valor de algunas propiedades HTTP antes de enviar la petición, la propiedad que se quiere establecer se indica en el parámetro key y su valor se establece en el parámetro value.
    Por ejemplo, en el código siguiente se crea una conexión HTTP a la URL http://www.it.uc3m.es/pervasive y se indica que el método de la petición es POST, y que la propiedad HTTP User-Agent tiene el valor Profile/MIDP-2.0 Configuration/CLDC-1.0:
    HttpConnection c = (HttpConnection)Connector.open("http://www.it.uc3m.es/pervasive");
    c.setRequestMethod(HttpConnection.POST);
    c.setRequestProperty("User-Agent", "Profile/MIDP-2.0 Configuration/CLDC-1.0");
    

    La transición entre el estado "setup" y el estado "connected" se produce cuando se invoca algún método que precisa enviar o recibir datos al servidor con el que se establece la conexión. Algunos de los métodos que proporciona la implementación del interfaz HttpConnection y que provocan esta transición, son:

    Cuando la conexión está abierta (se ha pasado al estado de "connected"), se pueden invocar los siguientes métodos:
    A continuación se muestra un ejemplo de código en el que se utiliza la implementación de HttpConnection para leer el contenido de la página http://www.it.uc3m.es/celeste/docencia/cr/hola.txt y mostrarselo al usuario (MIDletHTTPExample.java):

    import java.io.*;
    import javax.microedition.midlet.*;
    import javax.microedition.io.*;
    import javax.microedition.lcdui.*;
    
    /**
     * Un ejemplo de MIDlet para visualizar el contenido de una URL
     * utilizando la implementación del interfaz HttpConnection.
     */
    
    public class MIDletHTTPExample extends MIDlet {
    
      private Display display;
      private String url = "http://www.it.uc3m.es/celeste/docencia/cr/hola.txt";
    
      public MIDletHTTPExample() {
        display = Display.getDisplay(this);
      }
    
      /**
       * Método startApp()
       */
      public void startApp() {
        // Llama al método download para descargar el contenido de la URL
        try {
          download(url);
        } catch(IOException e) {
          System.out.println("IOException: " + e);
        }
      }
    
      private void download (String url) throws IOException {
        StringBuffer b = new StringBuffer();
        InputStream is = null;
        HttpConnection c = null;
        TextBox t = null;
     
        try {
          long len = 0 ;
          int ch = 0;
    
          // Abre una conexión del tipo HttpConnection
          c = (HttpConnection)Connector.open(url);
          
          // Obtiene un stream de entrada, para leer el contenido de la url
          // con la que establece la conexión
          is = c.openInputStream();
          
          // Lee hasta que se cierra la conexión
          while ((ch = is.read()) != -1) {
            b.append((char)ch);
          }
          
          // Se contruye un TextBox con el contenido de la URL
          t = new TextBox("Hola...", b.toString(), 1024, 0);
    
        } finally {
          // Se cierra tanto el stream de entrada
          if (is != null)
            is.close();
          // Se cierra la conexión
          if (c != null) 
            c.close();
        }  
        
        // Se visualiza el TextBox en el Display
        display.setCurrent(t);
      }
      
       /**
        * pauseApp
        */
       public void pauseApp() {
       }
    
       /**
        * destroyApp
        */
       public void destroyApp(boolean unconditional) {
       }
    }
    
    

     HttpsConnection (Conexión HTTP segura)
    HTTPS es la versión segura del protocolo HTTP y consiste básicamente en establecer conexiones HTTP sobre SSL (Secure Sockets Layer). Para poder establecer este tipo de conexiones MIDP 2.0 implementa el interfaz HttpsConnection, que es un subinterfaz de HttpConnection. Por tanto, el interfaz HttpsConnection implementa, además de los métodos y constantes que hereda de HttpConnection, nuevos métodos y constantes que permiten establecer conexiones de red seguras.

    La manera de trabajar con este interfaz es idéntica a la que vimos en el apartado anterior para conexiones HTTP convencionales. La única diferencia es que en este caso utilizaremos un objeto HttpsConnection.

    Los métodos adicionales que aporta el interfaz HttpsConnection son los siguientes:

    • getPort(): devuelve el puerto utilizado por una HttpsConnection.
    • getSecurityInfo(): devuelve la información de seguridad (objeto javax.microedition.io.SecurityInfo )asociada a una conexión establecida correctamente.
      Si la conexión se encuentra en el estado setup será inicializada para establecer una conexión segura con el servidor. El método devolverá el resultado cuando la conexión haya sido establecida y se haya validado el certificado dado por el servidor.
    Otra novedad que introducen las conexiones HTTP seguras son las excepciones CertificateException. Son un subtipo de la IOException y ocurren en la transición al estado "connected". Están relacionadas específicamente con errores ocurridos en el establecimiento de conexiones seguras.

    Ejemplo de utilización del interfaz HttpsConnection: EjemploHttpSeguro.java

    Cabe en este punto introducir un nuevo interfaz especificado por MIDP 2.0 : SecurityInfo. Este interfaz, que veremos a continuación, permite acceder a información relativa a conexiones seguras.


     SecurityInfo
    El interfaz SecurityInfo implementa, como ya se ha dicho, una serie de métodos que permiten acceder a la información asociada a conexiones seguras. Cualquiera de los protocolos que MIDP 2.0 implementa para establecer estas conexiones seguras (Https o sockets seguros sobre ssl) pueden utilizar estos métodos para conocer los parámetros de seguridad de estas conexiones.

    El API del interfaz SecurityInfo se muestra a continuación:

    Ejemplo:
    
    // obtener el objeto SecurityInfo asociado a una conexion segura
    
      SecurityInfo secInfo = conexionSegura.getSecurityInfo();
    
      // Extraer informacion de seguridad
    
      String cipherSuite = secInfo.getCipherSuite();
      String protocol = secInfo.getProtocolName();
      String version = secInfo.getProtocolVersion();
      Certificate certificado =  secInfo.getServerCertificate(); 
     
    

     CommConnection
    El interfaz CommConection define una conexión lógica con un puerto serie. El puerto en cuestión no tiene por qué corresponder a un puerto serie físico sino que puede ser un puerto lógico definido por el sistema operativo. Como corresponde a este tipo de conexiones la información se transmitirá como un flujo de bits en serie.

    Como ya se dijo anteriormente todos los tipos de conexión existentes se abren utilizando el método open(String URL) de la clase Connector. En este caso el String que debemos pasar como parámetro debe tener el siguiente formato:

    comm:< identificador del puerto >[< parámetros opcionales >]
    
    Los parámetros opcionales son los que definen como será la conexión. Habrá definidos unos valores por defecto que serán aplicados en caso de que no se especifiquen a la hora de establecer la conexión.

    Los parámetros son los siguientes:
    Parametro Valor por
    defecto
    Descripción
    baudrate depende de
    la plataforma
    Define la velocidad de transferencia de bits.
    bitsperchar 8 Numero de bits por caracter (7 ó 8).
    stopbits 1 Numero de bits de parada por caracter (1 ó 2)
    parity ninguna Control de errores por paridad. Puede ser par(even), impar(odd), o ninguna(none)
    blocking on Se espera (on) o no (off) a tener el fuffer de entrada lleno para leer
    autocts on Se espera (on) o no (off) a que la línea CTS esté activada antes de escribir
    autorts on Con on actviva la línea RTS cuando el buffer de entrada no está lleno.
    Con off la línea RTS está siempre activada.

    Tanto para establecer la conexión como para especificar los parámetros opcionales que se deseen utilizaremos el método open() de la clase Connector. Para ello el String que pasaremos como argumento debe tener el formato adecuado o de lo contrario se producirá una IllegalArgumentException.

    Para trabajar con conexiones por puerto serie se implementan, además de los métodos heredados de las interfaces Connection, InputConnection y OutputConnection, dos métodos adicionales:
    • getBaudRate(): devuelve la tasa de transferencia de la conexión.
    • setBaudRate(int baud_rate): permite fijar la tasa de transferencia de la conexión. Si el valor especificado no es válido el sistema fijará un valor.

     SocketConnection
    Este interfaz define la abstracción de sockets lo que nos permitirá comunicar distintos procesos entre sí incluso cuando estos se estén ejecutando en distintos dispositivos. Para poder crear un socket necesitaremos un String de conexión genérico que especifique explicitamente un host y un puerto con los que establecer el socket mediante la expresión ya conocida Connector.open(...).

    Ejemplo:
       
      SocketConnection socket = (SocketConnection)Connector.open("socket://< host >:< port >"); 
    
    El interfaz SocketConnection implementa StreamConnection que a su vez proporciona un objeto Connection a la vez que objetos de I/O (un InputStream y un OutputStream) que permiten trabajar con la conexión socket.
    Cada uno de estos interfaces tiene su propio método close(). Así pues, en sistemas que soporten comunicación duplex, debe ser posible cerrar uno de los sentidos de la comunicación si afectar al otro.

    La implementación del interfaz SocketConnection define los siguientes métodos:

    En este enlace puede ver un ejemplo (EjemploClient.java) de un cliente que conecta con un terminal remoto a través de sockets. En dicho terminal remoto (que se supone está ejecutando en el mismo ordenador) estará ejecutando un socket servidor. Este MIDlet podrá ser descargado en el siguiente apartado.
     SecureConnection
    Este interfaz define una conexión mediante sockets seguros que consiste en una conexión basada en SSL (Secure Socket Layer). Esta conexión segura se establece utilizando Connector.open(..) con un String de conexión genérico que especifique, además de un host y un puerto destino, que dicha conexión debe apoyarse en el protocolo SSL.

    Ejemplo:
       
      SecureConnection secureSocket = 
        (SecureConnection)Connector.open("ssl://< host >:< port >"); 
    
    Si tras llamar al método open no se pudo establecer la conexión debido a algún error relacionado con los certificados de seguridad se lanzará una CertificateException

    Como subinterfaz de SocketConnection, el interfaz SecureConnection implementará todos los métodos que se expusieron en el apartado anterior. Además de estos implementa el siguiente método:

    • getSecurityInfo(): devuelve un objeto SecurityInfo con la información relativa a la seguridad asociada a una conexión segura.

     ServerSocketConnection
    El interfaz ServerSocketConnection define un socket servidor o pasivo. La finalidad de este tipo especial de sockets es el poder conectar dos sockets establecidos en distintas máquinas de manera que éstas puedan comunicarse entre sí. De no disponer de un socket servidor ambos terminales deberían establecer un socket genérico de forma simultánea, produciéndose un error en caso contrario. Gracias a ServerSocketConnection dispondremos de un servidor que facilita esta labor. El servidor permanecerá escuchando las peticiones de conexión de los clientes (se tratará simplemente del establecimiento de sockets genéricos). Cuando reciba estas peticiones devolverá una instancia de un SocketConnection y la comunicación entre ambos terminales estará establecida.

    La forma en que el objeto ServerSocketConnection lleva a cabo lo anteriormente explicado es muy sencillo: utiliza el método acceptAndOpen() heredado de la clase javax.microedition.io.StreamConnectionNotifier.

    Ejemplo:
    
        SocketConnection socket = (SocketConnection)serverSocket.acceptAndOpen();
    
    En el ejemplo anterior el servidor permanecerá a la espera hasta recibir una petición de conexión SocketConnection dirigida a la máquina y puerto en los que esté escuchando. Una vez recibida la petición el método terminará su ejecución devolviendo la instancia que representa la conexión mediante sockets.

    El único dato que nos queda por conocer para poder implementar lo hasta ahora visto es cómo crear una instancia de ServerSocketConnection. Para ello utilizaremos, igual que en todos los tipos de conexión vistos hasta ahora, el metodo open() de la clase Connector. La única diferencia respecto a lo visto hasta ahora es que al crear una conexión ServerSocketConnection no se debe especificar el host con el que establecerla ya que será siempre la máquina local:

    ServerSocketConnection serverSocket = (ServerSocketConnection)Connector.open("socket://:< port number >");
    
    Al igual que en los demas tipos de conexiones vistos hasta ahora, podemos omitir el puerto para conseguir una asignación dinámica de un puerto disponible (el sistema gestionará dicha asignación):
    ServerSocketConnection serverSocket = (ServerSocketConnection)Connector.open("socket://");
    
    En este último caso, para conocer el puerto sobre el que se ha establecido la conexión, debemos ejecutar el método getLocalPort() que veremos más adelante.

    Los métodos disponibles en el interfaz ServerSocketConnection son:
    • getLocalAddress(): devuelve la dirección local en la que se ha establecido el socket.
      Esta es la dirección IP a la que otros terminales deben solicitar el establecimiento de un socket tal y como se explicó anteriormente. Dado que las direcciones IP pueden ser asignadas dinámicamente habrá que prestar atención en este aspecto e implementar aplicaciones robustas frente a cambios de la dirección IP del socket servidor.

      ATENCIÓN: Para acceder a la dirección IP de la máquina local se puede utilizar System.getProperty("microedition.hostname").

    • getLocalPort(): devuelve el número de puerto local en el que se ha establecido el socket.

    EjemploServer.java, MIDlet ejemplo en el que se establece un socket servidor esperando peticiones de establecimiento de sockets por parte de los clientes (MIDlet que puede ser descargado en el apartado anterior).


     UDPDatagramConnection
    El interfaz UDPDatagramConnection define una conexión por datagramas de la que se conoce la dirección del punto de terminación ("end point") local. Las características de este tipo de conexión no varían por tratarse de terminales móviles. Al trabajar con el protocolo UDP nos enfrentamos a dos limitaciones inherentes a éste: no se garantiza la entrega y no existe protección frente a duplicados. En comunicaciones sencillas, sin fuertes requisitos de pérdidas o duplicados, las conexiones por datagramas UDP representan un modo eficiente de establecer la comunicación. Sin embargo, en aplicaciones no resistentes frente a pérdidas o duplicados, será necesario trabajar con sockets TCP(utilizando el interfaz ServerSocketConnection que acabamos de ver), penalizando en la cantidad de recursos consumidos (las cabeceras TCP tienen un gran tamaño por lo que el ancho de banda utilizado es mucho menor). Habrá que tener en cuenta entonces cuales son los requisitos de nuestra aplicación para decidirnos por un protocolo u otro estableciendo un compromiso entre fiabilidad y consumo de recursos (en dispositivos limitados como los terminales móviles nos enfrentaremos a nuevas limitaciones).

    Para obtener una instancia de UDPDatagramConnection recurriremos, como ya es habitual, al método open() de la clase Connector. El formato en el que se debe especificar la dirección para establecer la conexión UDP es el mismo que el utilizado para especificar la dirección destino de un datagrama (utilizando Datagram.setAddress()):
    Ejemplo:
    
      UDPDatagramConnection UDPDat = (UDPDatagramConnection)Connector.open("datagram://< host >:< port >");
    
    
    Los métodos disponibles en el interfaz UDPDatagramConnection son:
    • getLocalAddress(): devuelve la dirección local en la que se ha establecido la conexión por datagramas.
    • getLocalPort(): devuelve el puerto local sobre el que se ha establecido la conexión por datagramas.

     LA CLASE PushRegistry
    Esta clase constituye una novedad de MIDP 2.0 respecto a la versión 1.0 y se encuentra también en el paquete javax.microedition.io. El push registry permite que los MIDlets puedan ser lanzados automáticamente sin necesidad de ser inicializados por el usuario. El concepto de push registry no modifica el ciclo de vida del MIDlet, simplemente introduce dos nuevas vías por las que un MIDlet puede ser activado:

      • Activación causada por conexiones de red entrantes (gracias a que MIDP 2.0 implementa nuevos interfaces de conexión como TCP, datagramas UDP e incluso SMS).
      • Activación causada por temporizadores.
    Será el push registry el que gestione la activación de estos MIDlets almacenando las conexiones o alarmas temporales que "despertarán" a un determinado MIDlet.

    Figura2
    Figura 2: Modelo de ciclo de vida de un MIDlet


    El push registry es parte del sistema gestor de aplicaciones (Application Management System o AMS), que es el software residente en el dispositivo móvil que es responsable del ciclo de vida de cada aplicación (instalación, activación, ejecución y eliminación). Sin embargo, a nivel de programación, es decir, en el propio código del MIDlet, también podremos llevar a cabo actuaciones sobre el push registry. Para ello utilizaremos el API de la clase PushRegistry.

    Figura3
    Figura 3: Elementos típicos del Push Registry


    CONEXIONES ESTÁTICAS Y DINÁMICAS.

    Para habilitar la activación "push" los MIDlets deben utilizar el push registry como ya vimos anteriormente. Se pueden llevar a cabo dos tipos de registros:
    • Registros estáticos - Registrar conexiones estáticas, es decir, las que sabemos con toda seguridad que se producirán y que son imprescindibles para el buen funcionamiento del MIDlet.
    • Registros dinámicos - Registrar conexiones dinámicas (las que pueden establecerse, o no, a lo largo de la ejecución del MIDlet) y alarmas. Ésta es la única manera de registrar los temporizadores asociados a un MIDlet.

      Cada uno de estas dos maneras de registrar "eventos activadores" del MIDlet son tratadas con más detalle a continuación.
    REGISTRO DE CONEXIONES ESTÁTICAS.

    El registro de conexiones estáticas ocurre durante la instalación del MIDlet suite. Para ello se deben especificar todas las conexiones a registrar mediantes los atributos MIDlet-Push en el fichero JAD o en el Manifiesto. Cada entrada tendrá el siguiente formato:
    MIDlet-Push-< n >: < ConnectionURL >, < MIDletClassName >, < AllowedSender >
    
      Ejemplo: 
    
        MIDlet-Push-1: socket://:79, com.sun.example.SampleChat, * 
        MIDlet-Push-2: datagram://:50000, com.sun.example.SampleChat, * 
    
    
    Éstas son dos entradas del fichero descriptor que reservarían una conexión socket en el puerto 79 y una conexión por datagramas en el puerto 5000. Si todas las entradas que incluyamos en el descriptor no pueden ser satisfechas (errores de sintaxis en la declaración, reserva de un puerto o una conexión ya ocupados, declaración de un protocolo no registrable ...) se informará al usuario y se le recomendará no instalar el MIDlet suite. Sin embargo si, aún sin poder llevar a cabo todos los registros en el push registry la aplicación puede funcionar de manera aceptable deberá registrar dinámicamente (utilizando el API de la clase PushRegistry) las conexiones que no pudieron ser registradas estáticamente con sus entradas correspondientes en el fichero descriptor.

    Por último, hay que tener en cuenta que la instalación del MIDlet suite con declaraciones como las anteriores en el fichero descriptor reserva las conexiones solicitadas para uso exclusivo de los MIDlets dentro de dicho suite. Mientras que éste esté instalado, cualquier intento por parte de otras aplicaciones de abrir una de las conexiones reservadas será rechazado produciendo uno IOException.
    Si dos MIDlet suites tienen una conexión "push" estática en común no podrán ser instalados simultáneamente y si así sucediera su funcionamiento no sería correcto.


    REGISTRO DE CONEXIONES DINÁMICAS.EL API DE LA CLASE PushRegistry

    Para el registro de conexiones "push" dinámicas utilizaremos los métodos de la clase javax.microedition.io.PushRegistry. Son los siguientes:

  • getFilter(String connection): recupera el filtro asociado a la conexión que se pasa como parámetro. Devuelve un String indicando que conexiones entrantes pueden activar la ejecución del MIDlet o null si esta conexión no fue registrada en el push registry por el correspondiente MIDlet suite.


  • getMIDlet(String connection): recupera el MIDlet al que está asociada la conexión especificada, devolviendo un String con el nombre de clase del MIDlet o null si no hay ningún MIDlet asociado.


  • registerAlarm(String midlet,long time): registra un temporizador para que el MIDlet se ejecute automáticamente.

    NOTA: El PushRegistry acepta sólo un temporizador por MIDlet. La aplicación debe tener un "TimerTask" para notificar a la aplicación los eventos del temporizador en tiempo. Si intentamos registrar un temporizador que ya está registrado el método devolverá el valor anterior de dicho temporizador, es decir, el instante para el que estaba programada la ejecución del MIDlet.
    Si es la primera vez que se hace el registro devolverá cero.


  • listConnections(boolean available): devuelve una lista (String[]) con los nombres de las conexiones registradas por el MIDlet suite instalado. Los Strings devueltos tendrán el formato de una conexión genérica: protocolo, host y puerto .


  • registerConnection(String connection, String midlet, String filter): registra una conexión dinámica que una vez registrada funcionará igual que una conexión pre-alojada por el fichero descriptor. Los atributos son los mismos que en el Push Registration Atribute que se utiliza para registrar conexiones estáticas.


  • unregisterConnection(String connection): elimina del registro la una conexión dinámica, devolviendo el resultado de la operación.

    NOTA IMPORTANTE: Cuando se implemente un MIDlet que al terminar su ejecución registre una alarma para ser activado tras un cierto tiempo, dicho registro debe ser gestionado por un nuevo hilo de ejecución para evitar "dead lock". El motivo es que existe el riesgo de que la ejecución del MIDlet llegue a su fin sin que se haya podido llevar a cabo el proceso de registrar la alarma de activación.

    Ejemplo de MIDlet que registra una alarma para su activación pasado un tiempo determinado: EjemploPush.java