Cómo capturar métricas de macrocomparativas

Las métricas son el tipo principal de información que se extrae de tus comparativas. Se pasan a la función measureRepeated como List, que te permite especificar varias métricas medidas a la vez. Se requiere al menos un tipo de métrica para que se ejecute la comparativa.

En el siguiente fragmento de código, se capturan las latencias de fotogramas y las métricas personalizadas de la sección de registro:

Kotlin

benchmarkRule.measureRepeated(
    packageName = TARGET_PACKAGE,
    metrics = listOf(
        FrameTimingMetric(),
        TraceSectionMetric("RV CreateView"),
        TraceSectionMetric("RV OnBindView"),
    ),
    iterations = 5,
    // ...
)

Java

benchmarkRule.measureRepeated(
    TARGET_PACKAGE,     // packageName
    Arrays.asList(      // metrics
        new StartupTimingMetric(),
        new TraceSectionMetric("RV CreateView"),
        new TraceSectionMetric("RV OnBindView"),
    ),
    5,                  // Iterations
    // ...
);

En este ejemplo, RV CreateView y RV OnBindView son los IDs de bloques rastreables que se definen en RecyclerView. El código fuente del método createViewHolder() es un ejemplo de cómo puedes definir bloques rastreables dentro de tu propio código.

StartupTimingMetric, TraceSectionMetric, FrameTimingMetric y PowerMetric se describen en detalle más adelante en este documento.

Los resultados de comparativas se muestran en Android Studio, como se muestra en la Figura 1. Si se definen varias métricas, todas se combinan en el resultado.

Resultados de TraceSectionMetric y FrameTimingMetric.
Figura 1: Resultados de TraceSectionMetric y FrameTimingMetric.

StartupTimingMetric

StartupTimingMetric captura las métricas de tiempo de inicio de la app con los siguientes valores:

  • timeToInitialDisplayMs: Es la cantidad de tiempo desde que el sistema recibe un intent de inicio hasta que renderiza el primer fotograma de Activity de destino.
  • timeToFullDisplayMs: Es la cantidad de tiempo que transcurre desde que el sistema recibe un intent de inicio hasta que la app informa por completo con el método reportFullyDrawn(). La medición se detiene cuando se completa la renderización del primer fotograma después de la llamada reportFullyDrawn() (o que lo contiene). Es probable que esta medición no esté disponible en Android 10 (nivel de API 29) y versiones anteriores.

StartupTimingMetric genera los valores mínimos, medios y máximos de las iteraciones de inicio. Para evaluar la mejora del inicio, debes enfocarte en los valores medios, ya que proporcionan la mejor estimación del tiempo de inicio típico. Para obtener más información sobre lo que contribuye al tiempo de inicio de la app, consulta Tiempo de inicio de la app.

Resultados de StartupTimingMetric
Figura 2: Resultados de StartupTimingMetric.

Mejora la precisión de los tiempos de inicio

Las dos métricas clave para medir los tiempos de inicio de la app son el tiempo para la visualización inicial (TTID) y el tiempo para la visualización completa (TTFD). TTID es el tiempo que lleva mostrar el primer fotograma de la IU de la aplicación. TTFD también incluye el tiempo para mostrar cualquier contenido que se cargue de forma asíncrona después de que se muestre el fotograma inicial.

El TTFD se informa una vez que se llama al método reportFullyDrawn() de ComponentActivity. Si nunca se llama a reportFullyDrawn(), se informa el TTID en su lugar. Es posible que debas retrasar el momento en que se llama a reportFullyDrawn() hasta que se complete la carga asíncrona. Por ejemplo, si la IU contiene una lista dinámica, como RecyclerView, o una lista diferida, esto podría propagarse con un tarea en segundo plano que se completa después de que se dibuja la lista por primera vez y, por lo tanto, después de que la IU se marca como informada por completo. En esos casos, la propagación de la lista no se incluye en las comparativas.

Para incluir la propagación de la lista como parte del tiempo de tus comparativas, obtén FullyDrawnReporter con getFullyDrawnReporter() y agrégale un generador de informes al código de la app. Debes lanzar el generador de informes una vez que la tarea en segundo plano termine de propagar la lista.

FullyDrawnReporter no llama al método reportFullyDrawn() hasta que se lanzan todos los generadores de informes agregados. Cuando se agrega un generador de registros hasta que se completa el proceso en segundo plano, los tiempos también incluyen la cantidad de tiempo que se tarda en propagar la lista en los datos de tiempo de inicio. Esto no cambia el comportamiento de la app para el usuario, pero permite que los datos de inicio de tiempo incluyan el tiempo que tarda en propagarse a la lista.

En el siguiente ejemplo, se muestra cómo puedes ejecutar varias tareas en segundo plano de forma simultánea, cada una de las cuales registra su propio generador de informes:

Kotlin

class MainActivity : ComponentActivity() {

    sealed interface ActivityState {
        data object LOADING : ActivityState
        data object LOADED : ActivityState
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            var activityState by remember {
                mutableStateOf(ActivityState.LOADING as ActivityState)
            }
            fullyDrawnReporter.addOnReportDrawnListener {
                activityState = ActivityState.LOADED
            }
            ReportFullyDrawnTheme {
                when(activityState) {
                    is ActivityState.LOADING -> {
                        // Display the loading UI.
                    }
                    is ActivityState.LOADED -> {
                        // Display the full UI.
                    }
                }
            }
            SideEffect {
                lifecycleScope.launch(Dispatchers.IO) {
                    fullyDrawnReporter.addReporter()

                    // Perform the background operation.

                    fullyDrawnReporter.removeReporter()
                }
                lifecycleScope.launch(Dispatchers.IO) {
                    fullyDrawnReporter.addReporter()

                    // Perform the background operation.

                    fullyDrawnReporter.removeReporter()
                }
            }
        }
    }
}

Java

public class MainActivity extends ComponentActivity {
    private FullyDrawnReporter fullyDrawnReporter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        fullyDrawnReporter = getFullyDrawnReporter();
        fullyDrawnReporter.addOnReportDrawnListener(() -> {
            // Trigger the UI update.
            return Unit.INSTANCE;
        });

        new Thread(new Runnable() {
            @Override
            public void run() {
                fullyDrawnReporter.addReporter();

                // Do the background work.

               fullyDrawnReporter.removeReporter();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                fullyDrawnReporter.addReporter();

                // Do the background work.

                fullyDrawnReporter.removeReporter();
            }
        }).start();
    }
}

No se llama a reportFullyDrawn() hasta que se completan todas las tareas, independientemente del orden.

Si tu app usa Jetpack Compose, puedes usar las siguientes APIs para indicar el estado dibujado por completo:

  • ReportDrawn: Indica que el elemento componible está listo de inmediato para interactuar.
  • ReportDrawnWhen: Toma un predicado, como list.count > 0, para indicar cuándo el elemento componible está listo para interactuar.
  • ReportDrawnAfter: Toma un método de suspensión que, una vez completado, indica que el elemento componible está listo para la interacción.

FrameTimingMetric

FrameTimingMetric captura información de latencia de los fotogramas que produce una comparativa, como un desplazamiento o una animación, y genera los siguientes valores:

  • frameOverrunMs: Es la cantidad de tiempo en el que un fotograma determinado pasó dentro de su plazo. Los números positivos indican un fotograma descartado y un bloqueo o salto visible. Los números negativos indican cuánto más rápido es un fotograma en comparación con su plazo. Nota: Solo está disponible en Android 12 (nivel de API 31) y versiones posteriores.
  • frameDurationCpuMs: Es el tiempo que se demora en producir el fotograma en la CPU, tanto en el subproceso de IU como en RenderThread.

Estas mediciones se recopilan en la distribución: percentil 50, 90, 95 y 99.

Para obtener más información sobre cómo identificar y mejorar los fotogramas lentos, consulta Renderización lenta.

Resultados de FrameTimingMetric
Figura 3: Resultados de FrameTimingMetric.

TraceSectionMetric

TraceSectionMetric captura la cantidad de veces que se produce una sección de registro que coincide con el sectionName proporcionado y el tiempo que tarda. Por el momento, muestra el tiempo mínimo, el máximo y la mediana en milisegundos. La sección de registro se define con una llamada a función trace(sectionName) o con el código entre Trace.beginSection(sectionName) y Trace.endSection() (o sus variantes asíncronas). Siempre selecciona la primera instancia de una sección de registro capturada durante una medición. De forma predeterminada, solo muestra las secciones de registro de tu paquete. Para incluir procesos fuera de tu paquete, establece targetPackageOnly = false.

Para obtener más información sobre el registro, consulta Descripción general del registro del sistema y Cómo definir eventos personalizados.

TraceSectionMetric
Figura 4: Resultados de TraceSectionMetric.

PowerMetric

PowerMetric captura el cambio en la alimentación o la energía durante la prueba para las categorías de energía proporcionadas. Cada categoría seleccionada se divide en sus subcomponentes medibles, y las categorías sin seleccionar se agregan a la métrica "sin seleccionar".

Estas métricas miden el consumo en todo el sistema, no el consumo por app, y están limitados a Pixel 6, Pixel 6 Pro y dispositivos posteriores:

  • power<category>Uw: La cantidad de energía consumida durante la prueba en esta categoría.
  • energy<category>Uws: La cantidad de energía transferida por unidad de tiempo durante la prueba en esta categoría.

Las categorías incluyen las siguientes:

  • CPU
  • DISPLAY
  • GPU
  • GPS
  • MEMORY
  • MACHINE_LEARNING
  • NETWORK
  • UNCATEGORIZED

Con algunas categorías, como CPU, puede ser difícil separar el trabajo que realizan otros procesos del que realiza tu propia app. Para minimizar la interferencia, quita o restringe apps y cuentas innecesarias.

Resultados de PowerMetric
Figure 5: Resultados de PowerMetric.