Los usuarios esperan que las apps se carguen rápido y sean responsivas. Una app con inicio lento no cumple con esta expectativa y puede decepcionar a los usuarios. Este tipo de experiencia negativa puede hacer que un usuario califique tu app con una puntuación baja en Play Store o incluso que deje de usarla por completo.
En esta página, se proporciona información para ayudarte a optimizar el tiempo de inicio de tu app, lo que incluye una descripción general de las partes internas del proceso de inicio, la forma de generar perfiles del rendimiento del inicio y algunos problemas habituales relacionados con la hora de inicio, además de sugerencias para saber cómo abordarlos.
Información sobre los diferentes estados de inicio de la app
El inicio de una app se puede llevar a cabo en uno de estos tres estados: inicio en frío, inicio semicaliente o inicio en caliente. Cada estado determina el tiempo que tarda tu app en volverse visible para el usuario. En inicio en frío, tu app se inicia desde cero. En los otros estados, el sistema necesita llevar la ejecución de la app del segundo plano al primero.
Te recomendamos que siempre optimices tu app en función de la perspectiva de un inicio en frío, ya que esto también puede mejorar el rendimiento de los inicios semicalientes y en caliente.
Para optimizar tu app para un inicio rápido, es útil conocer qué sucede en los niveles del sistema y de la app, y cómo estos interactúan, en cada uno de los estados.
Dos métricas importantes para determinar el inicio de la app son el tiempo para la visualización inicial (TTID) y el tiempo para la visualización completa (TTFD). El TTID es el tiempo que lleva mostrar el primer fotograma, y el TTFD es el tiempo que tarda la app en volverse interactiva por completo. Ambos son igualmente importantes, ya que el TTID informa al usuario que la app se está cargando, y el TTFD muestra cuándo la app ya se puede usar. Si alguno de estos es demasiado largo, es posible que el usuario salga de la app incluso antes de que se cargue por completo.
Inicio en frío
El inicio en frío hace referencia a una app que se inicia desde cero. Esto significa que, hasta este inicio, el proceso del sistema crea el proceso de la app. Los inicios en frío se producen, por ejemplo, cuando se inicia tu app por primera vez desde que se inició el dispositivo o desde que el sistema finalizó la app.
Este tipo de inicio presenta el mayor desafío a la hora de minimizar el tiempo de inicio, ya que el sistema y la app deben trabajar más que en los otros estados de inicio.
Cuando comienza el inicio en frío, el sistema tiene las siguientes tres tareas:
- Cargar e iniciar la app
- Mostrar una ventana de inicio en blanco para la app inmediatamente después del inicio
- Crear el proceso de la app
No bien el sistema crea el proceso de la app, este es responsable de las siguientes etapas:
- Crear el objeto de la app
- Iniciar el subproceso principal
- Crear la actividad principal
- Aumentar las vistas
- Diseñar la pantalla
- Realizar el dibujo inicial
Cuando el proceso de la app completa la apertura inicial, el proceso del sistema intercambia la ventana en segundo plano que se muestra y la reemplaza con la actividad principal. En este punto, el usuario puede comenzar a usar la app.
En la Figura 1, se muestra cómo los procesos del sistema y la app transfieren el trabajo entre sí.
Pueden surgir problemas de rendimiento durante la creación de la app y de la actividad.
Creación de la app
Cuando se inicia tu app, la ventana en blanco de inicio permanece en la pantalla hasta que el sistema termina de abrir la app por primera vez. En este punto, el proceso del sistema intercambia la ventana de inicio por tu app, lo que le permite al usuario interactuar con ella.
Si anulas Application.onCreate()
en tu propia app, el sistema invoca el método onCreate()
en el objeto de la app. Luego, la app genera el subproceso principal, también conocido como subproceso de IU, y le asigna la tarea de crear la actividad principal.
Desde este momento, los procesos del nivel del sistema y de la app continúan de acuerdo con las etapas del ciclo de vida de la app.
Creación de la actividad
Después de que el proceso de la app crea tu actividad, esta lleva a cabo las siguientes operaciones:
- Inicializa valores.
- Llama a constructores.
- Llama al método de devolución de llamada, como
Activity.onCreate()
, apropiado para el estado actual del ciclo de vida de la actividad.
Por lo general, el método onCreate()
produce el mayor impacto en el tiempo de carga, ya que realiza el mayor trabajo de sobrecarga: carga y aumenta vistas, además de inicializar los objetos necesarios para que se ejecute la actividad.
Inicio semicaliente
El inicio semicaliente comprende un subconjunto de las operaciones que se llevan a cabo durante el inicio en frío. Al mismo tiempo, representa una sobrecarga mayor que en el caso del inicio en caliente. Muchos estados potenciales pueden ser considerados como inicios semicalientes, por ejemplo:
El usuario cancela la operación de tu app, pero luego vuelve a iniciarla. La ejecución del proceso podría continuar, pero la app debe recrear la actividad desde cero llamando a
onCreate()
.El sistema quita la app de la memoria y, luego, el usuario vuelve a iniciarla. Es necesario reiniciar el proceso y la actividad, pero de algún modo, la tarea puede aprovechar el paquete de estado de la instancia guardada, que se pasó a
onCreate()
.
Inicio en caliente
Un inicio en caliente de la app tiene una sobrecarga menor que un inicio en frío. En un inicio en caliente, el sistema lleva tu actividad al primer plano. Si todas las actividades de la app están todavía en la memoria, la app puede evitar tener que repetir la inicialización del objeto, el aumento del diseño y la renderización.
Sin embargo, si se borró definitivamente una parte de la memoria en respuesta a eventos de recorte de memoria, como onTrimMemory()
, será necesario recrear esos objetos como respuesta al evento de inicio en caliente.
El inicio en caliente muestra el mismo comportamiento en pantalla que el inicio en frío: el proceso del sistema muestra una pantalla en blanco hasta que la app termina de renderizar la actividad.
Cómo identificar el inicio de una app en Perfetto
Para depurar los problemas de inicio de la app, es útil determinar qué se incluye exactamente en la fase de inicio de la app. Para identificar toda la fase de inicio de la app en Perfetto, sigue estos pasos:
En Perfetto, busca la fila con la métrica derivada de inicio de la app para Android. Si no la ves, intenta capturar un registro con la app de registro del sistema integrado en el dispositivo.
Haz clic en la porción asociada y presiona m para seleccionarla. Aparecen corchetes alrededor de la porción para indicar cuánto tiempo tardó. La duración también se muestra en la pestaña Selección actual.
Para fijar la fila de inicio de la app para Android, haz clic en el ícono de fijar, que se muestra cuando sitúas el puntero sobre la fila.
Desplázate hasta la fila en la que aparece la app en cuestión y haz clic en la primera celda para expandir la fila.
Para acercar el subproceso principal, por lo general en la parte superior, presiona w (presiona s, a, d para alejar, mover hacia la izquierda y hacia la derecha, respectivamente).
La porción de métricas derivadas facilita ver qué se incluye exactamente en el inicio de la app para que puedas continuar depurando con más detalle.
Cómo usar métricas para inspeccionar y mejorar los inicios
Si quieres diagnosticar de manera adecuada el rendimiento del tiempo de inicio, puedes registrar métricas que muestren cuánto tarda tu app en iniciarse. Android ofrece varios medios para mostrarte que tu app tiene un problema y te ayuda a diagnosticarlo. Android vitals puede alertarte cuando se produce un problema, y las herramientas de diagnóstico pueden ayudarte a identificarlo.
Beneficios de usar métricas de inicio
Android usa las métricas de tiempo para la visualización inicial (TTID) y tiempo para la visualización completa (TTFD) para optimizar los inicios de apps semicalientes y en frío. Android Runtime (ART) usa los datos de estas métricas para precompilar código de forma eficiente y optimizar los inicios en el futuro.
Los inicios más rápidos generan una interacción más prolongada de los usuarios con tu app, lo cual reduce las instancias en las que se sale de la app con antelación, se reinicia la instancia o se cambia a otra app.
Android vitals
Android vitals puede ayudarte a mejorar el rendimiento de tu app. Para ello, te envía alertas a través de Play Console cuando los tiempos de inicio de tu app son excesivos.
Android vitals considera excesivos los siguientes tiempos de inicio para tu app:
- El inicio en frío tarda 5 segundos o más.
- El inicio semicaliente tarda 2 segundos o más.
- El inicio en caliente tarda 1.5 segundos o más.
Android vitals usa la métrica de tiempo para la visualización inicial (TTID). Si deseas obtener información sobre el modo en que Google Play recopila datos de Android vitals, consulta la documentación de Play Console.
Tiempo para la visualización inicial
El tiempo para la visualización inicial (TTID) es el tiempo que lleva mostrar el primer fotograma de la IU de la app. Esta métrica mide el tiempo que tarda una app en producir su primer fotograma, incluidas la inicialización del proceso durante un inicio en frío, la creación de la actividad durante un inicio en frío o semicaliente, y la visualización del primer fotograma. Mantener bajo el TTID de tu app ayuda a mejorar la experiencia del usuario, ya que permite que este vea que se inicia rápidamente. El framework de Android informa de forma automática el TTID para cada app. Cuando realices optimizaciones para el inicio de la app, te recomendamos implementar reportFullyDrawn
para obtener información hasta el TTFD.
El TTID se mide como un valor de tiempo que representa el tiempo transcurrido total, que incluye la siguiente secuencia de eventos:
- Iniciar el proceso
- Inicializar los objetos
- Crear e inicializar la actividad
- Aumentar el diseño
- Dibujar la app por primera vez
Cómo recuperar el TTID
Para encontrar el TTID, busca en la herramienta de línea de comandos Logcat una línea de resultado que contenga un valor llamado Displayed
. Este valor es el TTID y es similar al siguiente ejemplo, en el que el TTID es 3s534ms:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms
Para encontrar el TTID en Android Studio, inhabilita los filtros en la vista de Logcat desde el menú desplegable de filtros y, luego, busca el tiempo Displayed
, como se muestra en la figura 5.
Esto es necesario porque el servidor del sistema, no la app misma, entrega este registro.
La métrica Displayed
de los resultados de Logcat no necesariamente captura el tiempo transcurrido hasta que se cargan y muestran todos los recursos. Se omiten los recursos a los que no se hace referencia en el archivo de diseño o que la app crea como parte de la inicialización del objeto. Excluye estos recursos porque cargarlos es un proceso intercalado, y no se bloquea la pantalla inicial de la app.
En ocasiones, la línea Displayed
de los resultados de Logcat incluye un campo adicional para el tiempo total. Por ejemplo:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)
En este caso, la primera medición de tiempo se realiza solo para la actividad que se inició primero. La medición de tiempo total
comienza cuando se inicia el proceso de la app y puede incluir otra actividad que se haya iniciado primero, pero que no mostró nada en la pantalla. Solo se muestra la medición de tiempo total
cuando hay una diferencia entre los tiempos de inicio de la actividad y el tiempo total de inicio.
Te recomendamos que uses Logcat en Android Studio, pero, si no usas Android Studio, también puedes medir el TTID ejecutando tu app con el comando adb
del administrador de actividades de shell. Por ejemplo:
adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN
La métrica Displayed
aparece en los resultados de Logcat, como antes. En la ventana de terminal, se muestra lo siguiente:
Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 2044
TotalTime: 2044
WaitTime: 2054
Complete
Los argumentos -c
y -a
son opcionales y te permiten especificar <category>
y <action>
.
Tiempo para la visualización completa
El tiempo para la visualización completa (TTFD) es el tiempo que tarda una app en ser interactiva para el usuario. Se informa como el tiempo que lleva mostrar el primer fotograma de la IU de la app, así como el contenido que se carga de forma asíncrona después de que se muestra el fotograma inicial. Por lo general, es el contenido principal que se carga desde la red o el disco, según lo informa la app. En otras palabras, el TTFD incluye el TTID y el tiempo que tarda la app en ser utilizable. Mantener bajo el TTFD de tu app ayuda a mejorar la experiencia del usuario, ya que permite que este interactúe con ella rápidamente.
El sistema determina el TTID cuando Choreographer
llama al método onDraw()
de la actividad y cuando sabe que lo llama por primera vez.
Sin embargo, el sistema no sabe cuándo determinar el TTFD porque cada app se comporta de manera diferente. Para determinar el TTFD, la app debe indicarle al sistema cuando alcanza el estado de estado de visualización completa.
Cómo recuperar el TTFD
Para encontrar el TTFD, indica el estado de visualización completa llamando al método reportFullyDrawn()
de ComponentActivity
. El método reportFullyDrawn
informa cuándo la app se visualiza por completo y tiene el estado de utilizable. El TTFD es el tiempo transcurrido desde que el sistema recibe el intent de inicio de la app hasta que se llama a reportFullyDrawn()
. Si no llamas a reportFullyDrawn()
, no se informa ningún valor de TTFD.
Para medir el TTFD, llama a reportFullyDrawn()
después de visualizar por completo la IU y todos los datos. No llames a reportFullyDrawn()
antes de que se visualice y muestre por primera vez la ventana de la primera actividad según la medición del sistema, ya que este informará el tiempo medido. En otras palabras, si llamas a reportFullyDrawn()
antes de que el sistema detecte el TTID, el sistema informará el TTID y el TTFD como el mismo valor, y este corresponde al valor del TTID.
Cuando usas reportFullyDrawn()
, Logcat muestra un resultado como el siguiente, en el que el TTFD es 1s54ms:
system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms
En ocasiones, el resultado de Logcat incluye un tiempo total
, como se describe en Tiempo para la visualización inicial.
Si los tiempos de visualización son más lentos de lo que deseas, puedes intentar identificar los cuellos de botella en el proceso de inicio.
Puedes usar reportFullyDrawn()
para indicar el estado de visualización completa en casos básicos en los que sabes que se alcanzó este estado. Sin embargo, en los casos en que los subprocesos en segundo plano deben completar el trabajo en segundo plano antes de que se alcance el estado de visualización completa, deberás retrasar reportFullyDrawn()
para obtener una medición del TTFD más precisa. Si quieres obtener información para retrasar reportFullyDrawn()
, consulta la siguiente sección.
Cómo mejorar la precisión de los tiempos de inicio
Si tu app realiza una carga diferida y la visualización inicial no incluye todos los recursos, como cuando la app recupera imágenes de la red, te recomendamos que retrases la llamada a reportFullyDrawn
hasta después que tu app se vuelva utilizable para incluir la propagación de la lista como parte del tiempo de tus comparativas.
Por ejemplo, si la IU contiene una lista dinámica, como una RecyclerView
o una lista diferida, esta podría completarse con una tarea en segundo plano que finaliza después de que se obtiene la lista por primera vez y, por lo tanto, después de que la IU se marca como visualizada 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. Lanza el generador de informes después de que la tarea en segundo plano termine de completar 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 informes 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. No se llama a reportFullyDrawn()
hasta que se completan todas las tareas, independientemente del orden.
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(); } }
Si tu app usa Jetpack Compose, puedes utilizar las siguientes APIs para indicar el estado de visualización completa:
ReportDrawn
: Indica que el elemento componible está listo de inmediato para interactuar.ReportDrawnWhen
: Toma un predicado, comolist.count > 0
, para indicar cuándo el elemento componible está listo para la interacción.ReportDrawnAfter
: Toma un método de suspensión que, una vez completado, indica que el elemento componible está listo para la interacción.
Cómo identificar cuellos de botella
Para detectar cuellos de botella, puedes usar el Generador de perfiles de CPU de Android Studio. Para obtener más información, consulta Cómo inspeccionar la actividad de la CPU con el Generador de perfiles de CPU.
Además, puedes obtener información sobre los posibles cuellos de botella con el seguimiento intercalado de los métodos onCreate()
de tus apps y actividades. Para obtener más información sobre el seguimiento intercalado, consulta la documentación sobre las funciones de Trace
y la descripción general del registro del sistema.
Cómo resolver problemas habituales
En esta sección, se analizan varios problemas que, a menudo, afectan el rendimiento de inicio de las apps. Por lo general, estos problemas están relacionados con la inicialización de apps y objetos de actividades, además de con la carga de pantallas.
Inicialización de app intensa
El rendimiento del inicio puede verse afectado cuando tu código anula el objeto Application
y ejecuta trabajo pesado o lógica compleja cuando se inicializa ese objeto. Es posible que tu app pierda tiempo durante el inicio si las subclases de Application
realizan inicializaciones que aún no son necesarias.
Algunas de estas pueden ser completamente innecesarias, como cuando se inicializa la información de estado para la actividad principal cuando la app se inicia en respuesta a un intent. Con un intent, la app usa solo un subconjunto de los datos de estado inicializados con anterioridad.
Otros desafíos que se presentan durante la inicialización de una app incluyen eventos de recolección de elementos no utilizados, que tienen un gran impacto o son numerosos, o E/S de disco que se producen junto con la inicialización y bloquean aún más el proceso. La recolección de elementos no utilizados se debe tener en cuenta específicamente para el tiempo de ejecución de Dalvik; el tiempo de ejecución de Android Runtime (ART) realiza la recolección de elementos no utilizados en simultáneo, lo que minimiza el impacto de esa operación.
Cómo diagnosticar el problema
Puedes usar el seguimiento de métodos o el seguimiento intercalado para diagnosticar el problema.
Seguimiento de métodos
Cuando se ejecuta el Generador de perfiles de CPU, se descubre que el método callApplicationOnCreate()
llama finalmente a tu método com.example.customApplication.onCreate
. Si la herramienta muestra que la ejecución de estos métodos tarda demasiado en completarse, sigue explorando para descubrir qué trabajo se está desarrollando.
Seguimiento intercalado
Usa el seguimiento intercalado para investigar posibles culpables, incluidos los siguientes:
- La función
onCreate()
inicial de tu app - Cualquier objeto singleton que tu app inicializa
- Cualquier E/S de disco, deserialización o bucle cerrado que pueda producirse durante el cuello de botella
Soluciones para el problema
Si el problema se relaciona con inicializaciones innecesarias o E/S de disco, la solución es la inicialización diferida. En otras palabras, solo inicializa los objetos que son inmediatamente necesarios. En lugar de crear objetos globales estáticos, cambia a un patrón singleton, en el que la app inicializa objetos solo la primera vez que los necesita.
Además, considera usar un framework de inserción de dependencias, como Hilt, que crea objetos y dependencias cuando se los inserta por primera vez.
Si la app usa proveedores de contenido para inicializar los componentes de la app al inicio, considera usar la biblioteca de App Startup.
Inicialización de actividad intensa
A menudo, la creación de actividades implica mucho trabajo de sobrecarga alta. En general, hay oportunidades de optimizar este trabajo para obtener mejoras de rendimiento. Estos son algunos problemas habituales:
- Ampliación de diseños grandes o complejos
- Bloqueo de la pantalla cuando se escribe en el disco, o E/S de red.
- Carga y decodificación de mapas de bits
- Rasterización de objetos
VectorDrawable
- Inicialización de otros subsistemas de la actividad
Cómo diagnosticar el problema
También en este caso, tanto el registro de métodos como el registro en línea pueden ser útiles.
Seguimiento de métodos
Cuando uses el Generador de perfiles de CPU, presta atención a los constructores de la subclase Application
y los métodos com.example.customApplication.onCreate()
de tu app.
Si la herramienta muestra que la ejecución de estos métodos tarda demasiado en completarse, sigue explorando para descubrir qué trabajo se está desarrollando.
Seguimiento intercalado
Usa el seguimiento intercalado para investigar posibles culpables, incluidos los siguientes:
- La función
onCreate()
inicial de tu app - Cualquier objeto singleton que tu app inicializa
- Cualquier E/S de disco, deserialización o bucle cerrado que pueda producirse durante el cuello de botella
Soluciones para el problema
Si bien hay varios cuellos de botella posibles, los siguientes son dos problemas y soluciones habituales:
- Cuando más grande es tu jerarquía de vistas, más tarda la app en ampliarla. Puedes seguir estos dos pasos para abordar el problema:
- Acopla la jerarquía de vistas reduciendo los diseños redundantes o anidados.
- No amplíes partes de la IU que no necesiten ser visibles durante el inicio.
En su lugar, usa un objeto
ViewStub
como marcador de posición para subjerarquías. que la app pueda ampliar en un tiempo más apropiado.
- Tener toda la inicialización de recursos en el subproceso principal también puede hacer que el inicio sea más lento. Puedes tratar este problema de la siguiente manera:
- Transfiere toda la inicialización de recursos para que la app pueda llevarla a cabo de manera diferida en otro subproceso.
- Permite que la app cargue y muestre las vistas, y luego actualiza las propiedades visuales que dependen de mapas de bits y otros recursos.
Pantallas de presentación personalizadas
Es posible que observes tiempo adicional durante el inicio si ya usaste uno de los siguientes métodos para implementar una pantalla de presentación personalizada en Android 11 (nivel de API 30) o versiones anteriores:
- Usar el atributo del tema
windowDisablePreview
para desactivar la pantalla en blanco inicial que dibuja el sistema durante el inicio - Usar una
Activity
dedicada
A partir de Android 12, se requiere migrar a la API de SplashScreen
.
Esta API permite un tiempo de inicio más rápido y te permite ajustar la pantalla de presentación de las siguientes maneras:
- Establece un tema para cambiar la apariencia de la pantalla de presentación.
- Controla el tiempo en el que se muestra la pantalla de presentación con
windowSplashScreenAnimationDuration
. - Personaliza la animación de la pantalla de presentación y controla de forma correcta la animación para descartar la pantalla de presentación.
Además, la biblioteca de compatibilidad proporciona portabilidad a versiones anteriores de la API de SplashScreen
para permitir la retrocompatibilidad y crear una apariencia coherente para la visualización de la pantalla de presentación en todas las versiones de Android.
Consulta la guía de migración de la pantalla de presentación para obtener más detalles.
Recomendaciones para ti
- Nota: El texto del vínculo se muestra cuando JavaScript está desactivado
- Renderización lenta
- Cómo capturar métricas de macrocomparativas
- Cómo crear perfiles de Baseline{:#creating-profile-rules}