Únete a ⁠ #Android11: The Beta Launch Show el 3 de junio.

Cómo generar comparativas del código de apps

La biblioteca de Jetpack Benchmark te permite obtener rápidamente comparativas de tu código basado en Kotlin o en Java dentro de Android Studio La biblioteca se ocupa de la preparación, mide el rendimiento del código y genera resultados de comparativas en la consola de Android Studio.

Algunos casos prácticos incluyen desplazar un elemento RecyclerView, inflar una jerarquía de View no trivial y realizar consultas de bases de datos.

Puedes usar la biblioteca en un entorno de integración continua (IC), como se describe en Cómo ejecutar comparativas en la integración continua.

Si todavía no implementaste AndroidX en un proyecto del que quieres generar comparativas, consulta Cómo migrar un proyecto existente con Android Studio.

Guía de inicio rápido

En esta sección, se proporciona un conjunto de pasos rápidos para generar comparativas sin necesidad de mover código a los módulos. Debido a que los pasos implican inhabilitar la depuración para obtener resultados de rendimiento precisos, no se confirmarán los cambios en el sistema de control del código fuente, aunque podría ser útil cuando quieras ejecutar mediciones únicas.

Para generar rápidamente comparativas únicas, completa los siguientes pasos:

  1. Agrega la biblioteca al archivo build.gradle del módulo:

    project_root/module_dir/build.gradle

        dependencies {
            androidTestImplementation "androidx.benchmark:benchmark-junit4:1.0.0-beta01"
        }
        
  2. Si deseas inhabilitar la depuración en el manifiesto de prueba, actualiza el elemento <application> a fin de forzar la inhabilitación de la depuración de manera temporal:

    project_root/module_dir/src/androidTest/AndroidManifest.xml

    <!-- Important: disable debuggable for accurate performance results -->
        <application
            android:debuggable="false"
            tools:ignore="HardcodedDebugMode"
            tools:replace="android:debuggable"/>
        
  3. Para agregar tu comparativa, agrega una instancia de la clase BenchmarkRule en un archivo de prueba del directorio androidTest. Si deseas obtener más información sobre cómo hacerlo, consulta Cómo escribir comparativas.

    En el siguiente fragmento de código se muestra cómo agregar comparativas a una prueba de JUnit:

    Kotlin

        @RunWith(AndroidJUnit4::class)
        class MyBenchmark {
            @get:Rule
            val benchmarkRule = BenchmarkRule()
    
            @Test
            fun benchmarkSomeWork() = benchmarkRule.measureRepeated {
                doSomeWork()
            }
        }
        

    Java

        @RunWith(AndroidJUnit4.class)
        class MyBenchmark {
            @Rule
            public BenchmarkRule benchmarkRule = new BenchmarkRule();
    
            @Test
            public void myBenchmark() {
                final BenchmarkState state = benchmarkRule.getState();
                while (state.keepRunning()) {
                    doSomeWork();
                }
            }
        }
        

Qué comparar

Las comparativas son especialmente útiles para el trabajo de la CPU que se ejecuta muchas veces en la app. Algunos ejemplos adecuados son el desplazamiento de RecyclerView, las conversiones o el procesamiento de datos y los fragmentos de código que se usan de manera repetida.

Otros tipos de código son más difíciles de medir mediante comparativas. Debido a que las comparativas se ejecutan en un bucle, el código que no se ejecute con frecuencia o que tenga un rendimiento diferente cuando se lo llame varias veces, podría no ser apropiado para generar comparativas.

Almacenamiento en caché

Evita medir solo la caché. Por ejemplo, las comparativas de diseño de una vista personalizada podrían medir solo el rendimiento de la caché de diseño. Para evitar que esto suceda, puedes pasar diferentes parámetros de diseño en cada bucle. En otros casos, como cuando se mide el rendimiento del sistema de archivos, podría resultar difícil, ya que el SO almacena en caché el sistema de archivos mientras está en un bucle.

Código ejecutado con poca frecuencia

Es poco probable que Android Runtime (ART) compile mediante JIT el código que se ejecuta una vez durante el inicio de la app. Por lo tanto, generar comparativas de este código mientras se ejecuta en un bucle no es una manera realista de medir su rendimiento.

Para este tipo de código, en cambio, recomendamos llevar un registro o generar perfiles del código en tu app. Ten en cuenta que esto no significa que no puedas comparar el código en tu ruta de acceso de inicio, sino que deberías elegir código que se ejecute en un bucle y que pueda compilarse mediante JIT.

Configuración completa del proyecto

Para configurar la generación de comparativas regulares, en lugar de las comparativas únicas, debes aislarlas en su propio módulo. De esta manera, podrás asegurarte de que su configuración y los ajustes de debuggable en false sean independientes de las pruebas regulares.

Para ello, completa las siguientes tareas:

  • Coloca el código y los recursos que deseas comparar en un módulo de biblioteca si todavía no están en uno.

  • Agrega un nuevo módulo de biblioteca que contenga las comparativas.

Nuestras muestras brindan ejemplos de cómo configurar un proyecto de esta manera.

Cómo definir propiedades de Android Studio

Si usas Android Studio 3.5, debes configurar manualmente las propiedades de Android Studio a fin de habilitar la compatibilidad con el asistente del módulo de comparativas. Esto no es necesario para Android Studio 3.6 o versiones posteriores.

Si deseas habilitar la plantilla de Android Studio para comparativas, haz lo siguiente:

  1. En Android Studio 3.5, haz clic en Help > Edit Custom Properties.

  2. Agrega la siguiente línea en el archivo que se abre:

    npw.benchmark.template.module=true

  3. Guarda el archivo y ciérralo.

  4. Reinicia Android Studio.

Cómo crear un módulo nuevo

La plantilla del módulo de comparativas configura automáticamente los ajustes para la generación de comparativas.

Si deseas usar la plantilla del módulo a fin de crear un módulo nuevo, haz lo siguiente:

  1. Haz clic con el botón derecho en tu proyecto o módulo y selecciona New > Module.

  2. Selecciona Benchmark Module y haz clic en Next.

    Figura 1: Módulo de comparativas

  3. Escribe un nombre para el módulo, selecciona el idioma y haz clic en Finish.

    Se crea un módulo configurado previamente para la generación de comparativas, con un directorio de comparativas y el valor debuggable configurado como false.

Cómo escribir comparativas

Las comparativas son pruebas de instrumentación estándar. Para crear comparativas, usa la clase BenchmarkRule que proporciona la biblioteca. Si deseas comparar actividades, usa ActivityTestRule o ActivityScenarioRule. Usa @UiThreadTest para comparar código de IU.

En el siguiente código, se muestran comparativas de ejemplo:

Kotlin

    @RunWith(AndroidJUnit4::class)
    class ViewBenchmark {
        @get:Rule
        val benchmarkRule = BenchmarkRule()

        @Test
        fun simpleViewInflate() {
            val context = ApplicationProvider.getApplicationContext()
            val inflater = LayoutInflater.from(context)
            val root = FrameLayout(context)

            benchmarkRule.keepRunning {
                inflater.inflate(R.layout.test_simple_view, root, false)
            }
        }
    }
    

Java

    @RunWith(AndroidJUnit4::class)
    public class ViewBenchmark {
        @Rule
        public BenchmarkRule benchmarkRule = new BenchmarkRule();

        @Test
        public void simpleViewInflate() {
            Context context = ApplicationProvider.getApplicationContext();
            final BenchmarkState state = benchmarkRule.getState();
            LayoutInflater inflater = LayoutInflater.from(context);
            FrameLayout root = new FrameLayout(context);

            while (state.keepRunning()) {
                inflater.inflate(R.layout.test_simple_view, root, false);
            }
        }
    }
    

Puedes inhabilitar la sincronización para secciones de código que no quieras medir, como se muestra en el siguiente código de ejemplo:

Kotlin

    @Test
    fun bitmapProcessing() = benchmarkRule.measureRepeated {
        val input: Bitmap = runWithTimingDisabled { constructTestBitmap() }
        processBitmap(input)
    }
    

Java

    @Test
    public void bitmapProcessing() {
        final BenchmarkState state = benchmarkRule.getState();
        while (state.keepRunning()) {
            state.pauseTiming();
            Bitmap input = constructTestBitmap();
            state.resumeTiming();

            processBitmap(input);
        }
    }
    

Consulta Cómo ejecutar las comparativas para obtener más información.

Cómo ejecutar las comparativas

En Android Studio, ejecuta tus comparativas como lo harías con cualquier @Test. En Android Studio 3.4 y versiones posteriores, puedes ver el resultado que se envió a la consola.

Para ejecutar las comparativas, en el módulo, navega a benchmark/src/androidTest y presiona Control + Shift + F10 (Command + Shift + R en Mac). Los resultados de las comparativas aparecerán en la consola, como se muestra en la Figura 2:

Resultado de comparativas en Android Studio

Figura 2: Resultado de comparativas en Android Studio

Desde la línea de comandos, ejecuta el comando connectedCheck regular:

./gradlew benchmark:connectedCheck
    

Cómo recopilar los datos

Se encuentra disponible en formato JSON un informe de comparativas completo con métricas adicionales e información sobre el dispositivo.

El complemento de Gradle androidx.benchmark habilita el resultado de JSON de manera predeterminada. Si deseas habilitar manualmente los resultados de JSON en entornos de compilación que no sean de Gradle, debes pasar un argumento de instrumentación, androidx.benchmark.output.enable, configurado en true.

El siguiente es un ejemplo en el que se usa el comando adb shell am instrument:

    adb shell am instrument -w -e "androidx.benchmark.output.enable" "true" com.android.foo/androidx.benchmark.junit4.AndroidBenchmarkRunner
    

De manera predeterminada, el informe de JSON está escrito en el dispositivo en la carpeta de descargas compartida externa del APK de prueba que, por lo general, se encuentra en:

    /storage/emulated/0/Download/app_id-benchmarkData.json
    

Puedes configurar la ubicación en la que se guardan los informes de comparativas en el dispositivo. Para ello, usa el argumento de instrumentación additionalTestOutputDir.

    adb shell am instrument -w -e "androidx.benchmark.output.enable" "true" -e "additionalTestOutputDir" "/path_to_a_directory_on_device_test_has_write_permissions_to/" com.android.foo/androidx.benchmark.junit4.AndroidBenchmarkRunner
    

Los informes de JSON se copian del dispositivo al host. Están escritos en la máquina host en:

    project_root/module/build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/device_id/app_id-benchmarkData.json
    

Complemento de Gradle para Android 3.6 y versiones posteriores

Cuando ejecutas comparativas desde la línea de comandos con Gradle, en los proyectos que usan el complemento de Gradle para Android 3.6 y versiones posteriores, puedes agregar la siguiente marca al archivo gradle.properties del proyecto:

    android.enableAdditionalTestOutput=true
    

De esta manera, se habilita la función experimental del complemento de Gradle para Android a fin de extraer informes de comparativas de dispositivos que ejecutan API nivel 16 y superiores:

Complemento de Gradle para Android 3.5 y versiones inferiores

Para los usuarios de versiones anteriores del complemento de Gradle para Android, el complemento androidx.benchmark de Gradle se ocupa de la copia de informes de comparativas de JSON del dispositivo al host.

Cuando uses el complemento de Gradle para Android versión 3.5 o anterior, y tengas como objetivo la API nivel 29, deberás agregar una marca al manifiesto de Android en el directorio androidTest de tus comparativas a fin de habilitar el comportamiento de almacenamiento externo heredado. Consulta Cómo inhabilitar la vista filtrada para obtener más información sobre cómo inhabilitar el almacenamiento específico.

    <manifest ... >
      <!-- This attribute is "false" by default on apps targeting Android 10. -->
      <application android:requestLegacyExternalStorage="true" ... >
        ...
      </application>
    </manifest>
    

Estabilidad del reloj

En los dispositivos móviles, los relojes cambian de manera dinámica del estado alto (para el rendimiento) al estado bajo (para ahorrar energía cuando el dispositivo se calienta). Estos relojes variables pueden hacer que los números de tus comparativas varíen en gran medida, por lo que la biblioteca proporciona maneras de abordar este problema.

Bloquear relojes (requiere permisos de administrador)

La mejor manera de estabilizar el rendimiento consiste en bloquear los relojes. De esta manera, se garantiza que los relojes nunca estén lo suficientemente altos para calentar el dispositivo, o bajos si las comparativas no están usando la CPU por completo. Aunque esta es la mejor manera de garantizar un rendimiento estable, la mayoría de los dispositivos no la admiten, ya que se necesitan permisos de administrador de adb.

Si quieres bloquear tus relojes, agrega el complemento auxiliar proporcionado en la ruta de clase del proyecto de nivel superior en el archivo build.gradle principal:

buildscript {
        ...
        dependencies {
            ...
            classpath "androidx.benchmark:benchmark-gradle-plugin:1.0.0-beta01"
        }
    }
    

Aplica el complemento en el build.gradle del módulo en el que agregas comparativas:

apply plugin: com.android.app
    apply plugin: androidx.benchmark
    ...
    

De esta manera, se agregan tareas de Gradle de comparativas a tu proyecto, como ./gradlew lockClocks y ./gradlew unlockClocks. Usa estas tareas para bloquear y desbloquear la CPU de un dispositivo mediante adb.

Si tienes varios dispositivos visibles para adb, usa la variable de entorno ANDROID_SERIAL a fin de especificar en qué dispositivo debería operar la tarea de Gradle:

    ANDROID_SERIAL=device-id-from-adb-devices ./gradlew lockClocks
    

Modo de rendimiento sostenido

Window.setSustainedPerformanceMode() es una función compatible con algunos dispositivos que permite que una app opte por una frecuencia de CPU máxima más baja. Cuando se ejecuta en dispositivos compatibles, la biblioteca Benchmark usa una combinación de esta API y el lanzamiento de su propia actividad para evitar los modelos térmicos y estabilizar los resultados.

Esta funcionalidad está habilitada de forma predeterminada por el conjunto testInstrumentationRunner del complemento de Gradle. Si deseas usar un ejecutor personalizado, puedes crear una subclase de AndroidBenchmarkRunner y usarla como testInstrumentationRunner.

El ejecutor lanza una actividad opaca en pantalla completa para garantizar que las comparativas se ejecuten en primer plano y no se superponga ninguna otra app.

Pausado de la ejecución automática

Si no se usan ni el bloqueo de relojes ni el rendimiento sostenido, la biblioteca realiza una detección automática de modelos térmicos. Cuando están habilitadas, las comparativas internas se ejecutan periódicamente para determinar cuándo la temperatura del dispositivo subió tanto como para disminuir el rendimiento de la CPU. Cuando se detecta un rendimiento reducido de la CPU, la biblioteca pausa la ejecución para permitir que el dispositivo se enfríe y vuelve a intentar ejecutar las comparativas actuales.

Errores de configuración

La biblioteca detecta las siguientes condiciones a fin de garantizar que tu proyecto y entorno estén configurados para un rendimiento apropiado para el lanzamiento:

  • Debuggable se configura en false.
  • Se está utilizando un dispositivo físico, no un emulador.
  • Los relojes están bloqueados si el dispositivo tiene permisos de administrador.
  • El nivel de batería en el dispositivo es suficiente.

Si alguna de las comprobaciones anteriores falla, las comparativas mostrarán un error a fin de evitar mediciones imprecisas.

Para hacer que estos problemas pasen como advertencias y evitar que muestren un error y detengan las comparativas, pasa el tipo de errores que quieras suprimir en una lista separada por comas al argumento de instrumentación androidx.benchmark.suppressErrors:

    adb shell am instrument -w -e "androidx.benchmark.suppressErrors" "DEBUGGABLE" com.android.foo/androidx.benchmark.junit4.AndroidBenchmarkRunner
    

Se puede configurar en Gradle de la siguiente manera:

    android {
        defaultConfig {
            testInstrumentationRunnerArgument 'androidx.benchmark.suppressErrors', 'DEBUGGABLE'
        }
    }
    

Ten en cuenta que suprimir errores permite que las comparativas se ejecuten en un estado de configuración incorrecto, pero el resultado de las comparativas se dañará intencionalmente anteponiendo nombres de pruebas con el error. Es decir que ejecutar comparativas que se pueden depurar con la supresión antes mencionada antepondrá DEBUGGABLE_ a los nombres de pruebas.

Muestras de comparativas

El código de comparativas de muestra está disponible en los siguientes proyectos:

Algunos de los proyectos de muestra incluyen lo siguiente:

  • BenchmarkSample: Muestra independiente que explica cómo usar un módulo de comparativas para medir el código y la IU.

  • PagingWithNetworkSample : Muestra de componentes de la arquitectura de Android que explica cómo generar comparativas del rendimiento de RecyclerView.

  • WorkManagerSample: Muestra de componentes de la arquitectura de Android que explica cómo generar comparativas de los trabajadores de WorkManager.

Envía comentarios

Si deseas informar errores o enviar solicitudes de funciones cuando usas las comparativas, consulta la herramienta de seguimiento de problemas de acceso público.