R8 te permite agregar reglas que afectan la optimización de tu app, además de las reglas de conservación. Agrega estas reglas en el mismo archivo proguard-rules.pro en el que mantienes tus reglas de conservación.
Las reglas se clasifican en las siguientes categorías:
- Suposiciones
-assumevalues-assumenosideeffects
- Otras optimizaciones
-convertchecknotnull-maximumremovedandroidloglevel
Suposiciones
Estas reglas le indican a R8 que puede hacer suposiciones específicas sobre el comportamiento de código específico en el tiempo de ejecución.
-assumevalues
La regla -assumevalues le indica a R8 que el valor de un campo o el valor de devolución de un método siempre es una constante específica o se encuentra dentro de un rango definido en el tiempo de ejecución. -assumevalues está diseñado para elementos como los valores de marcas que, en el momento de la compilación, se sabe que tienen valores específicos en el tiempo de ejecución.
Es posible que el análisis estático estándar de R8 no pueda determinar los valores de tiempo de ejecución de los miembros. Con -assumevalues, le indicas a R8 que suponga el valor o el rango especificados cuando optimice el código. Esto permite que R8 realice optimizaciones agresivas.
La sintaxis de -assumevalues es similar a la de conservar un member_specification, pero también incluye un return clause de la siguiente manera:
<member_specification> return <value> | <range>
Los argumentos <value> y <range> admiten los siguientes valores y tipos:
- Valores especiales:
true, false, null, @NonNull - Valores primitivos:
int - Referencias de campos estáticos (incluidos los campos de enumeración)
Para definir un rango, usa el formato inclusivo min..max. Por ejemplo, el siguiente fragmento muestra que la variable CUSTOM_VAL acepta valores de 26 a 2147483647:
-assumevalues public class com.example.Foo {
public static int CUSTOM_VAL return 26..2147483647;
}
Puedes usar esta regla en las siguientes situaciones:
- Para bibliotecas: Para garantizar que, cuando se optimicen las apps, se quiten todos los hooks de depuración locales del código de la biblioteca pública
- Para apps: Para quitar elementos como el código de depuración de una app de lanzamiento. Es preferible usar variantes de compilación y variantes de conjuntos de fuentes o constantes específicos, pero si los conjuntos de fuentes de variantes no funcionan en tu caso, o si necesitas una garantía más sólida de que las rutas de código se quiten por completo, usa
-assumevalues.
En el siguiente ejemplo, se muestra una clase en la que R8 quita las herramientas de depuración de la versión optimizada de una app:
package com.example;
public class MyConfig {
// This field is initialized to false but is overwritten by a resource
// value or other mechanism in the final build process. R8's static analysis
// might see the initial 'false' but the runtime value is known to be
// 'true'.
public static final boolean IS_OPTIMIZED_VERSION = false;
}
// In another class:
public void initFeatures() {
if (MyConfig.IS_OPTIMIZED_VERSION) {
System.out.println("Starting optimized features...");
android.util.Log.d(TAG, "Starting optimized features...");
initOptimizedService();
} else {
android.util.Log.d(TAG, "Starting debug/logging features...");
initDebugTools();
}
}
La siguiente regla muestra cómo indicarle a R8 que siempre se espera que la variable IS_OPTIMIZED_VERSION se establezca en true.
-assumevalues class com.example.MyConfig {
public static final boolean IS_OPTIMIZED_VERSION return true;
}
-assumenosideeffects
La regla -assumenosideeffects le indica a R8 que puede suponer que los miembros especificados no tienen efectos secundarios. R8 puede quitar por completo las llamadas a esos métodos que no tienen valores de devolución o que devuelven un valor fijo.
La sintaxis de -assumenosideeffects es similar a la de mantener un member_specification.
En el siguiente ejemplo, se muestra cómo indicarle a R8 que todos los métodos public static llamados log dentro de la clase DebugLogger no deben tener efectos secundarios, lo que le permite quitar las llamadas a estos métodos.
-assumenosideeffects class com.example.DebugLogger {
public static void log(...);
}
Otras optimizaciones
Estas son algunas optimizaciones más avanzadas que no están habilitadas de forma predeterminada. Cuando las habilitas, permites que R8 optimice el código según las instrucciones, además de las optimizaciones predeterminadas.
-convertchecknotnull
Puedes usar la regla -convertchecknotnull para optimizar las verificaciones de valores nulos. Esto se aplica a cualquier método que tome un parámetro de objeto y arroje una excepción si el objeto es nulo, de manera similar a una aserción estándar de Kotlin. El tipo y el mensaje de la excepción no son necesariamente los mismos, pero el comportamiento de bloqueo condicional sí lo es.
Si una regla de -convertchecknotnull coincide con un método determinado, cada llamada a ese método se reemplaza por una llamada a getClass() en el primer argumento. Las llamadas a getClass() sirven como reemplazo de la verificación de nulos y permiten que R8 quite los argumentos adicionales de la verificación de nulos original, como las asignaciones de cadenas costosas.
La sintaxis de -convertchecknotnull es la siguiente:
-convertchecknotnull <class_specification> {
<member_specification>;
}
Por ejemplo, si tienes la clase Preconditions con el método checkNotNull de la siguiente manera:
class Preconditions {
fun <T> checkNotNull(value: T?): T {
if (value == null) {
throw NullPointerException()
} else {
return value
}
}
}
Usa la siguiente regla:
-convertchecknotnull class com.example.package.Preconditions {
void checkNotNull(java.lang.Object);
}
La regla convierte todas las llamadas a checkNotNull() en llamadas a getClass en el primer argumento. En este ejemplo, una llamada a checkNotNull(bar) se reemplaza por bar.getClass(). Si bar fuera null, bar.getClass() arrojaría un NullPointerException, lo que lograría un efecto similar de verificación de nulos, pero de manera más eficiente.
-maximumremovedandroidloglevel
Este tipo de regla quita las sentencias de registro de Android (como Log.w(...) y Log.isLoggable(...)) en un nivel de registro determinado o inferior.
La sintaxis de maximumremovedandroidloglevel es la siguiente:
-maximumremovedandroidloglevel <log_level> [<class_specification>]
Si no proporcionas el class_specification opcional, R8 aplica la eliminación de registros a toda la app.
Los niveles de registro son los siguientes:
Etiqueta de registro |
Nivel de registro |
VERBOSE |
2 |
DEBUG |
3 |
INFO |
4 |
ADVERTENCIA |
5 |
ERROR |
6 |
ASSERT |
7 |
Por ejemplo, si tienes el siguiente código:
class Foo {
private static final String TAG = "Foo";
void logSomething() {
if (Log.isLoggable(TAG, WARNING)) {
Log.e(TAG, "Won't be logged");
}
Log.w(TAG, "Won't be logged");
Log.e(TAG, "Will be logged");
}
}
Con la siguiente regla:
# A level of 5 corresponds to a log level of WARNING.
-maximumremovedandroidloglevel 5 class Foo { void logSomething(); }
El código optimizado es el siguiente:
class Foo {
private static final String TAG = "Foo";
void logSomething() {
Log.e(TAG, "Will be logged");
}
}
Si proporcionas varios niveles de registro máximos para el mismo método, R8 usa el nivel mínimo. Por ejemplo, dadas las siguientes reglas:
-maximumremovedandroidloglevel 7 class ** { void foo(); }
-maximumremovedandroidloglevel 4 class ** { void foo(); }
entonces, el nivel de registro máximo quitado para foo() es 4.