Elige las bibliotecas con cuidado

Para habilitar la optimización de la app, debes usar bibliotecas compatibles con la optimización de Android. Si una biblioteca no está configurada para la optimización de Android (por ejemplo, si usa reflexión sin incluir reglas de conservación asociadas), es posible que no sea adecuada para una app para Android. En esta página, se explica por qué algunas bibliotecas son más adecuadas para la optimización de apps y se proporcionan sugerencias generales para ayudarte a elegir.

Prefiere la generación de código a la reflexión

En general, debes elegir bibliotecas que usen generación de código (codegen) en lugar de reflexión. Con la generación de código, el optimizador puede determinar con mayor facilidad qué código se usa realmente en el tiempo de ejecución y qué código se puede quitar. Puede ser difícil saber si una biblioteca usa codegen o reflexión, pero hay algunos indicios. Consulta las sugerencias para obtener ayuda.

Para obtener más información sobre la generación de código frente a la reflexión, consulta Optimización para autores de bibliotecas.

Sugerencias generales para elegir bibliotecas

Usa estas sugerencias para asegurarte de que tus bibliotecas sean compatibles con la optimización de la app.

Verifica si hay problemas de optimización

Cuando consideres usar una biblioteca nueva, revisa el registro de problemas y los debates en línea para verificar si hay problemas relacionados con la minimización o la configuración de la optimización de la app. Si es así, deberías buscar alternativas a esa biblioteca. Ten en cuenta lo siguiente:

  • Las bibliotecas de AndroidX y las bibliotecas como Hilt funcionan bien con la optimización de apps porque usan codegen en lugar de reflexión. Cuando usan la reflexión, proporcionan reglas de conservación mínimas para conservar solo el código que se necesita.
  • Las bibliotecas de serialización suelen usar la reflexión para evitar el código estándar cuando se instancian o serializan objetos. En lugar de enfoques basados en la reflexión (como Gson para JSON), busca bibliotecas que usen codegen para evitar estos problemas, por ejemplo, usando Kotlin Serialization.
  • Si es posible, se deben evitar las bibliotecas que incluyen reglas de conservación para todo el paquete. Las reglas de conservación para todo el paquete pueden ayudar a resolver errores, pero las reglas de conservación amplias se deben refinar con el tiempo para conservar solo el código necesario. Para obtener más información, consulta Adopta las optimizaciones de forma incremental.
  • Las bibliotecas no deberían requerir que copies y pegues reglas de conservación de la documentación en un archivo de tu proyecto, especialmente las reglas de conservación para todo el paquete. A largo plazo, estas reglas se convierten en una carga de mantenimiento para el desarrollador de la app y son difíciles de optimizar y cambiar con el tiempo.

Habilita la optimización después de agregar una biblioteca nueva

Cuando agregues una biblioteca nueva, habilita la optimización después y verifica si hay errores. Si hay errores, busca alternativas a esa biblioteca o escribe reglas de conservación. Si una biblioteca no es compatible con la optimización, informa un error sobre ella.

Las reglas son acumulativas

Ten en cuenta que las reglas de conservación son acumulativas. Esto significa que ciertas reglas que incluye una dependencia de biblioteca no se pueden quitar y podrían afectar la compilación de otras partes de tu app. Por ejemplo, si una biblioteca incluye una regla para inhabilitar las optimizaciones de código, esta inhabilitará las optimizaciones de todo tu proyecto.

Verifica el uso de la reflexión (avanzado)

Es posible que puedas saber si una biblioteca usa la reflexión inspeccionando su código. Si la biblioteca usa la reflexión, verifica que proporcione reglas de conservación asociadas. Es probable que una biblioteca use la reflexión si hace lo siguiente:

  • Usa clases o métodos de los paquetes kotlin.reflect o java.lang.reflect.
  • Usa las funciones Class.forName o classLoader.getClass
  • Lee las anotaciones en el tiempo de ejecución, por ejemplo, si almacena un valor de anotación con val value = myClass.getAnnotation() o val value = myMethod.getAnnotation() y, luego, hace algo con value.
  • Llama a métodos usando el nombre del método como una cadena, por ejemplo:

    // Calls the private `processData` API with reflection
    myObject.javaClass.getMethod("processData", DataType::class.java)
    ?.invoke(myObject, data)
    

Cómo filtrar las reglas de conservación incorrectas (avanzado)

Debes evitar las bibliotecas con reglas de conservación que retengan código que realmente debería quitarse. Sin embargo, si debes usarlas, puedes filtrar las reglas, como se muestra en el siguiente código:

// If you're using AGP 8.4 and higher
buildTypes {
    release {
        optimization.keepRules {
          it.ignoreFrom("com.somelibrary:somelibrary")
        }
    }
}

// If you're using AGP 7.3-8.3
buildTypes {
    release {
        optimization.keepRules {
          it.ignoreExternalDependencies("com.somelibrary:somelibrary")
        }
    }
}

Caso de estudio: Por qué Gson se interrumpe con las optimizaciones

Gson es una biblioteca de serialización que suele causar problemas con la optimización de la app porque usa mucho la reflexión. En el siguiente fragmento de código, se muestra cómo se usa Gson de forma habitual, lo que puede provocar fallas con facilidad durante el tiempo de ejecución. Ten en cuenta que, cuando usas Gson para obtener una lista de objetos User, no llamas al constructor ni pasas una fábrica a la función fromJson(). Construir o consumir clases definidas por la app sin ninguno de los siguientes elementos es un indicio de que una biblioteca podría estar usando reflexión abierta:

  • Clase de la app que implementa una biblioteca, o una interfaz o clase estándar
  • Complemento de generación de código, como KSP
class User(val name: String)
class UserList(val users: List<User>)

// This code runs in debug mode, but crashes when optimizations are enabled
Gson().fromJson("""[{"name":"myname"}]""", User::class.java).toString()

Cuando R8 analiza este código y no ve que se haya creado una instancia de UserList o User en ningún lugar, puede cambiar el nombre de los campos o quitar los constructores que no parecen usarse, lo que provoca que la app falle. Si usas otras bibliotecas de manera similar, debes verificar que no interfieran en la optimización de la app y, si lo hacen, evitarlas.

Ten en cuenta que Room y Hilt construyen tipos definidos por la app, pero usan codegen para evitar la necesidad de reflexión.