Posteado por: fzapataramirez | Lunes, diciembre 1, 2008

Introducción a J2ME

Una de las tecnologías que se puede utilizar para desarrollar aplicaciones es la que nos brinda sun con su edición reducida de java J2ME (Java 2 Micro Edition).

El J2ME, es un subconjunto de packages, clases, interfaces, etc. del java que se conoce normalmente para desarrollar aplicaciones de escritorio o web, y tiene un tamaño bastante pequeño lo cual permite ejecutarse en dispositivos con capacidad limitada de procesamiento y/o almacenamiento.

Como lo mencione en un post anterior, para desarrollar aplicaciones móviles en J2ME debemos tener en cuenta los perfiles y las configuraciones. Por parte de las configuraciones tenemos las opciones CLDC o CDC y por parte de los perfiles contamos con el MIDP.

Una configuración puede entenderse como un agrupamiento de paquetes y clases. Así, se tendrá una base común para todos los dispositivos que soporten determinada configuración. La configuración CLDC (Connected Limited Device Configuration) es la configuración que menos clases tiene, ya que está pensada para dispositivos con una capacidad de procesamiento y de memoria bastante bajos, como por ejemplo los celulares y PDA. La configuración CDC Connected Device Configuration está pensada para dispositivos con mejores capacidades de procesamiento como televisores, controles remotos, etc.

Por lo anterior, sobra decir que la configuración que se debe utilizar para desarrollar aplicaciones para dispositivos como PDA y celulares es la CLDC, la cual usa una versión reducida de la maquina virtual conocida como KVM (Kilo Virtual Machine, el “kilo” se debe a su diminuto peso).

Por otro lado, los perfiles son clases de alto nivel que complementan a las configuraciones, con el fin de dotar al dispositivo de un entorno de ejecución completo para las aplicaciones. Para dispositivos como celulares y PDAS se debe usar el perfil MIDP (Mobile Information Device Profile), que contiene gran parte de clases de Interfaz de usuario.

En la actualidad contamos con varias versiones de cada configuración y perfil. Así, para la configuración CLDC tenemos la versión 1.0 y la versión 1.1, siendo la principal diferencia entre ellas, el que la versión 1.0 no trabaja con valores de punto flotante, mientras la segunda sí. Esto se traduce en que CLDC 1.0 no admite valores de punto flotante, es decir, que los teléfonos que tengan la versión 1.0 de la configuración CLDC no pueden trabajar con tipos de datos como double, float. Por esta razón, si nuestra aplicación necesita trabajar con datos numéricos que contengan decimales, el primer requisito que debemos tener en cuenta para el dispositivo que ejecutara la aplicación es que tenga la versión 1.1 de CLDC.

Por el lado del perfil MIDP, contamos actualmente con 3 versiones 1.0, 2.0 y 2.1, siendo la más utilizada ahora la 2.0 ya que la 2.1 solo la traen los teléfonos de última generación.

El primer paso a dar a la hora de comenzar con el desarrollo de una aplicación móvil, es elegir la plataforma de desarrollo. Personalmente me he inclinado por Netbeans ya que ofrece una gran facilidad a la hora de diseñar la parte visual de nuestra aplicación. También he escuchado que eclipse ha progresado bastante en cuanto a eso pero nunca lo he probado, por lo que a continuación indicaré a grandes rasgos que se necesita para desarrollar una aplicación j2ME en Netbeans.

Netbeans cuenta con varias “distribuciones”, según el tipo de aplicación que tengamos pensado desarrollar. Para desarrollar aplicaciones móviles, necesitamos descargar la versión “All” que podremos encontrar en la página http://www.netbeans.org/downloads/index.html. Esta es la versión ideal ya que trae incorporado todo lo necesario para el desarrollo de aplicaciones móviles. Sin embargo, si y ase cuenta con una versión diferente de netbeans instalada, se puede descargar la última versión del Wireless Toolkit para adicionarle el soporte para j2me.

El primer requisito para instalar Netbeans, es tener instalado el JDk en la maquina, el cual se puede descargar gratuitamente de la pagina de sun. Es recomendable tener la última versión del JDK, incluyendo los updates ya que así podremos acceder a las correcciones de bugs detectadas y/o nuevas funcionalidades.

Una vez instalado el Netbeans, solo tenemos que abrir el IDE, hacer clic en File > New Project y seleccionar la opción MIDP Application que se encuentra en la categoría Mobility, tal y como se muestra a continuación.

 

 

 

netbeans

Luego debemos seleccionar la versión del MIDP y del CLDC que deseamos utilizar en nuestro proyecto. Es importante tener en cuenta este dato antes de comenzar el proyecto ya que una mala elección de estas versiones podría dejar inutilizable la aplicación en los dispositivos reales.

En este paso, también debemos elegir el dispositivo (device) con el que queremos ejecutar la aplicación. Esta opción es para indicarle a Netbeans que cada que nosotros ejecutemos nuestra aplicación con el comando ejecutar de netbeans, abra automáticamente el emulador del dispositivo seleccionado. Esta elección no afecta para nada la aplicación final ni implica que la aplicación solo podrá ejecutarse en dicho dispositivo, únicamente es una elección para emular el comportamiento de la misma en tiempo de desarrollo.

Por defecto, Netbeans trae instalados unos emuladores básicos con los que podemos probar el funcionamiento de nuestras aplicaciones. Sin embargo, cada fabricante de dispositivos, provee emuladores más específicos para poder emular la ejecución de la aplicación en un entorno más real. Así por ejemplo si la aplicación está siendo desarrollada para celulares Nokia 6100, tenemos la posibilidad de descargar un emulador de dicha plataforma para tener una ejecución mucho más realista.

Aquí vamos entendiendo que eso de la portabilidad de java no deja de ser pura teoría, porque la misma aplicación puede comportarse de manera diferente en distintos dispositivos. Para que necesito yo una aplicación que se pueda instalar y ejecutar en cualquier dispositivo con CLDC 1.1, si funcionalmente es inservible en algunos? A lo que me refiero es a que es probable que la aplicación si instale en todos los dispositivos CLDC 1.1, pero que solo funcione correctamente en los Nokia.

Esto se debe, porque además de la configuración y el perfil que soporte cada dispositivo, debemos tener en cuenta los JSR (Java Specification Request) con los que este equipado, y peor aún, cada fabricante de dispositivos puede implementar de manera diferente los JSR.

 Los JSR son un conjunto de clases que complementan las librerías bases ofrecidas por la configuración. Para que quede más claro, recordemos que todos los dispositivos que soporten la misma versión de CLDC van a tener las mismas librerías o clases bases. Pero si somos realistas, cada teléfono es diferente, algunos pueden tener bluetooth, otros pueden tener capacidad de acceso a Internet mientras que otros no. Cada JSR ataca funcionalidades especificas, por lo que podremos encontrar un jsr que se encarga de la funcionalidad del bluetooth, otro JSR que se encarga de las peticiones HTTP y SOAP (clientes de servicios web), etc.

Por lo anterior, es necesario verificar que librerías adicionales estamos utilizando (JSR) estamos utilizando ya que eso será un requisito mas para el dispositivo que ejecute la aplicación. Por ejemplo, si la aplicación móvil consume un web service, estamos utilizando el JSR 172 y será requisito indispensable que el dispositivo cuente con la implementación de dicho jsr ya que de lo contrario la aplicación no funcionara correctamente.

A manera de conclusión entonces, tenemos que las aplicaciones móviles desarrolladas en Java no son tan portables como dice la teoría ya que el correcto funcionamiento de la misma en diferentes teléfonos depende de las librerías que implemente cada celular.

Por ejemplo, supongamos que en la aplicación se implementa un control Tabla con la funcionalidad de scroll horizontal y vertical, el cual se utiliza para mostrar información tabulada. Es posible (y casi certero) que la misma tabla funcione correctamente en unos dispositivos mientras que en otros no funcione el scroll vertical y en otros no funcione ningún tipo de scroll. Esto se debe a que como lo comente anteriormente, cada fabricante puede implementar de manera diferente cada API de java. En este caso especifico, pudiera deberse a que los fabricantes de los dispositivos implementan de manera diferente el método encargado del LAYOUT o “distribución de controles en pantalla”.

Por tal motivo es recomendable:

1. Entender, que en pocas ocasiones la misma aplicación sirve en diferentes tipos de dispositivos.

2. Analizar que versión de CLDC y MIDP se va a utilizar, teniendo en cuenta que eso será requisito para el dispositivo en que se ejecutara la aplicación.

3. Analizar que JSR se están utilizando para incluirlos como requisito del dispositivo que ejecuten.

4. Probar la aplicación inicialmente en el emulador genérico de Netbeans, luego descargar el emulador del dispositivo específico en el que se ejecutara l aplicación (Nokia, Motorola, Blackberry, Sony Ericsson, etc.)

5 Finalmente desplegar la aplicación a los dispositivos reales, estando dispuesto a sorpresitas poco deseadas, porque recuerda, con J2ME, Write Anywhere, Run Once.

 

Suerte !!!

 

 

 

 

Anuncios
Posteado por: fzapataramirez | Viernes, noviembre 14, 2008

Lectura y escritura de archivos en c#

El manejo de archivos en la plataforma .NET se logra mediante la clase Stream que representa un flujo de información (Un archivo es considerado un flujo de datos, al igual que los datos transferidos de un dispositivo a otro, o los datos transferidos por la red mediante TCP/IP).

La clase Stream, es una clase abstracta, por lo que no es posible utilizarla directamente ya que no se puede crear instancias. Lo que se debe hacer es utilizar una de sus clases derivadas que se especializan en el tratamiento de streams para diferentes destinos como por ejemplo FileStream (para el manejo de archivos), Memorystream (para el manejo de datos en memoria), etc.

La primera opción para escribir y/o leer datos de una archivo de texto es utilizar la clase FileStream. Esta clase se encuentra en el namespace System.IO y es una clase derivada de la clase Stream.

El FileStream actúa como un intermediario entre el sistema de archivos y nuestra aplicación, permitiendo realizar de una manera limpia y sencilla operaciones de escritura y lectura en archivos. Para utilizar el FileStream, lo primero que se debe hacer es crear una instancia que apunte al archivo deseado. Para esto se tiene la opción de utilizar alguno de los constructores ofrecidos, tal y como se muestra a continuación:

clip_image002

Como se ve en la figura anterior, es un constructor bastante tedioso de utilizar porque recibe una cantidad considerable de parámetros. Para contrarestar esto, tenemos la posibilidad de utilizar alguno de los métodos ofrecidos por la clase utilitaria File, que nos permite obtener un FileStream con parámetros específicos de acuerdo al método utilizado para obtenerlo. A continuación se muestra un ejemplo:

clip_image004

En el ejemplo anterior se utilizó el método Create el cual crea el archivo en la ruta especificada y en caso de que el archivo exista, es sobrescrito. Estos métodos ofrecidos por la clase File, facilitan el desarrollo de aplicaciones con manejo de archivos y permite una mejor lectura de código.

Para escribir datos en un archivo de texto, se utilizan los métodos Write y WriteByte. El primero recibe como parámetro un vector de bytes que representa la información a almacenar, mientras el segundo recibe como parámetro un solo byte para ser escrito. Elegir cual de los dos métodos utilizar depende del escenario, ya que cuando se va a escribir gran cantidad de información, no es recomendable escribir byte a byte porque se ejecutaría muchas veces el mismo ciclo sino que por el contrario, es conveniente escribir varios bytes a la vez.

A continuación se muestra un ejemplo de la operación de escritura con ambos métodos:

clip_image006

Se puede observar como se utiliza la clase Encoding para formatear la cadena en UTF8 y obtener el array de bytes que se debe escribir en el archivo. De la misma manera se puede codificar el texto a escribir en otro formato como por ejemplo ASCII, Unicode, etc.

Es importante mencionar que todo stream se debe cerrar al finalizar la operación que se realiza sobre el, ya que de lo contrario el archivo quedaría bloqueado y no se liberarían recursos del sistema operativo. Adicionalmente, el método Flush, se utiliza para indicarle a la clase FileStream, que escriba en el archivo físico lo que se ha escrito con el método WriteByte o con el método Write hasta el momento ya que de lo contrario, dichos cambios se verían reflejados únicamente cuando se cierre el Stream. Este método seria útil cuando queremos que se escriba el texto en el archivo y no deseamos cerrar el Stream porque necesitamos seguir haciendo operaciones de escritura, además de ser una buena práctica siempre utilizarlo aun cuando parezca ser innecesario.

La implementación de la misma funcionalidad con el otro método, seria como se muestra a continuación.

clip_image008

Como se ve en el ejemplo anterior, con estos métodos, el programador tiene un control muy preciso sobre la información que desea escribir en el archivo, tanto, que hasta puede llegar a ser engorroso en algunas ocasiones.

Afortunadamente, la plataforma .NET ofrece otras maneras de administrar Streams para hacerle la vida más fácil al desarrollador. Una de esas maneras es utilizar la clase StreamWriter para escribir en archivos y StreamReader para leer desde archivos. Es necesario tener en cuenta que estas clases están orientadas a obtener caracteres como salida a diferencia de las clases que heredan de Stream que están orientadas a obtener bytes.

Con la clase StreamWriter, solo nos debemos preocupar por utilizar el método Write o WriteLine para escribir datos en un archivo de texto. Estos métodos reciben tipos de datos nativos del lenguaje como por ejemplo int, bool, decimal, float, string, char, etc. Y utiliza el codificador indicado cuando se instancia la clase para codificar los caracteres de salida al archivo de texto. En caso de utilizar un constructor que no especifique algún Encoding, por defecto esta clase opera con el UTF8.

clip_image010

Como se ve en el ejemplo anterior, las líneas de código son mucho mas sencillas que las vistas en el ejemplo del FileStream. Esto es porque la clase StreamWriter es una clase especializadas para el trabajo de Stream hacia archivos de texto y nos evita el trabajo de tener que obtener la representación en bytes del valor que queramos escribir.

La diferencia entre el método Write y WriteLine, es que el segundo inserta un salto de línea al final de los datos ingresados, haciendo que la próxima vez que se quiera insertar, se hará en la siguiente línea.

Aunque la clase StreamWriter no hereda de la clase Stream, si utiliza en su implementación un Stream en el cual escribe o lee secuencias de caracteres. Por eso, esta clase en su interior, se encarga de crear y utilizar el Stream necesario para apuntar al archivo de texto representado por la ruta que se utilizo en el constructor del ejemplo. Sin embargo, esta clase también tiene otro constructor donde se le puede pasar un Stream que tengamos creado en lugar de la ruta, como se ve a continuación:

clip_image012

El complemento del objeto StreamWriter es el objeto StreamReader, cuyo principal objetivo es facilitarnos las tareas de lectura de cadenas de caracteres. Con este objeto, nos podemos despreocupar de esas tareas de bajo nivel para poder obtener un código mucho mas limpio y fácilmente legible.

clip_image014

En el ejemplo se ve como fácilmente se puede leer información de un archivo de texto utilizando el método ReadLine del objeto StreamReader. Este método, simplemente lee la línea siguiente teniendo en cuenta la posición actual del puntero del archivo.

Es importante aclarar que utilizando los streams vistos hasta el momento se pueden leer y escribir en archivos de texto cadenas de caracteres, es decir, texto plano. Esto implica que la información escrita en los archivos podrá ser vista y entendida por cualquier persona, ya que se guarda tal y como se encuentra en un principio.

Lo anterior puede llegar a ser un problema en los casos en que se elige utilizar archivos de texto como repositorio de datos de una aplicación (aunque esto es poco recomendable, habrán casos en los pueda llegar a ser necesario), y digo que es un problema porque podría haber información que no queremos que cualquier persona la pueda ver y mucho menos entender, sino que quisiéramos que dicha información estuviera almacenada de forma segura.

Una posible opción para esto es almacenando la información en formato binario, es decir, que la información estará almacenada no en formato de texto plano sino en su representación binaria (1s y 0s), lo cual permite tener mas privacidad (OJO, solo mas privacidad, no mucha privacidad ni seguridad. Si el punto fuera como obtener una forma segura de almacenar información tendríamos que mirar los temas de cifrado y hashing). En .NET, el objeto que nos facilita trabajar con Stream de datos binarios es el BinaryWriter y el BinaryReader, los cuales supongo pueden deducir como se utilizan según lo visto anteriormente.

El primero permite escribir datos en un archivo de texto en formato binario mediante el método Write(), el cual.tiene varias sobrecargas entre los cuales acepta varios tipos de dato como por ejemplo int, long, bool, double, decimal, char, string. El segundo permite recuperar información almacenada en formato binario por medio del método indicado. Como así que por medio del método indicado?. Lo que sucede con el BinaryReader es que no tiene un método Read genérico, sino que tiene un Read para cada tipo de dato que puede ser escrito, por ejemplo ReadInt32, ReadBoolean, ReadChar, etc. Esto nos obliga a conocer la estructura del archivo con el que estamos trabajando para poder leer el tipo de dato correcto en la posición correcta.

A continuación se muestra un pequeño ejemplo sobre como utilizar el BinaryReader y el BinaryWriter.

clip_image016

En el ejemplo anterior se observa como se puede escribir varios tipos de dato con el mismo método Write del objeto BinaryWriter ya que tiene varias sobrecargas.

A continuación se muestra como se vería la información en el caso de intentar visualizarla con un editor de texto:

clip_image018

Para leer información binaria se utiliza el objeto BinaryReader y se utiliza “El método indicado”.

clip_image020

En el ejemplo anterior se observa como se utiliza el método de lectura según el tipo de dato que desee leer, lo cual implica que con anterioridad se conozca la estructura del archivo. En este caso, nótese que se leyó exactamente en el mismo orden en el que se escribió la información con el BinaryWriter.

El siguiente es el resultado de la ejecución del código anterior:

clip_image022

Posteado por: fzapataramirez | Martes, octubre 7, 2008

Colecciones de datos en C#.NET (Parte V: Colecciones Genéricas)

A diferencia de las colecciones de datos tratadas en los post anteriores, las colecciones genéricas se encuentran en el namespace System.Collections.Generics ya que son colecciones con la misma funcionalidad que las colecciones no genéricas (las normales, las que están en el namespace System.Collections), con la diferencia que estas están orientadas a trabajar con un tipo de dato especifico.

Con un tipo de dato especifico?. Si así es, porque a diferencia de las colecciones no genéricas en las que se pueden agregar cualquier tipo de elementos y todos son convertidos a System.Object, en este tipo de colecciones solo es posible almacenar elementos de un tipo de dato específico.

Cuando comencé a leer sobre esta nueva característica ofrecida por el Framework 2.0, me preguntaba si el nombre verdaderamente correspondía a la definición de estas colecciones, ya que no entendía el porque llamarlas genéricas si solo era posible almacenar datos del mismo tipo. Por el contrario, parecían ser más genéricas las otras colecciones (las del namespace System.Collections) ya que me permitían almacenar cualquier tipo de dato.

Pero finalmente y después de leer varios artículos y códigos de ejemplo, comprendí el porque del nombre generics y espero poder transmitirlo en este post.

Definitivamente, una colección genérica solo puede almacenar datos de un tipo, pero dicho tipo de dato es definido por nosotros mismos al momento de declararla e instanciarla. Es decir, no hay una clase para diferenciar las colecciones que operan con enteros de las que operan con cadenas de texto ni de las que operan objetos de tipo Empleado (por ejemplo). Absolutamente todas están definidas en la misma clase, lo único que las diferencia es la manera en que se instancia y se declara cada una de ellas. Ese es el secreto del ser genérica. Para entender mejor el concepto de la colección genérica, veamos el siguiente ejemplo:

clip_image002

En el ejemplo se observa la implementación de la colección genérica List, que es la versión genérica del ArrayList tratado en un post anterior. Se puede deducir que la primera colección permitirá almacenar únicamente datos de tipo int, que la segunda almacenara solo cadenas de texto y que la última almacenara objetos de tipo TablaDatos, el cual es un tipo definido en una clase aparte. En las definición de las tres colecciones se utilizo la clase List, es decir, que hay una clase “genérica” capaz de trabajar con el tipo de dato que indiquemos al momento de inicializarla. (Realmente no es cualquier tipo de dato, sino los tipos de datos soportados por la clase).

La diferencia para inicializar una clase genérica de una clase no genérica, es que en la primera se debe utilizar una parámetro adicional después del nombre de la clase y entre los caracteres < y >. Este parámetro debe ser un tipo de datos, que indica el tipo de datos con el que la colección deberá operar.

Así como esta la colecciones List<T>, que es la versión genérica del ArrayList, también existen las colecciones genéricas Stack<T>, Queue<T> y Dictionary<T>, entre otras que son las versiones genéricas de las clases Snack, Queue y HashTable respectivamente.

Un ejemplo de la clase genérica de Stack seria:

clip_image004

Se puede ver como se define una pila en la que únicamente se pueden almacenar datos de tipo entero, lo cual me permite obtener datos de la colección y tratarlos directamente como enteros sin necesidad de hacer algún tipo de cast, lo cual no seria posible con las colecciones no genéricas ya que el elemento es almacenado como un System.Object.

El hecho de que las colecciones genéricas operen con un tipo de dato definido en el momento de su declaración, las hace mucho más eficientes, ya que evita tener que realizar boxing, unboxing y casting de objetos. Adicionalmente los desarrolladores, obtienen un mayor grado de control sobre la información almacenada en la colección ya que en caso de intentar ingresar un objeto de un tipo de dato diferente al establecido en la inicialización de la colección, se arroja una excepción.

Además de esta ventaja que nos brindan las colecciones genéricas, tenemos la posibilidad de crear nuestras propias clases y métodos genérico cuyo comportamiento estará ligado al tipo de dato que se utilice al utilizarlo tal y como sucede con las colecciones. Para lograr esto debemos hacer uso de los “type parameters”, los cuales permiten establecer un parámetro cuyo tipo se desconocerá hasta que se indique en la declaración.

Para comprender mejor el funcionamiento de los “type parameters” que nos permiten crear clases genéricas, he desarrollado una aplicación de muestra que consiste en una clase impresora “genérica”, que es capaz de almacenar en una cola, objetos de un tipo específico (del tipo que se define al momento de definir un objeto de esta clase). Adicionalmente expone un método “imprimir” que se encarga de imprimir en pantalla cada uno de los valores contenidos en la cola. A continuación presento los fragmentos principales del código.

clip_image006

En esta primera imagen, se observa como se ha definido una clase Impresora genérica. Note que en este caso se utiliza la letra T para representar el “type parameter”, sin embargo es posible utilizar cualquier letra o palabra. Mediante este parámetro estamos indicando que al momento de definir e instanciar un objeto de tipo Impresora, se debe pasar como parámetro entre < y > el tipo de datos con el que se desea trabajar.

En el constructor de esta clase, se puede ver como la Queue genérica que tiene la clase Impresora como atributo toma su mismo tipo de dato, es decir, si al crear la clase Impresora indicamos el tipo de dato int, la Queue también tomara el tipo de dato int.

clip_image008

En esta segunda imagen se observa una propiedad que retorna una cola genérica, pero el type parameter para indicar que es una Queue genérica es el mismo utilizado en nuestra clase Impresora, lo cual indica que retornara una Queue del tipo de dato utilizado en la clase.

Finalmente se observa el método imprimir, cuyo objetivo es indicar el tipo de Queue que se esta imprimiendo y además mostrar cada uno de los elementos que contiene.

Como paso final, para comprobar el funcionamiento de la clase Impresora, se crea un objeto de tipo Impresora de enteros, así:

clip_image010

Otra de las ventajas que nos brinda la utilización de Generics, es que los “type parameters” se traducen en tiempo de compilación, lo cual implica que es posible darnos cuenta en el momento que desarrollamos nuestra aplicación, el tipo de datos con el que se esta trabajando en cada instancia de la clase Impresora, como se ve a continuación:

clip_image012

Finalmente, se observa el resultado de la ejecución de la aplicación en consola:

clip_image014

En conclusión, debemos utilizar las colecciones genéricas tanto como sea posible en lugar de utilizar las colecciones no genéricas, ya que las primeras incrementan el rendimiento de la aplicación ya que evita un sin numero de casting, boxing y unboxing lo que permite realizar un mejor manejo de la memoria y procesamiento.

Además podemos crear propias clases y/o métodos genéricos para aumentar la flexibilidad y la posibilidad de reutilizar nuestro código.

« Newer Posts - Older Posts »

Categorías