Developing Frogtek

El blog del Departamento de Tecnología

Etiqueta: android (página 2 de 3)

Robolectric: TDD en Android

Llevábamos ya desde nuestros inicios buscando herramientas que nos permitieran practicar TDD para desarrollar las clases de nuestro proyectos en Android. El problema era que las Activity (clase fundamental de las aplicaciones Android) solo podían ser testeadas a través del propio entorno Android, nunca como clase independiente. Por lo tanto, los únicos tests que habíamos realizado sobre esas clases eran tests de integración, que podían tardar del orden de 4 minutos en ejecutarse por completo, lo que nos imposibilitaba el realizar un diseño guiado por tests.

Robolectric (http://pivotal.github.com/robolectric) es una herramienta que nos va a permitir lanzar tests unitarios en Android.

Su uso nos aporta varias ventajas:

  • Ejecuta tests en Android que duran segundos y no minutos, como en el caso de un Android jUnit test.
  • Su velocidad nos permite practicar TDD al crear clases en Android.
  • Podemos usar Mockito o PowerMock en conjunción con los tests de Robolectric.
  • Nos permite utilizar clases de librerías Android, como JSONObjects o JSONArrays, que en otros entornos no podríamos usar.

Como algunas clases de Android no tienen métodos para acceder al estado o las variables que contienen, la gente de Robolectric se ha sacado de la manga los Shadows (ShadowImageView, ShadowRelativeLayout, ShadowActivity, etc.), los cuales nos van a permitir realizar pruebas como esta:

@Test
public void testImagenEsCorrecta() throws Exception {
    ImageView imagenBanner = (ImageView) activity.findViewById(R.id.banner);
    ShadowImageView shadowBanner = Robolectric.shadowOf(imagenBanner);
    assertThat(shadowBanner.resourceId, equalTo(R.drawable.img_banner_grande));
}

Cómo configurar Robolectric para Eclipse:

Estos son los pasos básicos para configurar Robolectric en Eclipse. Recomiendo seguir el quick-start de la web oficial para más detalles:

  1. Crear una nueva carpeta en la raíz de nuestro proyecto que vamos a testear, llamada test.
  2. Crear un nuevo proyecto (u obtenerlo del SVN) llamada MyProjectTest.
  3. Queremos meter el código dentro de la carpeta test antes creada, así que en MyProjectTest haremos clic en propiedades y en Build Path/Source/Link Source seleccionaremos la carpeta test.
  4. Añadir al proyecto el .jar de Android.
  5. Añadir al proyecto el .jar de Robolectric (¡con dependencias!).
  6. Añadir una nueva run configuration en Run/Run configurations, de forma que elijamos Eclipse JUnit launcher y en la pestaña Arguments seleccionaremos Other/Workspace y elegiremos la carpeta raíz del proyecto que queremos testear.

Ejemplo de uso para testear una Activity:

@RunWith(RobolectricTestRunner.class)
public class MyActivityTest {
    private Activity activity;
    private Button pressMeButton;
    private TextView results;

    @Before
    public void setUp() throws Exception {
        activity = new MyActivity();
        activity.onCreate(null);
        pressMeButton = (Button) activity.findViewById(R.id.press_me_button);
        results = (TextView) activity.findViewById(R.id.results_text_view);
    }

    @Test
    public void shouldUpdateResultsWhenButtonIsClicked() throws Exception {
        pressMeButton.performClick();
        String resultsText = results.getText().toString();
        assertEquals("Testing Android Rocks!", resultsText);
    }
}

De momento, Robolectric es un proyecto joven (surgió en noviembre de 2010). Esperamos que esta herramienta mantenga una cierta continuidad, para que podamos obtener el máximo rendimiento a las prestaciones que nos ofrece.

Por ahora estamos experimentando las posibilidades que nos otorga, todavía nos queda por descubrir como crear nuestros propios objetos Cursor dentro de este entorno. Seguiremos informando…

Aquí huele a cuco (IV)

Bueno, un poco de autocrítica nunca viene mal y en este caso yo soy uno de los culpables de este desaguisado….

public int getVendorsCount() {
	if (this.mDm.openDBConnection()) {
		this.mDbHelper = new VendorsDbAdapter();
		Cursor cursor = this.mDbHelper.getVendorsCount();
		if (cursor != null) {
			cursor.close();
			return cursor.getCount();
		}
		this.mDm.closeDBConnection();
	}
	return 0;
}

Fijaos bien: estamos intentando acceder a un cursor después de cerrarlo….. además solo cerramos la base de datos si el cursor es null… vamos, una joya de código. Gracias a que tenemos test funcionales en Android hemos encontrado semejante esperpento. Ya vamos recuperando poco a poco lo invertido en ellos 🙂 .

Así he dejado el código después del pertinente refactor.

public int getVendorsCount() {
	int vendorsCount = 0;
	if (this.mDm.openDBConnection()) {
		this.mDbHelper = new VendorsDbAdapter();
		Cursor cursor = this.mDbHelper.getVendorsCount();
		if (cursor != null) {
			vendorsCount = cursor.getCount();
			cursor.close();
		}
		this.mDm.closeDBConnection();
	}
	return vendorsCount;
}

¿Bastante mejor no? ¿Alguna sugerencia para mejorarlo todavía más?

Efecto marquesina en Android

Cuando queremos mostrar un texto muy largo en una pequeña pantalla como la del móvil podemos crear un efecto de marquesina facilmente, podemos ponerlo a nivel de xml con esta propiedad.

android:ellipsize="marquee"

Sólo se activara cuando el objeto tenga el foco. Podemos forzarlo por código usando el método

 requestFocus() 

Al parecer no hace falta que el elemento tenga el foco para que active la marquesina, pero si debe tener el singleLine activado. Gracias a Maestro Linares por las correcciones.

Aquí la info oficial con el resto de valores.

Gracias a Gerardo sabemos algo más del comportamiento del efecto marquesina. Actualizo la entrada del post con sus comentario.

El TextView empezara a ser scrollable cuando tenga el foco y/o haya sido seteada como selected. Esto ultimo no puede hacerse desde el xml, por lo que sera necesario hacerlo desde el codigo.
El requestFocus puede funcionar, aunque es peligroso, ya que programaticamente estariamos dando el foco a ese objeto. Que sucede por ejemplo si queremos tener varios TextView con marquesina?.
Yo recomiendo hacerlo selected desde el codigo, por ejemplo en el onCreate:
mTextView.setSelected(true)

Y desde el xml indicar:

 android:singleLine=”true”
android:ellipsize=”marquee”

Si el TextView pertenece a un ListView este rotara el texto de forma automatica cuando el elemento tenga el foco.

Jose B. Cortés ganador de la segunda edición de la carrera del cerdito

Tras un mano a mano apasionante con el maestro Linares y un emocionante e imprevisible sprint Jose se ha alzado con el segundo cerdito de nuestra andadura. Lo cierto que es que podría haber ganado cualquiera de los dos así que vaya desde aquí la felicitación para ambos.

Hemos aprovechado el Ranatón y la presencia entre nosotros de David para hacer la entrega de premios que como siempre ha consistido en manosear un poquito al cochino que tenemos por hucha antes de dedicarnos al noble arte de destriparlo (es más fácil de decir que de hacer). Aquí tenéis una foto donde sale Linares (anterior ganador), David nuestro CEO y fundador y Jose, flamante ganador con coleta y sudadera de Android.

El cerdo contenía 34€.

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?

El findViewById(), y lo cargante que puede llegar a ser

Si no vienes del post introductorio, quizás te gustaría leerlo.

Segundo asalto. La aplicación sigue estando lenta, y no sabemos muy bien por qué. Ya no hay tantos onMeasure(); sólo los “necesarios”. Pero hay una nueva función que aparece en los primeros puestos de nuestro querido traceview: findViewTraversal(). ¿Y quién llama tantísimas veces a esta última? pues findViewById().
Y es que esta función, es la que enlaza un recurso de la interfaz de usuario de una aplicación, con una variable en nuestro código. O simplemente se encarga de acceder a ese recurso para cambiar una de sus propiedades.

Supongamos, pues, que tenemos un layout con una veintena de widgets entre layouts, Buttons, EditText, TextView, ImageView… Y que en un momento dado del ciclo de vida de la actividad, tenemos que acceder a uno ó varios de ellos para cambiar sus propiedades. Podemos acceder a ellos del modo:
((EditText) this.findViewById(R.id.edittext_cliente)).setOnClickListener(…);
Aunque también podemos guardar el valor que devuelve la función findViewById() en una variable, o variable miembro de la Activity para operar con ella varias veces a lo largo del citado ciclo de vida.

¿Cuál es la diferencia? y ya que estamos ¿cuál era nuestro error? Pues que cada vez que se llama a la función findViewById(), android recorre todo el árbol jerárquico del Layout que hemos definido previamente en XML, en busca del recurso al que queremos acceder. Como ya habréis averiguado, el costo de esa operación es muy alto si el control a encontrar es de los últimos en el árbol, o si hay muchos niveles de layouts anidados, o simplemente queremos cambiar el valor de una de las propiedades de ese recurso bastantes veces a lo largo del ciclo de vida de la aplicación.
Si a esto le sumamos que el cambio en una de las propiedades de un EditText (por ejemplo), afecta a cinco o seis widgets más; y a todos accedemos del modo findViewById(), estamos vendidos.

La solución para una Activity, cuyos widgets han de ser modificados con bastante asiduidad es el guardar el resultado de la función archinombrada en este post en variables miembro, a las cuales se accederá sin tener que recorrer (findViewTraversal()) todo el árbol jerárquico “n” veces, con la consiguiente pérdida de velocidad en la transición de un estado a otro de la Activity.
Eso sí: Podemos prescindir de estas variables miembro si accedemos una, y sólo una, vez al widget en cuestión.

Esto también hay que grabarlo a fuego en la mente.

Diseño de layouts XML, sucios pero eficientes, en android

Si no vienes del post introductorio, quizás te gustaría leerlo.

Bien, pues a medida que añadíamos nueva funcionalidad a la aplicación en la tablet, experimentábamos una lentitud tremenda en alguna de las actividades que abríamos. No era normal que si en el teléfono, el mismo layout funcionaba perfectamente, ¿por qué al unir dos layouts en uno sólo, pero más grande, vaya tan lento? Respuesta: no hay nada que el traceview no solucione. Nada.

Rápidamente, Julio comenzó a ver que había miles de llamadas a una función nativa de android que se llama onMeasure(). La misma, se encarga de que cada vez que hay que posicionar un widget dentro del layout, hacer las medidas necesarias de lo ya presente en el mismo para así determinar dónde debe de ir colocado. ¿Y qué problema hay con esto? Pues que si tu layout tiene muchos widgets, y estos widgets tienen algunas de sus propiedades (como el layout_width, o layout_height) con valores wrap_content o fill_parent, todo resultará en un festival de onMeasure(). Creo recordar, que el 90% del tiempo de carga de una pantalla ineficiente nuestra, se lo llevaba esta función. Obviamente, pasamos de medio segundo para cargar una pantalla determinada en el teléfono, a 2 ó 3 (incluso más) en la tablet.
Por lo tanto, uno de los nuevos mandamientos que hay que grabarse a fuego es que: siempre darás medidas a tus widgets (Button, TextView, Layouts, EditText, etc), en lugar de que las decida android. Siempre, a no ser que sea estrictamente necesario; que alguna vez lo es.
Este es el primer paso para que la aplicación fluya un poquito más rápido que antes. Pero sólo un poquito.

¿Qué, que aún no hemos ensuciado el layout? Bien, pues este punto tiene que ver con el nivel en que un widget se encuentra anidado. Podemos tener un layout muy sencillo, con un RelativeLayout y dos Button dentro, o podemos tener algo como esto (o peor). Es por esto lo que un día dije en la oficina. Somos unos barrocos.
Por lo tanto, desde frogtek recomendamos encarecidamente que, a poder ser, se tenga un RelativeLayout padre con todos sus hijos al mismo nivel. Si se tienen 4 o 5 widgets en el layout, serás afortunado; si tienes 30 o 40 como en alguno de nuestra tablet, puedes volverte loco buscando uno en concreto en el Outline de eclipse. Cierto es que queda todo mucho más ordenado si vamos anidando widgets dentro de Layouts, pero se pierde rapidez al cargar, ya que también hay muchos más onMeasures() que si estuviesen todos al mismo nivel. Y traceview no miente.

Por lo tanto, el primer paso para agilizar la carga (o recarga) de una pantalla, pasa por dar medidas a todo widget al que podamos dar medidas absolutas, e intentar que nuestros layouts sean lo menos barrocos posibles en cuanto a ordenación jerárquica se refiere.

Consejo extra que se me ocurre mientras escribo: si utilizas el LayoutInflater para cargar layouts dentro de otros, tener un layout con uno o dos niveles de anidación ayudará mucho. Nosotros, que tenemos layouts dentro de layouts, y de nuevo dentro de layouts (por necesidad), lo hemos notado.

Optimizar una aplicación android (Introducción)

Hacía como mes y medio que quería escribir sobre este tema, pero me habría precipitado ya que desde esa fecha hasta ahora hemos aprendido mucho más sobre la optimización de una aplicación para android; aunque aún no quedan muchos conocimientos por adquirir.

Como ya hemos contado alguna vez, desde hace casi dos años, desarrollamos nuestro producto para teléfonos; teléfonos con una resolución fija. Y durante este verano, hemos dado el salto a una tablet. Suerte que todo el núcleo de la aplicación lo teníamos bien separado en un .jar. Por lo que el 2% del tiempo lo hemos invertido en retocar un poquito el núcleo, y el 95% restante ha sido armar una interfaz de usuario muy bonita, pero que nos ha dado bastantes quebraderos de cabeza, y que voy a intentar plasmar en una serie de posts.

Tras unas cuantas iteraciones y un empujón espectacular, teníamos muy avanzada la versión para la tablet. Pero el cambio de una pantalla de 320 x 480, a una de 800 x 480 conlleva problemas de memoria y de rendimiento (con un hardware superior al del teléfono, todo iba mucho más lento). Por que no es oro todo lo que reluce. Con un buen procesador, y bastante memoria no todo va a funcionar como pensabas. Por lo menos, en android. Nos hemos rascado mucho la cabeza, hemos sufrido, pero finalmente hemos aprendido muchas cosas; sobretodo de nuestros errores.

Es por ello, que nos gustaría compartir con vosotros los siguientes temas:

A medida que vayamos redactando la serie de posts, se actualizarán los puntos con enlaces a las URLs.

Optimizar EditText en modo landscape

Hoy me ha pasado algo mientras programaba una nueva pantalla, que me ha traído a la cabeza recuerdos de toda la vida de la UI de la aplicación. La verdad, ha cambiado y mejorado mucho desde su inicio. Cuando comenzamos hace mucho tiempo el desarrollo de nuestro producto, nos centramos únicamente en una versión para teléfonos android, sin pensar en otro tipo de dispositivos (no había más gadgets con este sistema que el HTC G1).
Desde ese momento con menor atractivo que funcionalidad, el diseño de nuestra interfaz gráfica se basaba en pantallas con layouts verticales. Obviamente, trabajábamos en el emulador que venía en las primeras versiones del SDK de android y aún no teníamos a varios clientes reales dando feedback sobre la usabilidad. Por aquel entonces, era más importante el ir implementando funcionalidad que hacer la aplicación preciosa; ya habría tiempo para ello más tarde.

Pero llegó el día en que algunas personas comenzaron a utilizar la aplicación, y a sugerir cosas nuevas para la interfaz gráfica. Alguna de ellas venía dado el uso que se hacía de la aplicación y que no tenía nada que ver con el modo al que la usábamos los programadores. Resulta que el HTC G1 tiene un teclado físico, que aparece al desplazar la pantalla; y al mostrar este teclado, si en nuestro fichero AndroidManifest.xml no se dice lo contrario, la pantalla cambia a modo landscape u horizontal. Por lo que la pantalla ha de tener su equivalente horizontal, o se pierde más o menos la mitad del los widgets que interaccionan con el usuario. Pero esa es otra historia.

Teníamos un problema al que no prestamos mucha atención, por no decir ninguna. Y es que, por defecto, cuando un EditText (o caja de texto) es seleccionado para introducir un texto y el teléfono está en modo landscape, la caja de texto viene a ocupar todo el espacio que no ocupa el teclado software de android (desde la SDK 1.6). Por lo que el usuario, puede perder la noción de en qué caja de texto se encuentra, si hay muchas de ellas en una determinada pantalla. Pero esto no era un gran problema en una pequeña pantalla como la del HTC G1, con una resolución de 380×420, no importa mucho que una caja de texto ocupe gran parte de la pantalla en modo landscape. ¿Y qué me ha pasado hoy? Pues que al desarrollar para una resolución de 800×480, tenemos una caja de texto en la que, por poco, no cabe la biblia en verso. Para muestra, un botón:
¿Un bonito EditText de 700 y pico pixeles de ancho por unos 240 de alto? ¡Ni hablar! Sobre todo, cuando se utiliza para una propiedad de un producto de una tienda como por ejemplo: el nombre.
¿Qué hacer? Pues investigar un poco en horario after hours. Resulta, que se puede modificar el comportamiento de un EditText una vez ha sido seleccionado para introducir texto. Esto se hace mediante la propiedad android:imeOptions. Sus posibles valores son distintos flags que modifican el comportamiento tando del EditText como del teclado software. El que me interesa es flagNoExtractUi; de este modo, al ganar el foco el EditText, muestra el teclado software (si queremos que se muestre), editamos directamente sobre la caja de texto, y no sobre la misma maximizada para escribir 15 carácteres a lo sumo para escribir el nombre como ya he dicho.

Así que hay veces, que es mejor perder tiempo leyendo documentación y posts del equipo de android, que ganar tiempo yendo rápido y no pensando en pequeños detalles.

Muy bien Pablo, pero ¿y si quiero mantener ese aspecto maximizado para que escriban, por ejemplo, la biblia en verso y como tengo tantos campos grandes no quiero que se pierdan por la UI al presionar la tecla Next que pasa el foco al siguiente EditText?
Pablo responde: Entonces tienes una propiedad del EditText llamada android:Hint, que muestra en color gris clarito un pequeño texto informativo definido por el programador. Este texto desaparecerá cuando el usuario comience a escribir, y volverá a aparecer cuando se vacíe el texto. Así el usuario podrá ver en la oscuridad de las UI de android.

Eficiencia en Android

Continuamos con nuestro afán por tener una aplicación más eficiente e iremos escribiendo con lo que nos vamos encontrado: tenemos una tabla con datos para sincronizar y mientras se sincronizan actualizamos una barra de progreso en la status bar. Actualmente, actualizábamos la barra de progreso cada vez que se procesaba una fila, con lo cual, actualizábamos la barra de progreso tantas veces como filas teniamos.

Julio (QA) al percatarse de que esta operación costaba mucho tiempo, lanzó su herramienta más preciada…, el traceview. Se pasó por mi mesa y vimos que la clase Notify de Android se nos estaba llevando un gran tiempo debido al problema explicado en el párrafo anterior.

Con lo que nos pusimos manos a la obra y escribimos el correspondiente método estático que nos calcula el porcentaje y nos dirá si tenemos que actualizar la progress bar, de este modo siempre la actualizaremos 100 veces. Ahí va:

/*** This method retuns true if an update is needed in the progess bar
 *
 * @param itemsCount The total number of items
 * @param iCount The actual item to be processed
 * @return True if is necesary to update the progress bar
 */
 public static boolean isNeededProgressBarUpdate(int itemsCount, int iCount) {
    if (itemsCount != 0 && iCount !=0) {
        int perActual = (iCount * 100) / itemsCount;
        int perPrevious = ((iCount - 1) * 100) / itemsCount;
        if (perActual == perPrevious) {
            return false;
        } else {
            return true;
        }
    } else {
       return true;
    }
 }

Por otro lado también hemos notado que el Log de Android consume bastantes recurso y así lo comentan en la documentación. Pero no tenemos muy claro qué pasa con el Log.d (DEBUG). Según dice en la documentación, no se escribe en tiempo de ejecución, pero… creemos que depende de si marcas el teléfono para debug o no.

Como conclusión final hemos conseguido reducir un proceso que le costaba una hora a unos 20 minutos.

Antiguas entradas Recientes entradas