domingo, 4 de abril de 2010

Exception at TimerTask.run()java.lang.RuntimeException: pushModalScreen called by a non-event thread

Tratando de abrir una ventana de diálogo desde un thread en una aplicación para Blackberry, fallaba con:
Exception at TimerTask.run()java.lang.RuntimeException: pushModalScreen called by a non-event thread

El código que lo provocaba era:

public class RemindTask extends TimerTask {
public void run() {
try {
currentTime = currentTime - 1;
System.out.println("currentTime = " + currentTime);
countdown.setDate(currentTime);
if (currentTime <= 0) {
oneTimer.cancel();
Dialog.alert("Countdown completed");
}
} catch (Exception e) {
System.out.println("Exception at TimerTask.run()" + e.toString());
e.printStackTrace();
}
}
}


La solución es incorporar esa acción dentro del thread de Eventos, de esta forma:
public class RemindTask extends TimerTask {
public void run() {
try {
currentTime = currentTime - 1;
System.out.println("currentTime = " + currentTime);
countdown.setDate(currentTime);
if (currentTime <= 0) {
oneTimer.cancel();

UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
Dialog.alert("Countdown completed");
}
});

}
} catch (Exception e) {
System.out.println("Exception at TimerTask.run()" + e.toString());
e.printStackTrace();
}
}
}

La solución la obtuve del foro java.lang.RuntimeException:pushModalScreen called by a non-event thread

Tambien se pueden consultar estas referencias

http://www.blackberry.com/developers/docs/5.0.0api/UI-summary.html

http://www.thinkingblackberry.com/archives/182

sábado, 5 de abril de 2008

Cómo obtener el código fuente partiendo del jar

Teniendo un midlet en forma de archivo .jar se puede obtener el código fuente mediante un descompilador. Este proceso analiza bytecode y convierte los archivos .class a .java.

Existen varios descompiladores de java, en este ejemplo se utilizará Jode.

Los pasos para descompilar el bytecode de un jar son:
1. Descargar Jode
Buscar y descargar el archivo jode-1.1.2-pre1.jar del proyecto jode en sourceforge.

2. Determinar las versiones de CLCD y MIDP del jar
Abrir el archivo .jar con una aplicación que maneje archivos zip.
Dentro del archivo en la carpeta META-INF se encuentra el archivo MANIFEST.MF, extraer y abrirlo con un editor de texto.
Tomar nota de las versiones.

3. Configurar la variable classpath
Se debe configurar esta variable para incluir la referencia a las clases que se determinó en el paso 2.
Si está instalado Java Sun Wireless Toolkit y el midlet utiliza CLDC 1.0 y MIDP 2.0 sería por ejemplo:
set CLASSPATH=C:\Documents and Settings\Luis\My Documents\jode-1.1.2-pre1.jar;C:\WTK2.5.2\lib\cldapi10.jar;C:\WTK2.5.2\lib\midpapi20.jar
4. Ejecutar jode
Para ejecutar el descompilador se utiliza la sintaxis:
java jode.decompiler.Main --dest srcdir program.jar

Esto dejará el código fuente original en el directorio srcdir. Cuando el midlet recurra a algún paquete opcional se debe incluir también en el classpath.

Este instructivo debería ser suficiente para las aplicaciones más sencillas, en algunos casos estos pasos no logran recontruir el código fuente completo.

martes, 18 de marzo de 2008

Problema con backCommand en Netbeans Mobility Pack

Una de las funcionalidades más interesante de esta herramienta es el editor visual del flujo.

Permite rápidamente manejar el cambio de pantallas y la navegación.

Sin embargo los comandos "Back" agregados con este método tienen un comportamiento que puede no ser el deseado.

Cada vez que se cambia de pantalla, el midlet guarda la pantalla a la que se va a acceder y la que se está dejando en una HashTable.
Al ejecutar un comando "Back" se llama a switchToPreviousDisplayable(), el cual recupera para la pantalla actual cual fue la anterior.
El problema es que cuando ejecuta switchToPreviousDisplayable() también escribe en la HashTable de manera que queda switcheando en forma permanente entre dos pantallas.

Es decir
1) Se ingresa a la pantalla A
2) Se ingresa a la pantalla B
3) Se ingresa a la pantalla C
4) Se presiona back y se regresa a la pantalla B
5) Se presiona back y se direcciona a la pantalla C de nuevo!

De esta forma el flujo no vuelve a la pantalla A y si se presiona Back queda alternando siempre entre B y C.

En muchos casos sería preferible que al presionar back sucesivamente llevara a todas las pantallas recorridas en la forma explicada al final de Uso de la clase java.util.Stack

Lamentablemente tanto el código de switchToPreviousDisplayable() como switchDisplayable() no se puede modificar desde el editor.

La mejor solución sería cambiar el comportamiento de NetBeans para que genere otro código. Y la solución más rápida sin perder la funcionalidad del editor de flujo es sobreescribir la HashTable para que no considere los cambios hechos por switchToPreviousDisplayable() .


public void switchDisplayable(Alert alert, Displayable nextDisplayable) {
// write pre-switch user code here
Displayable onePrevious = null;
Displayable twoPrevious = null;
try {
Displayable current = getDisplay().getCurrent();
onePrevious = (Displayable)__previousDisplayables.get(current);
twoPrevious = (Displayable)__previousDisplayables.get(onePrevious);
} catch(Exception e) {

}

Display display = getDisplay();
Displayable __currentDisplayable = display.getCurrent();
if (__currentDisplayable != null && nextDisplayable != null) {
__previousDisplayables.put(nextDisplayable, __currentDisplayable);
}
if (alert == null) {
display.setCurrent(nextDisplayable);
} else {
display.setCurrent(alert, nextDisplayable);
}
// write post-switch user code here
if (onePrevious != null) {
if (nextDisplayable == onePrevious) {
if (twoPrevious != null) {
__previousDisplayables.put(nextDisplayable, twoPrevious);
}
}
}
}
Lo que hace es antes de salir del procedimiento "restaurar" la referencia original si es que se está volviendo a una pantalla anterior.

Notese que en realidad no captura el comando "Back" si no que lo deduce cuando la pantalla a la que se va a acceder coincide con la previa. Esto puede no aplicar en el caso en que se acceda a una misma pantalla más de una vez en un mismo flujo de navegación.

Otra solución sería encapsular todo el código de este método en un if (1==2) y reescribirlo a gusto.

Problema con ChoiceGroup en Netbeans Mobility Pack

Me topé con un inconveniente al usar Netbeans Mobility Pack. La causa es el código que genera cuando creamos una lista en el editor visual de pantallas.
Cuando agregamos elementos a un ChoiceGroup se genera el siguiente código


public ChoiceGroup getCgToAccount() {
if (cgToAccount == null) {
// write pre-init user code here
cgToAccount = new ChoiceGroup("To Account", Choice.POPUP);
cgToAccount.append("Choice Element 1", null);
cgToAccount.append("Choice Element 2", null);
cgToAccount.setFitPolicy(Choice.TEXT_WRAP_DEFAULT);
cgToAccount.setSelectedFlags(new boolean[] { false, false });
cgToAccount.setFont(0, null);
cgToAccount.setFont(1, null);
// write post-init user code here
}
return cgToAccount;
}


Si más tarde debemos modificar la lista de valores con el método append(value, null), la aplicación falla mostrando :

java.lang.ArrayIndexOutOfBoundsException
at javax.microedition.lcdui.ChoiceGroup.insertImpl(ChoiceGroup.java:1377)
at javax.microedition.lcdui.ChoiceGroup.append(+25)


Esto aparentemente sería un bug de Java, el cual al llamar a .setFont de alguna forma "congela" el tamaño de la lista y no pueden agregarse nuevos elementos sin recibir el error. Hay un tema en el foro de SDN que explica este bug en detalle: CLDC and MIDP - Problem in changing List control's font.

Por suerte el problema no se presenta si no existe una llamada a .setFont así es que para el caso de NetBeans la solución es borrar todos los elementos del ChoiceGroup y agregarlos por código. Esto por supuesto en el caso de que necesitemos modificarlos durante la ejecución.