Developing Frogtek

El blog del Departamento de Tecnología

Etiqueta: convertView

Crear nuestros propios adapters para listados, y no morir en el scroll

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:

  1. El ViewHolder para evitar millones de findViewById()
  2. No hacer consultas a la Base de Datos dentro de un getView()
  3. La fuente de datos de un Adapter, a ser posible, que sea un Cursor

¿Me olvido algo?

5 claves para hacer tu aplicación más eficiente en Android

En frogtek hemos pasado una iteración centrándonos en mejorar la eficiencia de nuestro aplicación en Android. Estos son los 5 puntos principales que hemos encontrado:

  • Mejorar la eficiencia de las consultas SQL. Esto es básico para cualquier aplicación ya sea móvil o de escritorio. El simple cambio de un “SELECT IN” por un “INNER” o crear un índice puede reducir en un 50% el tiempo de la consulta.
  • Evitar crear objetos. Android esta basado en un máquina virtual y resulta muy caro crear objetos. En nuestro caso habíamos creado un proyecto orientado a API en el que teníamos entidades propias, leíamos de la bbdd 1000 elementos y creábamos nuestros objetos con los datos leídos. Es 10 veces más rápido un acceso directo (cursores en nuestro caso) que pasar por objetos.
  • Nunca uses un “wrap_content” para definir la altura de una lista. Para calcular el tamaño final de la lista android se recorre todos los elementos cada vez que hacemos scroll en la lista.
  • Para crear el efecto de “pulsado” sobre una imagen lo más fácil es cambiar el png del fondo en el evento OnTouch. Esta solución solo incrementa el tamaño de la carpeta drawables y carga de memoria el heap de nuestra aplicación. Desde frogtek decidimos hacerlo “programmatically” usando el efecto alpha del framework de animaciones. Nos creamos un componente propio “FrogButton” el cual llamamos desde nuestro layout. Explicaremos cómo hacer esto en un futuro post.
  • Listas eficientes usando el convertView del metodo getView. Si queremos personalizar cualquier lista necesitaremos crearnos nuestro propio list adapter e implementar el método getView. Bien, este método lo único que hace es devolver una vista especifica que la lista le pide bajo demanda. En el convertView lo que tenemos es una especie de holder que ya tiene datos cargados de la vista anterior (las listas suelen tener vistas similares). Simplemente controlando esto ganaremos mucha velocidad a la hora de desplazarte por la lista.
  • public View getView(int position, View convertView, ViewGroup parent) {
       RelativeLayout rowLayout;
       if (convertView == null) {
          rowLayout = (RelativeLayout) LayoutInflater.from(mContext).inflate(
          R.layout.row_sales_item, parent, false);
       } else {
          rowLayout = (RelativeLayout) convertView;
       }
       //DO SOME STUFF HERE AND RETURN THE VIEW
       return rowLayout;
    }

Os recomendamos estos dos vídeos para entender un poco mejor estos tips, aunque en cualquier caso nos podéis preguntar cualquier cosa:

Google I/O 2009 – …Make your Android UI Fast …

Google I/O 2010 – The world of ListView

Aquí una entrada hablando de la eficiencia en un blog de recomendada lectura.

http://blog.radioactiveyak.com/2010/04/content-provider-iterator-or-things.html