J2ME, FileConnection API. Acceso a tarjetas de memorias desde MIDlets

En este tutorial se va a intentar hacer una introducción al API FileConection de J2ME que nos permite acceder a estas memorias desde aplicaciones para dispositivos móviles.

J2ME, FileConnection API. Acceso a tarjetas de memorias desde MIDlets

En la actualidad, debido a la naturaleza multimedia de los terminarles móviles (teléfonos, PDAs, etc.), es muy normal que admitan tarjetas de memoria externas (SD, MultimediaCard, Memory Stick, etc.) que incrementan notablemente sus capacidades de almacenamiento.

En este tutorial voy a intentar hacer una introducción al API FileConection (JSR75) de J2ME que nos permite acceder a estas memorias desde aplicaciones para dispositivos móviles (MIDlets). Se presupone que el lector ya posee conocimientos básicos de programación (J2ME, MIDP, CLDC), compilación e instalación de MIDLets.

índice de contenidos

Introducción

La gran mayoría de las aplicaciones necesitan guardar información en memorias permanentes cuando la aplicación finalice.

CLDC, nos proporciona a través de las clases del paquete

Text
javax.microedition.io

de un mecanismo estándar de almacenamiento de bloques de bytes (

Text
RecordStore

) que sólo pueden ser leídos, modificados y borrados por el MIDlet que lo creó.

Aunque en la mayoría de las aplicaciones este mecanismo es más que suficiente, en otras se ve la necesidad de tener un tratamiento más completo que permita la lectura, la escritura, el borrardo y la búsqueda de archivos y carpetas.

Entre otras cosas, esto último lo que podemos realizar a través del API FileConnection.

FileConnection es una especificación opcional, por lo que la implementación de esta en cada terminal depende del fabricante.

El API

Es un API ligero y sencillo, definido en el paquete

Text
javax.microedition.io.file

.

javax.microedition.io.file.FileSystemRegistry

A través de esta clase podemos enumerar las raices de sistemas de ficheros presentes en el terminal.

Para hacer una analogía, podría ser como la ventana "MI PC" de los sistemas operativos Windows en donde se muestran las unidades físicas y lógicas presentes sistema.

Además a través de esta clase podemos registrar y desregistrar oyentes (Listener) que serán invocados cuando estas raices cambien. Por ejemplo, cuando se extraiga la tarjeta de memoria.

Text
public static boolean addFileSystemListener(FileSystemListener listener)

Registra un

Text
FileSystemListener

que será notificado cuando varíen las raices de los sistemas de archivos.

Text
public static boolean removeFileSystemListener(FileSystemListener listener)

Desregistra un

Text
FileSystemListener

que será notificado cuando varíen las raices de los sistemas de archivos.

Text
public static java.util.Enumeration listRoots()

Proporciona una enumeración de

Text
java.lang.String

con las cadenas de conexión necesarias para el acceso a cada uno de las raices de los sistemas de ficheros disponibles.

Por ejemplo, si devolviese "SDCard/" podríamos acceder a ese sistema de ficheros mediante la instrucción:

Text
FileConnection fs = (FileConnection) Connector.open("file:///SDCard/")

javax.microedition.io.file.FileSystemListener

Es una interfaz que deberán implementar las clases que deseen ser registradas. Contiene un sólo método que será invocado por el sistema cuando se añada o elimine la tarjeta de memoria.

Text
public void rootChanged(int eventType, java.lang.String rootName)

EventType podrá ser una de las dos constantes estáticas definidas en la interface:

Text
ROOT_ADDED

o

Text
ROOT_REMOVED

javax.microedition.io.file.FileConnection

Se trata de una interfaz que será implementada por cada dispositivo y que nos proporciona decenas de métodos para crear, leer, escribir, consultar y eliminar información relacionada con archivos y directorios. Debido a la extensión de la misma sólo voy a enumerar algunos de ellos, debiendo dirigirse usted a la especificación oficial si necesita más información.

Text
public long availableSize()


Devuelve el número de bytes dispobibles en la memoria.

Text
 

Text
public long directorySize(boolean includeSubDirs) throws java.io.IOException


Devuelve el número de bytes que ocupa un directorio teniendo en cuenta o no los subdirectorios.

Text
 

Text
public void fileSize() throws java.io.IOException


Devuelve el número de bytes que ocupa el fichero.

Text
 

Text
public java.util.Enumeration list() throws java.io.IOException

Devuelve una enumeracion de

Text
java.lang.String de

todos los archivos y carpetas que contiene el directorio actual.
Los directorios se distinguen fácilmente de los archivos normales por que acaban con el caracter "/".

Text
public void setFileConnection(java.lang.String fileName) throws java.io.IOException


Sirve para reutilizar instancias de FileConnection y así no malgastar recursos.

Text
 

Puede usarse el nombre ".." para ir al directorio padre.

Text
public void rename(java.lang.String newName) throws java.io.IOException


Renombra un archivo o carpeta.

Text
public void delete() throws java.io.IOException


Elimina un archivo o carpeta.

Text
public java.io.InputStream openInputStream() throws java.io.IOException


Obtiene un

Text
java.io.InputStream

para leer el archivo.

Text
public java.io.OutputStream openOutputStream() throws java.io.IOException

Obtiene un

Text
java.io.OutputStream

para escribir información en el archivo.

El ejemplo: Explorando las tarjetas de memoria y abriendo un archivo de texto.

En este ejemplo vamos a hacer una aplicación que nos permita navegar por todas las carpetas de todas las memorias así como abrir y mostrar un archivo de texto.

Para el desarrollo de aplicaciones para móviles, yo personalmente utilizo el IDE NetBeans con la extensión Mobility


package autentia.tutoriales.fileconnections;

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

/**
 * Clase principal del ejemplo.
 * Explorador de ficheros para terminales móviles
 * @author Carlos García. Autentia
 * @see http://www.mobiletest.es 
 */
public class FileExplorerMIDlet extends javax.microedition.midlet.MIDlet {
  
	private FileSelector fileSelector;

	
	/**
	* Constructor
	*/
	public FileExplorerMIDlet(){
		this.fileSelector = new FileSelector(this);
	}

	
	/*
	* Punto de inicio del MIDlet
	* @see javax.microedition.midlet.MIDlet#startApp()
	*/
	public void startApp(){
		Displayable current = Display.getDisplay(this).getCurrent();
		
		if (current == null){
			// Averiguamos si el dispositivo implementa la especificación FileConnection
			boolean isFCAvailable = System.getProperty("microedition.io.file.FileConnection.version") != null;
			  
			if (! isFCAvailable) {
				Alert splashScreen = new Alert(null, 
				"El terminal no permite esta característica", null, AlertType.INFO);
				Display.getDisplay(this).setCurrent(splashScreen);
				return;
			}
			
			Display.getDisplay(this).setCurrent(this.fileSelector);
		} else {
			Display.getDisplay(this).setCurrent(current);
		}
	}
	
	/*
	* @see javax.microedition.midlet.MIDlet#pauseApp()
	*/
	public void pauseApp(){
		// No se requiere la realización de ninguna tarea aqui cuando el MIDlet es pausado.
	}

	
	/*
	* Liberamos recursos y salimos
	* @see javax.microedition.midlet.MIDlet#destroyApp(boolean)
	*/
	public void destroyApp(boolean unconditional){
		this.fileSelector.stop();
		this.notifyDestroyed();
	}
}

package autentia.tutoriales.fileconnections;

import java.io.*;
import java.util.*;
import javax.microedition.io.*;
import javax.microedition.io.file.*;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.MIDlet;

/**
 * Explorador de ficheros para terminales móviles
 * @author Carlos García. Autentia
 * @see http://www.mobiletest.es 
 */
public class FileSelector extends List implements CommandListener, FileSystemListener, Runnable {
	
	/**
	 * Separador de ficheros
	 */
	private final static String FILE_SEPARATOR = (System.getProperty("file.separator") != null) ? 
						System.getProperty("file.separator") : "/";
	
	/**
	 * Directorio padre
	 */
	private final static String UPPER_DIR = "..";
	
	// Códigos de las posibles operaciones: Inicialización y apertura de ficheros
	private final static int LOAD_OP = 1;
	private final static int OPEN_OP = 2;
	
	
	private MIDlet midlet;
	private Vector roots;
	private FileConnection currentRoot;
	
	// Las tareas de I/O deben realizarse en un hilo independiente
	private int    operationCode;
	private Thread task;
	
	// Atributos gráficos
	private Command cmdOpen, cmdExit;

	
	/**
	 * Constructor
	 * @param midlet Referencia al MIDlet
	 */
	public FileSelector(MIDlet midlet) {
		super("File Explorer", Choice.IMPLICIT);
		this.midlet = midlet;
		this.roots  = new Vector();
		
		this.createUI();	

		// Nos registramos para que el sistema nos notifique cuando se inserten o extraigan memorias externas.
		FileSystemRegistry.addFileSystemListener(FileSelector.this);
		
		// Mostramos todas las posibles raices de sistemas de ficheros.
		this.operationCode = FileSelector.LOAD_OP;
		task = new Thread(this);
		task.start();
	}
	
	/**
	 * Crea e inicializa el interfaz gráfico
	 */
	private void createUI(){
		this.cmdOpen = new Command("Abrir", Command.ITEM, 1);
		this.cmdExit = new Command("Salir", Command.EXIT, 1);
		
		this.addCommand(this.cmdOpen);
		this.addCommand(this.cmdExit);
		this.setCommandListener(this);		
	}
	
	
	/**
	 * Este método será invocado cuando la aplicación sea cerrada
	 */
	public void stop() {
		/* Liberamos recursos eliminando la subcripción de que el sistema (AMS) 
		nos notifique de las inserción o extración de una memoria externa */

		FileSystemRegistry.removeFileSystemListener(this);
	}

	
	/* 
	 * El usuario hizo clic en un botón.
	 * @see javax.microedition.lcdui.CommandListener#commandAction(javax.microedition.lcdui.Command, javax.microedition.lcdui.Displayable)
	 */
	public void commandAction(Command cmd, Displayable d) {
		if (cmd == this.cmdOpen) {
			this.operationCode = FileSelector.OPEN_OP;
			task = new Thread(this);
			task.start();
		} else if (cmd == this.cmdExit) {
			this.midlet.notifyDestroyed();
		}
	}

	
	/** 
	 * Una targeta de memoria externa ha sido insertada o extraída. Recargamos la información.
	 * @see javax.microedition.io.file.FileSystemListener#rootChanged(int, java.lang.String)
	 */
	public void rootChanged(int state, String rootName) {
		this.operationCode = FileSelector.LOAD_OP;
		task = new Thread(this);
		task.start();
	}
	
        
	/**
	 * Borra todos los elementos de la pantalla
	 */
	private void deleteAll(){
		int num = this.size();
		for (int i = 0; i < num; i++){
			this.delete(0);
		}
	}

	
	/**
	 * Muestra en pantalla todos las raices encontradas 
	 */
	private void displayAllRoots() {
		this.setTitle("Raices");
		this.deleteAll();
		
		Enumeration enum1 = this.roots.elements();
		String		root  = null;		
		while (enum1.hasMoreElements()) {
			root = (String) enum1.nextElement();
			
			// El primer caracter es FileSelector.FILE_SEPARATOR. ==> lo descartamos.
			this.append(root.substring(1), null);
		}
		this.currentRoot = null;
	}
	
	/**
	 * Inicializa 'roots' con todos las raices de sistemas de ficheros disponibles en el terminal
	 */
	private void loadRoots() {
		// Borramos los elementos que tuviese previamente
		this.roots.removeAllElements();			
		
		// Solicitamos el dispositivo que nos proporcione la URL de las raices de todos los sistemas de ficheros LóGICOS

		Enumeration roots = FileSystemRegistry.listRoots();
		String      root  = null;
		while (roots.hasMoreElements()) {
			root  = (String) roots.nextElement();
			root  = FileSelector.FILE_SEPARATOR + root;
			this.roots.addElement(root);
		}
	}
	
	/**
	 * Abre el fichero o directorio seleccionado
	 */
	private void openSelected() throws java.io.IOException {
		int		selectedIdx  = this.getSelectedIndex();
		String	url			 = null;
		String	selectedFile = null;
		
		if (selectedIdx < 0) {
			return;
		}
		
		selectedFile = this.getString(selectedIdx);
		
		// ¿ Desea abrir un directorio?		
		if (selectedFile.endsWith(FileSelector.FILE_SEPARATOR) || selectedFile.endsWith("/")) {
			if (this.currentRoot == null) {
				this.currentRoot = (FileConnection) Connector.open("file:///" + selectedFile, Connector.READ);
			} else {
				this.currentRoot.setFileConnection(selectedFile);
			}				
			this.displayCurrentRoot();
			return;
		}
		
		// ¿ Desea ir al directorio anterior?
		if (selectedFile.equals(FileSelector.UPPER_DIR)) {
			if (this.roots.contains(this.currentRoot.getPath() + this.currentRoot.getName())) {
				this.displayAllRoots();
			} else {
				this.currentRoot.setFileConnection(FileSelector.UPPER_DIR);
				this.displayCurrentRoot();
			}
			return;
		}
		
		// Es un fichero, lo mostramos.
		url = this.currentRoot.getURL() + selectedFile;			
		this.showTextFile(url);
	}
	
	/**
	 * Muestra el contenido de un fichero en la pantalla del terminal.
	 * @param url URL del fichero a mostrar.
	 */
	private void showTextFile(String url) throws java.io.IOException {
		FileConnection  conn  = null;
		DataInputStream input = null;
		byte[]          bytes = null;

		try {
			// Leemos el fichero
			conn  = (FileConnection) Connector.open(url, Connector.READ);
			input = conn.openDataInputStream();
			bytes = this.readFully(input);
			
			// Borramos la pantalla
			this.deleteAll();
			
			// Mostramos el contenido
			this.append(new String(bytes), null);		
			conn.close();
		} finally {
			if (conn != null){
				conn.close();
			}
		}
	}
	
	/**
	 * (La forma de lectura aqui mostrada no es la más óptima)
	 * @return Devuelve todos los bytes disponibles y cierra el stream
	 */
	public byte[] readFully(DataInputStream input) throws java.io.IOException  {
		ByteArrayOutputStream bo = null;
	   
		try {
			bo = new ByteArrayOutputStream(6144); // 6Kb
			while (true){
				bo.write(input.readByte());
			}
		} catch (java.io.IOException ex){
			// No quitar
		}
		
		byte[] bytes = bo.toByteArray();
		
		// Liberamos recursos
		try {
			bo.close();        
		} catch (Exception ex){}
		
		try {
			input.close();      
		} catch (Exception ex){}        
		
		return bytes;
	} 
    
	/**
	 *  Muestra el contenido del directorio actual
	 */
	private void displayCurrentRoot() {
		Vector dirs    = new Vector();
		Vector files   = new Vector();
		String current = null;
		
		try {
			// Mostramos información sobre la ruta en el título.
			this.setTitle(this.currentRoot.getURL());
		
			// Inicializamos la pantalla
			this.deleteAll();
			
			// Añadirmos la posibilidad de volver al directorio anterior
			this.append(FileSelector.UPPER_DIR, null);
		
			// Mostramos los directorios
			Enumeration enum1 = this.currentRoot.list("*", true);
			while (enum1.hasMoreElements()) {
				current = (String) enum1.nextElement();
				if (current.endsWith(FileSelector.FILE_SEPARATOR)) {                        
					dirs.addElement(current);   // Es un directorio
				} else {                        
					files.addElement(current);  // Es un fichero
				}
			}
		
			this.showItems(dirs);   // Añadimos los directorios a la pantalla
			this.showItems(files);  // Añadimos los ficheros
		} catch (Exception e) {
			Alert alert = new Alert(null, e.getMessage(), null, AlertType.ERROR);
			Display.getDisplay(this.midlet).setCurrent(alert, this);
		}
	}
	
	
	/**
	 * Muestra los elementos de tipo string almacenados en un vector
	 */
	private void showItems(Vector items){
		Enumeration enum1   = items.elements();
		String      current = null;
		while (enum1.hasMoreElements()){
			current = (String) enum1.nextElement();
			this.append(current, null);
		}                   
	}
	                
	/*
	 * Ejecuta la tarea en segundo plano. 
	 * (En J2ME, todas las tareas de I/O deben ser en segundo plano.)
	 * @see java.lang.Runnable#run()
	 */
	public void run() {
		if (this.operationCode == LOAD_OP) {
			FileSelector.this.loadRoots();
			FileSelector.this.displayAllRoots();
		} else if (this.operationCode == OPEN_OP) {
			FileSelector.this.openSelected();
		}
	}
}

Conclusiones y reflexiones

Es increíble la evolución de las capacidades de los dispositivos móviles. Actualmente es normal tener varios cientos de Mb de almacenamiento disponibles.

Aunque normalmente esta memoria es usada para el almacenamiento de imagen y sonido, dejando volar la imaginación se pueden crear aplicaciones que requieran grandes capacidades de almacenamiento y que antes eran inviables... Gestión de mapas, diccionarios, etc. Aun así deberiamos de poner todo nuestro empeño en hacer las cosas bien más aun en este tipo de aplicaciones en donde los recursos de CPU y memoría volátil son limitados.

Bueno, espero que os haya parecido interesante. Desde Autentia nos gusta compartir el conocimiento. aquí tenéis un poquito más de nuestra aportación.

Carlos García Pérez. Creador de MobileTest, un complemento educativo para los profesores y sus alumnos.

cgpcosmad@gmail.com

Comentarios

Un comentario

  1. Buenas. mira estoy trabajando en una aplicación que tiene que poder leer un archivo y tambien editarlo, me podrían decir como sería el codigo para escribir o editar en el archivo

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

He leído y acepto la política de privacidad

Información básica acerca de la protección de datos

  • Responsable: IZERTIS S.A.
  • Finalidad: Envío información de carácter administrativa, técnica, organizativa y/o comercial sobre los productos y servicios sobre los que se nos consulta.
  • Legitimación: Consentimiento del interesado
  • Destinatarios: Otras empresas del Grupo IZERTIS. Encargados del tratamiento.
  • Derechos: Acceso, rectificación, supresión, cancelación, limitación y portabilidad de los datos.
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad

Técnico especialista en informática de empresa (CEU). Ingeniero Técnico en Informática de Sistemas (UPM) Creador de MobileTest, Haaala!, Girillo, toi18n. Charla sobre desarrollo de aplicaciones en Android. @cgpcosmad

¿Quieres publicar en Adictos al trabajo?

Te puede interesar

30/10/2025

Benjamín Suárez Menéndez

El Complex Problem Solving (CPS) es un proceso estructurado basado en herramientas, técnicas y actitudes que nos facilita la resolución de problemas complejos.

03/10/2025

Miguel García Rodríguez

Descubre cómo el diseño y la psicología del comportamiento utilizan sesgos cognitivos para influir en la toma de decisiones de los usuarios y potenciar la persuasión.

30/09/2025

Iván García Sainz-Aja

En este artículo exploraremos cómo utilizar ZenWave360 para generar un proyecto completo de Spring Boot con Kotlin a partir de un modelo DSL de Lenguaje Ubicuo.