Recientemente nos hemos visto sumergidos en el desarrollo de aplicaciones para dispositivos móviles con J2ME, y tras algunos dolores de cabeza hemos salido a flote. Y digo esto porque no esperábamos la cantidad de problemas con los que nos íbamos a encontrar y la escasa documentación de los infinitos bugs que existe en Internet. Como no quiero que al siguiente le pase lo mismo que a mí, que a veces parecía que estaba jugando al http://www.googlewhack.com/, pienso compartir parte de las cosas que he descubierto en algunos posts en el blog. Y digo parte porque me debo a la confidencialidad con el cliente.
Y la primera en la frente.
PORQUE EL con.openInputStream COMO APARECE EN TODOS LOS LIBROS Y TUTORIALES NO FUNCIONA Pues eso, que te compras un libro sobre j2me, lo estudias y tan panchito te pones a crearte tu primer midlet con conexiones a servidor web. Para ello y como aparece en Toooodos los libros y tutoriales te decides a poner:
HttpConnection con = (HttpConnection) Connector.open(url, Connector.READ, true);
con.setRequestMethod(HttpConnection.GET);
InputStream in = con.openInputStream();
Bonito verdad? Pues bien, te vas a encontrar que en GRAN cantidad de móviles NO FUNCIONA. Así de claro y de alto. Y diría que no es simplemente un problema de bugs, sino de la propia especificación MIDP2.0, y me explico:
Lo primero que debes saber es que en algunos libros y/o tutoriales te recomiendan que resulta bonito crear un thread de peticiones por red, para que la pantalla siga
respondiendo a los comandos del usuario. Bien, no es que sea bonito, es que es TOTAL Y ABSOLUTAMENTE NECESARIO.
La mayoría de los teléfonos suelen pedir permiso al usuario para usar el “airtime” (o tiempo de conexión por red, y menos mal que lo hacen dadas las tarifas de los operadores ;-)); eso suele suceder cuando realizamos la llamada Connector.open(), osea cuando ya hemos empezado a realizar la petición, o sea el control del programa está en esa llamada NO ATENDIENDO a la pantalla. Con lo cual cuando te pregunte, si no has creado un thread paralelo al thread que atiende los comandos, para las peticiones de red, tu usuario se quedará mirando esta pantalla hasta que se aburra, sin posibilidad de hacer nada más.
¿No es muy buena “experiencia de usuario”, no? ;-)
Usar otro thread para las peticiones de red no es una opción, sino una obligación
Bien pues aprendido esto, cambiamos nuestro código, y las cosas “parece” que empiezan a funcionar ;-):
// En algun lugar de tu midlet
NetRequest request = new NetRequest();
request.start();
...
public class NetRequest extends Thread{
....
HttpConnection con = (HttpConnection) Connector.open(url, Connector.READ, true);
con.setRequestMethod(HttpConnection.GET);
InputStream in = con.openInputStream();
Y digo “parece”, porque efectivamente (si usas un emulador, benditos sean que siguen viviendo en el país del Edén) abres tu conexión, y obtienes el InputStream que querías, y lo puedes usar en que te de la gana. A no ser que seas un blade runner y te decidas a hacer los tests que en Linking Paths hicimos. En un momento dado, desde el emulador (el WTK de Sun), desconectábamos el equipo de la red. Alguno pensará, ¿para qué? Hombre pues teniendo en cuenta la cobertura de los móviles se trata de una situación bastante probable ¿no?; pero es que además como veremos en algunos teléfonos, existen algunos divertidos bugs en su implementación de openInputStream().
Bueno, pues si acertábamos en el momento clave (indicado abajo), EL THREAD DE RED SE QUEDABA BLOQUEADO EN EL MÉTODO openInputStream(), sin saltar ningún tipo de Excepción (ni la IOEXception, ni la OyesQueMeHeQuedadoSinConexionException):
HttpConnection con = (HttpConnection) Connector.open(url, Connector.READ, true);
con.setRequestMethod(HttpConnection.GET);
// Aqui la conexión de red se va a hacer gargaras
InputStream in = con.openInputStream();
Glups!, Se quedaba bloqueado para siempre, jamás de los jamases. ¿Pero y los temporizadores de red que a su bien tuvieron enseñarme en la Escuela de Ingenieros? ¿No saltan? Pues no, NO SALTA NINGÚN TIPO DE TEMPORIZACIÓN DE RED, y eso aunque pongas Connector.open(url, Connector.READ, true). Para ver realmente donde está el problema debemos ir a la especificación MIDP2.0, que en su API, respecto a Connector:
“An optional third parameter is a boolean flag that indicates if the calling code can handle timeout exceptions. If this flag is set, the protocol implementation may throw an InterruptedIOException when it detects a timeout condition. This flag is only a hint to the protocol handler, and it does not guarantee that such exceptions will actually be thrown. If this parameter is not set, no timeout exceptions will be thrown.”
Por resumir: si le dices a un fabricante de móviles que puede hacerlo o no, y que no está obligado, te apuesto lo que quieras a que no lo hará. Y en todos los móviles que lo hemos probado (unos 10) ocurre que NO EXISTEN TEMPORIZADORES DE RED PARA HTTP.
Y aunque la especificación no diga nada sobre este método opnInputStream(), también ocurre lo mismo con inputStream.read(), y si miramos la especificación:
“This method blocks until input data is available, the end of the stream is detected, or an exception is thrown.”
Con lo cual sospechamos que esa actitud blockeante es inherente a los InputStream. Con lo cual tenemos una paradoja muy interesante:
Si se cierra la conexión al abrir o al leer de un InputStream, por cualquier causa, no se lanza ninguna excepción, y como no existen temporizadores, el bloqueo del thread es permanente
Y no es que ocurra con algún móvil arcaico, ocurre en todas las gamas modernas, incluso en el emulador de móviles MIDP2 de Sun (el WTK). Podéis comprobarlo, dando por ejemplo un sleep de varios segundos para que os de tiempo a cerrar la conexión en el momento justo. Ahh, se me olvidaba, si alguien pretende solucionar el bloqueo en el read() llamando antes al método avalaible(), que se olvide. Es un método que siempre devuelve cero ;-) (¿Verdad que te empiezan a gustar los estándares?;-))
Para nuestro cliente hemos encontrado ciertos “work-arounds” para que la aplicación no se quedase parada en ese thread, pero lamentablemente es confidencial. Sólamente comentaré varias cosas:
Como el método stop() de los threads ya está deprecado (y no es para nada recomendable), sólamente un thread se puede matar a si mismo. Eso en la práctica se consigue con algún tipo de booleano en el run, que se pone a false cuando se quiere parar la ejecución. Si el thread está en un bucle, podemos parar su ejecución controlando el valor de esa variable. Lamentablemente cuando estamos en plena llamada openInputStream (que en algunos móviles puede tardar varios segundos), no estamos en un bucle, y si se produce el problema ahí, esta técnica no funciona
Existen algunos móviles (como el Samsung SGH-D500) que simplemente se bloquean cuando intentas hacer una conexión a url distintas: referencia , Verdad que es divertido? Para esos casos, hemos descubierto que intentando de nuevo las conexiones, el bloqueo del primer thread desaparece, obteniendo ahora dos conexiones, la antiguamente bloqueada y la nueva
Llamar a connection.close() desde otro thread paralelo, a veces funciona (haciendo que salte una IOException), y a veces no (Simplemente no pasando nada, y quedando el thread bloqueado)
Muchos teléfonos tienen un límite máximo en el número de conexiones y de threads que pueden existir en un momento determinado, así que el problema puede ser aún mayor
La verdadera solución vendría porque en la próxima especificación de MIDP, se obligara realmente a los fabricantes a crear temporizadores de red (como es lógico), o que los métodos openInputStream o read realmente recibieran IOEXception, que no lo reciben. Pero la verdad no sé quien está metido en esa especificación, porque este tipo de errores nos dejan perplejos.
Pues nada, a disfrutar de j2me, y a rezar porque ningún móvil pierda la conexión de red o tengáis que hacer un midlet de red para los Samsung. Eso o contratar a LP para dar solución ;-)
Share This
Ultimos comentarios
Darwin, Yomismo, Marcelo, ibon, oscar ordoñez
» Flatee.com o cómo crear un proyecto en internet, ¿Buscas un piso compartido? at Linked, » Trabajando con Linking Paths, ¿Qué queréis saber de Linking Paths? at Linked, alberto, Jose, aitor, Jose, De Linking Paths a la Formula1 at Linked, Dani [...]
plunchete, M@k, el Buscaimposibles
¿Buscas un piso compartido? at Linked
Jesus Chuda Contreras, Angeles, Ger, Pedro, Alfonso, Windzor, javier, xelha
Cerramos el trimeste (2/2008) at Linked, Goio Telletxea, Sergio, el primo, alberto, raultxi