Repasando mi antiguo blog he encontrado un artículo que escribí en 2009 sobre cómo optimizar código Java. Por aquel entonces (o quizás antes) yo hacía cosas para teléfonos móviles J2ME (como gvSIG Mini) y había que ingeniárselas para que aquello fuera un poco ágil. Cosas como instanciar una clase era casi una herejía…
<autobombo>La verdad es que es una pena que al poco tiempo saliera Android porque precisamente gvSIG Mini para teléfonos Java creo que ha sido de las pocas aplicaciones de mapas decentes que ha habido (con permiso de MGMaps)</autobombo>
Al tema. Éstas son algunas buenas prácticas para optimizar código Java para Android sobre todo. Algunas posiblemente ya estén obsoletas porque ahora se suele hacer compilación en tiempo de ejecución (JIT) y el compilador de Java ya lleva optimizadores, pero la verdad es que alguna de estas buenas prácticas para optimizar código Java ya las tengo totalmente interiorizadas y me parece una ‘cerdada’ no hacerlas. Eso sí, ojito porque algunas son un poco gore y se pasan por el forro la OOP y los principios SOLID.
Las voy a dividir en dos categorías, pero antes conviene responder a dos preguntas:
¿Por qué optimizar código Java para Android?
Básicamente por dos motivos:
El primero es que si optimizas la manera en que escribes código se generará menos código intermedio (DEX code o byte code en Java) con lo cual la máquina virtual tendrá que transformar menos código a código máquina y la aplicación irá más rápida.
El segundo motivo es que como efecto colateral de optimizar el código, tu aplicación consumirá menos batería
Aún así, hay que tener en cuenta que sólo me voy a centrar en optimizar código Java para Android, pero para temas de ahorro de batería hay muchas más optimizaciones que se pueden hacer. Igual me da para otro artículo otro día 🙂
¿Cuándo optimizar código Java para Android?
Esto es importante también, porque en la mayoría de los casos no deberías preocuparte en optimizar el código. Es decir, si haces cutre-aplicaciones con 3 formularios, que consumen datos de un servidor y hacen 4 cosas, probablemente no necesites optimizar nada.
Ahora bien, en mi caso gvSIG Mini, hacía peticiones http contínuamente, utilizaba pools de hilos para tareas en segundo plano, renderizaba imágenes en Canvas, instanciaba contínuamente objetos, escribía en disco, trabajaba con grandes cantidades de datos… En aplicaciones de ese tipo (o en juegos) sí vas a notar una gran diferencia optimizando el código Java.
Buenas prácticas para optimizar código Java para Android que debes aplicar o serás un paquete
- Evitar concatenaciones de Strings, utilizar StringBuffer o StringBuilder para tal caso. La concatenación de Strings produce cada vez un nuevo objeto y por tanto, mayor consumo de memoria y mayor recolección de basura. (Por favor, esta hacedla siempre)
- La encriptación y conexiones https tienen peor rendimiento.
- Crear métodos con el menor número de parámetros posible. Esta es de primero de SOLID.
- Si se van a realizar operaciones complejas como, senos, cosenos u otras operaciones complicadas de coma flotante y se sabe de antemano cuál va a ser el resultado, es conveniente pre-calcular estos valores y utilizarlos como constantes.
- Soportar el trabajo off-line siempre que sea posible persistiendo la información en el almacenamiento del dispositivo.
- Sacar fuera de los bucles las constantes y creación de nuevos objetos
- Siempre que vayamos a acceder a la misma posición de un array varias veces, es mejor guardar esa posición en una variable local y así evitar el acceso repetido al índice del array.
- Delegar operaciones demasiado complejas en portales y acceder a los resultados a través de servicios web o conexiones de red.
- Utilizar buffers para leer datos a través de la red y leer los datos en porciones en lugar de byte a byte que es más lento.
- Reutilizar y hacer pool de objetos siempre que sea posible para evitar crear nuevas instancias.
- Liberar recursos tan pronto como sea posible, como conexiones de red, a streams o a ficheros. Normalmente, se suele liberar este tipo de recursos dentro de la cláusula finally para asegurarnos de que los recursos se liberan aún cuando se produzca alguna excepción.
- Referenciar a null instancias de objetos que ya no se van a usar, para que el recolector de basura libere memoria.
- Instanciación perezosa de objetos.
Buenas prácticas para optimizar código Java para Android si eres muy friki o un nostálgico
- Los métodos sincronizados son los más lentos, a continuación los métodos de interfaz, los métodos de instancia, los métodos finales y por último los métodos estáticos son los más rápidos. Hay que tener en cuenta esta clasificación para evitar siempre que sea posible la sincronización e interfaces.
- Evitar en cualquier caso sincronización dentro de bucles.
- Usar variables es más eficiente que arrays. Los arrays son más eficientes que Vector o HashTable y en cualquier caso, arrays unidimensionales siempre mejor que bidimensionales. Tener en cuenta también que hay que inicializar la clase Vector y HashTable con un tamaño que se ajuste a nuestras necesidades.
- El acceso a los atributos de una clase es más rápido que encapsular con getter y setter.
- El acceso a variables locales es más rápido que a atributos de la clase. Siempre que sea posible asignar atributos de una clase a una variable local si se va a hacer referencia a ella varias veces dentro de un método o bucle.
- Contar hacia atrás es más rápido en los bucles.
- Usar operadores como x+=1 en vez de x = x+1 ya que generan menos byte code.
- Utilizar desplazamiento de bits en vez de la multiplicación o división si es posible. Por ejemplo, x >> 2 es equivalente a x / 4 y x << 10 es equivalente a x * 1024, 1 << 20 es equivalente a Math.pow(2, 20).
- Cuando sea posible evitar bucles ya que evitaremos toda la sobrecarga de control de flujo en cada iteración. Por ejemplo, si tenemos una operación que se va a realizar 5 veces, en vez de utilizar un bucle podemos realizar las 5 operaciones secuencialmente.
- Normalmente cuesta menos comparar un número a cero, así, siempre que sea posible en un bucle utilizar como guarda una comparación a cero.
- Usar tipos escalares en lugar de objetos Java siempre que sea posible, por ejemplo, int en lugar de Integer.
- Usar excepciones únicamente cuando sea necesario ya que cada excepción lanza un nuevo objeto.
Muy útil gracias!
Una pregunta relacionada, no conoces o no tienes por ahí ningún artículo relacionado con lo siguiente?
a) Cuando usar sqlite antes que otro recurso equivalente?
b) Cuando guardar en el dispositivo datos parseados de un xml online, o cogerlos cada vez que se necesitan?
c) uso o abuso de Threads o AsyncTask?
etc,etc.
Me interesa mucho,
saludos y felicidades.
Hola Jordi,
ahí va mi opinión:
a) Cuando necesites alguna de las funciones de sqlite 😛 Por ejemplo, si necesitas hacer búsquedas, almacenar muchos datos offline para sincronizar cuando haya conexión, cuando necesites paginar, etc.
b) Cachear datos en local siempre, con alguna política de renovación de caché (cada x minutos, cuando el servidor avise, etc.)
c) Yo siempre he usado Threads en Android para operaciones que bloquean la UI aunque luegoque tener en cuenta que desde un hilo no puedes acceder al hilo de la UI, para eso los AsyncTask son mucho más cómodos
Saludos.
Hola, como sería en cuanto a “Sacar fuera de los bucles las constantes y creación de nuevos objetos” si necesito convertir en objetos a los registros que recupero de un cursor para cargarlos en un ArrayList?
Gracias desde ya.