Si no vienes del post introductorio, quizás te gustaría leerlo.
Tercera entrega de la optimización a la que fue sometida nuestra aplicación. Esta vez, tiene que ver con los listados de elementos; de muchos elementos, como puede ser el inventario de una tienda. Sirva como ejemplo práctico que solíamos crear nuestros propios adapters (extend BaseAdapter
, todo sea dicho) muy alegremente, pero sólo al principio. Como buenos desarrolladores, copiábamos código del primer sitio que encontrábamos, sin fijarnos en lo óptimo que era. Y como buenos desarrolladores, repito, hacíamos las pruebas de nuestros propios adapters con 5 o 6 elementos para simular el inventario de una tienda.
¿Qué pasó? Cuando tuvimos nuestro primer tendero con ganas de inventariar toda su tienda, enseguida se nos vio el plumero. Una lista de 1200 (mil doscientos) elementos, con código copi-pegado y poco probado, tiene un 99% de posibilidades de ir más lenta, al hacer scroll, que el stand-up meeting de un lunes de resaca.
Como bien apuntó Francho en un comentario, una manera de ganar velocidad haciendo scroll es omitiendo los reiterados findViewById
cada vez que hay que cambiar los valores del famoso convertView
. En su lugar, debemos hacer uso de los tag que podemos asignar a las filas (rows) de nuestro listado. ¿A quién no le suenan las funciones setTag()
y getTag()
? Pues hace tiempo, a nosotros ni se nos pasaban por la cabeza. Luego de ver el magnífico video de la Google I/O 2009, Making your Android UI Fast and Efficient, hicimos una capillita en la oficina a Romain Guy. Merece la pena cada año ver los videos de la Google I/O...
Muy bien. Con esto que he nombrado arriba, tenemos solucionado una parte de nuestro problema; es decir: que cada vez que se haga scroll y haya que buscar los widgets cuyas propiedades han de cambiar, no se haga siempre mediante el findViewById()
, si no que utilicemos los tags.
La otra parte del problema, venía al experimentar que aún habiendo mejorado la velocidad de scroll un poquito, no conseguíamos ese efecto de fluidez que una lista ha de tener. ¿Por qué? Muy sencillo: cada nueva fila que aparecía en el listado al hacer scroll contenía uno, o a veces más, widgets cuyo contenido era calculado en el mismo momento del getView()
. A veces eran cálculos de sumas, fáciles. Otras veces eran consultas a la Base de Datos, lo cual nos estaba robando mili-segundos de muy alto precio al hacer que el scroll de tu aplicación sea fluido.
Por lo tanto, otro mandamiento que nos grabamos a fuego en la mente: No harás consultas a la Base de Datos en el getView()
de tus propios adapters. Las consultas, se hacen antes del setAdapter()
del ListView
. Si no, arderás en el infierno de los developers.
Y ya, para rizar el rizo de la optimización de un listado y hacerlo más rápido que Usain Bolt: Dime cuál es la fuente de datos que alimenta tu Adapter, y te diré cuántos mili-segundos perderás en cada getView()
.
¿Por qué digo esto? Por que, como bien dijo Pedro en un post: hay que evitar a toda costa el trabajar con ArrayList
de objetos propios en android. Reflection lo llaman, ¿no? Android y reflection no son buenos amigos.
Así que en lugar de acceder a los datos que poblarán nuestros ListView
de manera limpia y con código legible, como puede ser un ArrayList
, os recomendamos que uséis directamente el objeto Cursor
devuelto por la Base de Datos. Sí: mucho más sucio el código, pero no veas lo rápido que va todo.
En resumen, hoy hemos visto:
- El
ViewHolder
para evitar millones defindViewById()
- No hacer consultas a la Base de Datos dentro de un
getView()
- La fuente de datos de un Adapter, a ser posible, que sea un
Cursor
¿Me olvido algo?