Creating a PhoneGap powered Android application from the command line

You hate eclipse, I hate Eclipse and a lot of people do. That's why when you want to approach mobile development using one of the awesome Javascript frameworks out there, you want to use your favorite editor and go fast.

I've been experimenting lately with PhoneGap and I am going to write down the basics steps you might take to have your app deployed in your device in a matter of minutes. First of all I will assume that you have installed de Android SDK, PhoneGap and, at least, one target device in your AVT.

In this example, we will create a dummy app that tests the WriteFile feature. First, go to the tools directory in your Android SDK installation and execute:

android create project --target 1 --name FileTest --path ~/projects/android/FileTest --package com.phonegap.filetest --activity App

You should now have a FileTest folder under your $HOME/projects/android directory. You only need to write the following command to see the HelloWorld app running in your device:

ant clean install

Now comes the trickiest part but it is pretty straightforward. You will need to copy the necessary files, make a small modification in your main activity and, finally, modify your Manifest.

Copying the necessary files:

  • Create a assets/www directory
  • Copy phonegap.jar from your PhoneGap installation to your libs directory
  • Copy the xml directory from your PhoneGap installation to your res directory
  • Copy your application (index.html + extra files) to the assets/www directory
  • Copy de sample AndroidManifest.xml to your project

Modify the main Activity:

Edit the src/com/phonegap/filetest/App.java file and do the following changes:

  • Replace the setContentView() line with super.loadUrl("file:///android_asset/www/index.html")
  • Add an import com.phonegap.*;
  • Change class header so it extends from DroidGap instead of Activity.

Modify AndroidManifest.xml:

Open the sample Manifest you copy before and change:

  • The android:name attribute in the main <activity> so it matches your App name. In this example it should be ".App" (NOTE THE DOT IF YOU DON'T WANT TO GET CRAZY).
  • The package attribute in the manifest header so it matches yours. In this example "com.phonegap.testfile".

And that is all. Now you only will be worried about editing your JS, CSS and HTML files as a web developer with the editor or tool you like, and you will be able to compile and send your application to your Android device simply with a "ant clean install".

Happy hacking!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

HTML5 Audio en Chromium bajo Linux

Tras un rato vuelto loco sin saber por qué no funcionaba un Audio (mp3) embebido en un proyecto en el que ando trabajando al fin he dado con el problema.

Estoy utilizando el borrador del tag <audio> de HTML5, que me consta que funciona en Chrome/Chromium para la versión que tengo http://caniuse.com/#feat=audio

Resulta que por defecto, al menos en Ubuntu, al instalar Chromium desde paquetería no te instala el paquete que contiene los codecs necesarios para la reproducción. Tan sencillo como sudo apt-get install chromium-codecs-ffmpeg-extra

Por si alguien tiene el problema y, así, no tiene que Googlear tanto como yo.

Bases de datos de grafo con temporalidad y localización

En el mundo de las bases de datos de grafo, donde cada nodo representa a un objeto del mundo real se abren dos problemas que actualmente se encuentran bajo una intensa investigación, la temporalidad y la geolocalización.

Supongamos que se desea hacer un grafo representando obras de arte de un periodo concreto de la historia, y se quieren ver conexiones entre ellas. Para realizar esta tarea es fundamental saber dónde se realizó dicha obra y cuándo, de manera que se pueda situar en un "contexto espacio-temporal" adecuado.

Pero aún hay más, para realizar un estudio realmente interesante deberían tenerse en cuenta los lugares por los que ha pasado dicha obra (y cuando) a lo largo de la historia. Almacenar esta información así como extraer datos útiles de estos movimientos no es una tarea sencilla.

Algunas bases de datos como AllegroDB[1] plantean dichas posibilidades de representación en su documentación, pero aún así se hace necesaria la elaboración de herramientas capaces de sacar información útil de todos estos datos.

¿Será posible realizar herramientas de este tipo? En ello andamos...

[1] http://www.franz.com/agraph/support/documentation/current/agraph-introduction.html

Popurrí con la API gráfica de Facebook y Neo4j

Si aún no te has dado cuenta, las bases de datos gráficas como Neo4j están de moda. La gente hace preguntas, escribe sobre ellas y, ¡diablos!, es en lo que trabajo.

Recientemente Twitter ha liberado su implementación de base de datos gráfica, que hablando rápido y mal, es de grafos distribuídos. Y entonces Facebook mostró su API gráfica usando el protocolo Open Graph.

En este artículo[1] de Planet Neo4j, se muestra lo sencillos que es usar la API Gráfica de Facebook para mezclar datos de Facebook con datos de una base de datos gráfica que tengas en tu propia máquina.

[1] http://blog.neo4j.org/2010/05/mashups-with-facebook-graph-api-and.html

Búsquedas complejas (Facebook)

En los nuevos sistemas de información nada es tan simple como una
consulta SQL. La información está ubicada en un lugar, interesa a una
serie concreta de personas, el idioma de la información y del
«consultante» pueden ser distintos...

Es por ello que se hace necesario un sistema que permita tener en
cuenta diversos parámetros para poder valorar toda esta información,
tales como:
- El contexto personal
- El contexto social
- La propia consulta
- La popularidad global.

En este interesantísimo artículo de Facebook Engineering [1], se
detalla la visión de uno de los grandes de la Web al respecto, lo que
sin duda definirá el modo de estructurar la información en la era de
los grafos.

[1] http://www.facebook.com/note.php?note_id=365915113919

Optimizando con Cython

Siguiendo con el proceso de optimización que comencé anteriormente, es hora de ir más allá de utilizar hacks sobre el código python y aplicar algo más técnico. En este momento la ejecución mediante el profiler me indica que el tiempo de proceso es de 202 segundos.
Concretamente, voy a utilizar Cython, que es una herramienta que toma código Python y lo compila a código C, multiplicando, la velocidad considerablemente. El proceso básicamente consiste en crear un fichero .so que se importa como un módulo estándar de Python.
Para mi aplicación, tengo el grueso de las estructuras de datos en un fichero llamado structures.py, así que para crear un fichero structures.so, voy a crear el siguiente fichero setup.py que se encargue del proceso:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [Extension("structures", ["structures.py"])]

setup(
    name = "structures lib",
    cmdclass = {'build_ext': build_ext},
    ext_modules = ext_modules
)

Una vez creado este fichero simplemente ejecutamos lo siguiente:
# python setup.py build_ext --inplace

Y con ello tendremos el fichero binario structures.so creado.
Ahora voy a ejecutar el profiler pero utilizando el módulo compilado (structures.so) en lugar del original, y el resultado es:
         9878 function calls (9827 primitive calls) in 80.584 CPU seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        3   79.311   26.437   79.311   26.437 {evaluate}
        1    0.604    0.604   80.584   80.584 merger.py:15(merge)
        1    0.442    0.442    0.442    0.442 {filter}
        1    0.189    0.189    0.189    0.189 {classify}
        2    0.007    0.003    0.012    0.006 {recombine}
      101    0.006    0.000    0.010    0.000 random.py:263(shuffle)
     1230    0.005    0.000    0.015    0.000 re.py:134(match)
     1230    0.004    0.000    0.008    0.000 re.py:229(_compile)
     3590    0.003    0.000    0.003    0.000 {method 'random' of '_random.Random' objects}
     1230    0.003    0.000    0.003    0.000 {built-in method match}
       58    0.002    0.000    0.002    0.000 {rule_exists}
....

Y aquí, como se puede ver, es donde empieza «el milagro». La ejecución pasa de 202 a 80 segundos, sin hacer ningún cambio adicional sobre el código. Simplemente he generado el .so con una línea y he ejecutado.
Evidentemente, con Cython podemos hacer muchas más cosas, pero por analizar una de sus propiedades voy a usar el tipado estático. Es sabido que las variables en Python no necesitan que se declare previamente su tipo como en otros lenguajes como Java o C. Esto es muy bueno para la felicidad del programador, que simplemente se tiene que preocupar de programar lo que quiere hacer, pero determinante para los tiempos de ejecución, ya que es el interprete de Python el que tiene que resolver el tipo de una variable en tiempo de ejecución, con la consecuente carga.
Lo que propone el tipado estático de Cython es declarar las variables como en C, evitando el overhead que tiene la resolución del tipo de una variable. Puede parecer poca cosa, pero la función evaluated_attribute:
def evaluated_attribute(self, attribute):
    start = self.starting_bits[attribute]
    end = start + self.dataset.attribute_lengths[attribute]
    return 1 in self.genes[start:end]

resuelve el tipo de las variables enteras start y end los 50 millones de veces que se ejecutaba. Especificando el tipo de ambas variables el código queda:




def evaluated_attribute(self, attribute):
    cdef int start = self.starting_bits[attribute]
    cdef int end = start + self.dataset.attribute_lengths[attribute]
    return 1 in self.genes[start:end]

y el resultado es:
         9888 function calls (9837 primitive calls) in 75.398 CPU seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        3   74.123   24.708   74.123   24.708 {evaluate}
        1    0.621    0.621   75.398   75.398 merger.py:15(merge)
        1    0.434    0.434    0.434    0.434 {filter}
        1    0.184    0.184    0.184    0.184 {classify}
      101    0.007    0.000    0.010    0.000 random.py:263(shuffle)
        2    0.006    0.003    0.011    0.006 {recombine}
....

Si bien, la mejora ya no es tan impresionante como en los cambios anteriores hemos conseguido arañarle 5 segundillos más a la ejecución.
Finalmente, sé que no gusta ensuciar el código Python con cosas como cdef int, y para ello, existe un sistema para realizar el trabajo sin tener que tocar los archivos .py, y que puede encontrarse en [1].

[1] http://wiki.cython.org/pure

Profiling con Python

Tengo un proyecto personal sobre Minería de Datos Distribuida que recientemente decidí migrar de Java a Python, para hacer más agradable la realización de modificaciones y pruebas.

El problema es que la esperada bajada de rendimiento fue mucho más dura de lo esperado. No hice ningún tipo de pre-optimización (Premature optimization is the root of all evil), así que pensé que tras hacer un poco de apaños seguro que la cosa mejoraba. Y nos topamos con el segundo problema, ¿por dónde empiezo?

Pues la solución fue sencilla: Python-Profiler. Con este módulo es posible estudiar donde se está atascando tu código y a partir de ahí saber por donde mejorar. Así que reduje las iteraciones de mi genético de 600 a 2 y cree el siguiente script para realizar el profiling:

#!/usr/bin/env python
# encoding: utf-8
# filename: profile.py

import pstats, cProfile

import merger

cProfile.runctx("merger.merge([None, 'mushroom', 1, 5, 'c45unpruned'])", globals(), locals(), "Profile.prof")

s = pstats.Stats("Profile.prof")
s.strip_dirs().sort_stats("time").print_stats()

Tras ejecutarse el algoritmo para el dataset dado, los resultados del profiling (resumidos) fueron los siguientes:

         131709041 function calls (131708990 primitive calls) in 392.195 CPU seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
 53561620  190.354    0.000  242.946    0.000 structures.py:186(evaluated_attribute)
  4609517   92.497    0.000  382.148    0.000 structures.py:227(covers_sample)
 60293781   59.112    0.000   59.112    0.000 {len}
  6404630   31.898    0.000   46.706    0.000 structures.py:219(meets_attribute)
  6418374    8.621    0.000    8.621    0.000 {range}
      751    7.497    0.010  387.961    0.517 structures.py:281(classify)
    13535    0.723    0.000    2.143    0.000 structures.py:241(__init__)
    13593    0.679    0.000    0.972    0.000 structures.py:103(__init__)
   311407    0.392    0.000    0.392    0.000 {method 'index' of 'list' objects}
        7    0.109    0.016    2.330    0.333 structures.py:8(__init__)
        1    0.103    0.103    1.818    1.818 structures.py:294(filter)
    23701    0.035    0.000    0.035    0.000 structures.py:117(__cmp__)
    13719    0.030    0.000    0.030    0.000 {method 'split' of 'str' objects}
      842    0.028    0.000    0.028    0.000 {method 'replace' of 'str' objects}
    13593    0.022    0.000    0.022    0.000 structures.py:137(set_klass)
    17023    0.021    0.000    0.021    0.000 {method 'append' of 'list' objects}
      750    0.010    0.000  387.123    0.516 structures.py:357(evaluate)
       50    0.009    0.000    0.014    0.000 genetic.py:118(rule_hamming_distance)
      150    0.008    0.000  387.133    2.581 genetic.py:77(single_core_fitness_function)
      101    0.006    0.000    0.010    0.000 random.py:263(shuffle)
     1230    0.005    0.000    0.015    0.000 re.py:134(match)
        5    0.004    0.001    0.005    0.001 {method 'sort' of 'list' objects}
     1230    0.004    0.000    0.008    0.000 re.py:229(_compile)
     3608    0.004    0.000    0.004    0.000 {method 'random' of '_random.Random' objects}
        1    0.003    0.003  392.195  392.195 merger.py:15(merge)
       58    0.003    0.000    0.004    0.000 structures.py:274(rule_exists)
       ....

Puede apreciarse que las tres primeras funciones son las que realmente se están llevando la carga del sistema, la interpretación de la información es la siguiente:

  • cabecera: Indica el número de llamadas y el tiempo aproximado de ejecución, en este caso 392 segundos.
  • ncalls: Número de veces que se llama a la función.
  • tottime: Tiempo total que se ejecuta el cuerpo de esa función
  • percall: Tiempo estimado por cada llamada a la función.
  • cumtime: Tiempo total que se ejecuta el cuerpo de esa función incluyendo llamadas a otras funciones internas.

Para comenzar con la optimización, tomé la función len que como puede apreciarse se llama 60.293.781 veces y que dado a esta cifra, es de sospechar que es llamada desde evaluated_attribute que se ejecuta también 53.561.620, siendo la llamada a otras funciones muy inferior.
El código sospechoso que encontré era la línea:

end = start + len(self.dataset.attributes[attribute])

que se encuentra en el interior de la función evaluated_attribute, por lo tanto, esa llamada a len se realiza 53.561.620 veces. Modifiqué el código para guardar la longitud de dichas estructuras, durante su creación, en un diccionario y no tener que calcularla.
Nuevamente ejecuté el profiling y los resultados (resumidos) fueron:
         77210528 function calls (77210477 primitive calls) in 243.753 CPU seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
 54126634   98.213    0.000   98.213    0.000 structures.py:190(evaluated_attribute)
  4595154   91.820    0.000  233.623    0.000 structures.py:234(covers_sample)
  5908729   29.162    0.000   43.590    0.000 structures.py:226(meets_attribute)
  5922473    7.822    0.000    7.822    0.000 {range}
      751    7.330    0.010  239.927    0.319 structures.py:289(classify)
6236414/6236403    6.935    0.000    6.935    0.000 {len}
    13535    0.739    0.000    2.139    0.000 structures.py:249(__init__)
    13593    0.666    0.000    0.955    0.000 structures.py:107(__init__)
   311407    0.391    0.000    0.391    0.000 {method 'index' of 'list' objects}
        7    0.116    0.017    2.496    0.357 structures.py:8(__init__)
        7    0.104    0.015    0.104    0.015 {method 'read' of 'file' objects}
        1    0.101    0.101    1.159    1.159 structures.py:302(filter)

Se aprecia que el mero cambio de precalcular la longitud de las listas ha disminuido unos 150 segundos la ejecución. La tarea ahora es seguir analizando código y seguir disminuyendo los tiempos de ejecución. Por no extenderme más no comentaré más cambios, pero a tiempo de escritura de este post y combinando estás técnicas con Cython, del que hablaré proximamente, he reducido el tiempo de ejecución a 70 segundos, es decir, al 18%.

JDK6 de Sun en Lucid Lynx

Por algunos extraños motivos que pueden leerse buscando un poco en
Google, Canonical ha decidido no tener en sus repositorios oficiales
el paquete de Sun para tener el JDK disponible, incluyendo el
cuestionado OpenJDK únicamente.

Para tener disponible los paquetes necesarios hay que añadir el
repositorio de socios:
#sudo add-apt-repository "deb http://archive.canonical.com/ lucid partner"

Fuente: http://ubuntronics.blogspot.com/2010/04/instalacion-de-java-6-jdk-en-ubuntu.html

Cerebro de gato

Me alegra ver que aún hay gente trabajando en el mundo de los
algoritmos y sistemas bioinspirados. Fue el área que más me llamó la
atención cuando comencé a investigar pero la abandoné debido al poco
seguimiento que se realizaba en España. Enlazo al interesante artículo
para reconocimiento de patrones, con inspiración en el funcionamiento
del cerebro de un gato.[1]

[1] http://esciencenews.com/articles/2010/04/14/cat.brain.a.step.toward.electroni...