Agrega reglas de retención

En un nivel alto, una regla de conservación especifica una clase (o subclase o implementación) y, luego, los miembros (métodos, constructores o campos) dentro de esa clase que se deben conservar.

La sintaxis general de una regla de conservación es la siguiente. Sin embargo, ciertas opciones de conservación no aceptan el parámetro opcional keep_option_modfier.


-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>

El siguiente es un ejemplo de una regla de conservación que usa keepclassmembers como la opción de conservación, allowoptimization como el modificador y conserva someSpecificMethod() de com.example.MyClass:

-keepclassmembers,allowoptimization class com.example.MyClass {
  void someSpecificMethod();
}

Opción para conservar

La opción de conservación es la primera parte de la regla de conservación. Especifica qué aspectos de una clase se deben conservar. Existen seis opciones de conservación diferentes: keep, keepclassmembers, keepclasseswithmembers, keepnames, keepclassmembernames y keepclasseswithmembernames.

En la siguiente tabla, se describen estas opciones de conservación:

Opción para conservar Descripción
keepclassmembers Conserva solo los miembros especificados si la clase existe después de la optimización.
keep Conserva las clases y los miembros (campos y métodos) especificados, lo que impide que se optimicen.

Nota: Por lo general, keep solo se debe usar con modificadores de la opción de conservación, ya que keep por sí solo impide que se realicen optimizaciones de cualquier tipo en las clases coincidentes.
keepclasseswithmembers Conserva una clase y sus miembros especificados solo si la clase tiene todos los miembros de la especificación de la clase.
keepclassmembernames Impide que se cambie el nombre de los miembros de la clase especificada, pero no impide que se quite la clase o sus miembros.

Nota: El significado de esta opción a menudo se malinterpreta. Considera usar la opción equivalente -keepclassmembers,allowshrinking en su lugar.
keepnames Evita que se cambie el nombre de las clases y sus miembros, pero no impide que se quiten por completo si se consideran no utilizados.

Nota: El significado de esta opción a menudo se malinterpreta. Considera usar la opción equivalente -keep,allowshrinking en su lugar.
keepclasseswithmembernames Evita que se cambie el nombre de las clases y sus miembros especificados, pero solo si los miembros existen en el código final. No impide la eliminación de código.

Nota: El significado de esta opción a menudo se malinterpreta. Considera usar la opción equivalente -keepclasseswithmembers,allowshrinking en su lugar.

Elige la opción de conservación adecuada

Elegir la opción de conservación correcta es fundamental para determinar la optimización adecuada para tu app. Algunas opciones de conservación reducen el código, un proceso por el cual se quita el código al que no se hace referencia, mientras que otras ofuscan o cambian el nombre del código. En la siguiente tabla, se indican las acciones de las distintas opciones de conservación:

Opción para conservar Reduce las clases Ofusca clases Reduce miembros Obfuscates members
keep
keepclassmembers
keepclasseswithmembers
keepnames
keepclassmembernames
keepclasseswithmembernames

Modificador de la opción de conservación

Un modificador de opción de conservación se usa para controlar el alcance y el comportamiento de una regla de conservación. Puedes agregar 0 o más modificadores de la opción de conservación a tu regla de conservación.

En la siguiente tabla, se describen los valores posibles para un modificador de opción de conservación:

Valor Descripción
allowoptimization Permite la optimización de los elementos especificados. Sin embargo, los elementos especificados no se renombran ni se quitan.
allowobfucastion Permite cambiar el nombre de los elementos especificados. Sin embargo, los elementos no se quitarán ni se optimizarán de ninguna otra manera.
allowshrinking Permite quitar los elementos especificados si R8 no encuentra referencias a ellos. Sin embargo, los elementos no se renombran ni se optimizan de ninguna otra manera.
includedescriptorclasses Indica a R8 que conserve todas las clases que aparecen en los descriptores de los métodos (tipos de parámetros y tipos de datos que se muestran) y los campos (tipos de campos) que se conservan.
allowaccessmodification Permite que R8 cambie (por lo general, amplíe) los modificadores de acceso (public, private, protected) de clases, métodos y campos durante el proceso de optimización.
allowrepackage Permite que R8 mueva clases a diferentes paquetes, incluido el paquete predeterminado (raíz).

Especificación de la clase

Debes especificar una clase, una superclase o una interfaz implementada como parte de una regla de conservación. Todas las clases, incluidas las del espacio de nombres java.lang, como java.lang.String, se deben especificar con su nombre de Java completamente calificado. Para comprender los nombres que se deben usar, inspecciona el código de bytes con las herramientas que se describen en Cómo obtener nombres de Java generados.

En el siguiente ejemplo, se muestra cómo debes especificar la clase MaterialButton:

  • Correcto: com.google.android.material.button.MaterialButton
  • Incorrecto: MaterialButton

Las especificaciones de clase también especifican los miembros dentro de una clase que se deben conservar. La siguiente regla conserva la clase MaterialButton y todos sus miembros:

-keep class com.google.android.material.button.MaterialButton { *; }

Subclases e implementaciones

Para segmentar una subclase o una clase que implementa una interfaz, usa extend y implements, respectivamente.

Por ejemplo, si tienes la clase Bar con la subclase Foo de la siguiente manera:

class Foo : Bar()

La siguiente regla de conservación preserva todas las subclases de Bar. Ten en cuenta que la regla de conservación no incluye la superclase Bar en sí.

-keep class * extends Bar

Si tienes la clase Foo que implementa Bar, haz lo siguiente:

class Foo : Bar

La siguiente regla de conservación preserva todas las clases que implementan Bar. Ten en cuenta que la regla de conservación no incluye la interfaz Bar en sí.

-keep class * implements Bar

Modificador de acceso

Puedes especificar modificadores de acceso, como public, private, static y final, para que tus reglas de conservación sean más precisas.

Por ejemplo, la siguiente regla mantiene todas las clases public dentro del paquete api y sus subpaquetes, y todos los miembros públicos y protegidos en estas clases.

-keep public class com.example.api.** { public protected *; }

También puedes usar modificadores para los miembros dentro de una clase. Por ejemplo, la siguiente regla solo conserva los métodos public static de una clase Utils:

-keep class com.example.Utils {
    public static void *(...);
}

Modificadores específicos de Kotlin

R8 no admite modificadores específicos de Kotlin, como internal y suspend. Usa los siguientes lineamientos para conservar esos campos.

  • Para conservar una clase, un método o un campo internal, trátalo como público. Por ejemplo, considera el siguiente código fuente de Kotlin:

    package com.example
    internal class ImportantInternalClass {
      internal f: Int
      internal fun m() {}
    }
    

    Las clases, los métodos y los campos de internal son public en los archivos .class que produce el compilador de Kotlin, por lo que debes usar la palabra clave public, como se muestra en el siguiente ejemplo:

    -keepclassmembers public class com.example.ImportantInternalClass {
      public int f;
      public void m();
    }
    
  • Cuando se compila un miembro suspend, haz que coincida su firma compilada en la regla de conservación.

    Por ejemplo, si tienes la función fetchUser definida como se muestra en el siguiente fragmento:

    suspend fun fetchUser(id: String): User
    

    Cuando se compila, su firma en el bytecode se ve de la siguiente manera:

    public final Object fetchUser(String id, Continuation<? super User> continuation);
    

    Para escribir una regla de conservación para esta función, debes hacer coincidir esta firma compilada o usar ....

    A continuación, se muestra un ejemplo del uso de la firma compilada:

    -keepclassmembers class com.example.repository.UserRepository {
    public java.lang.Object fetchUser(java.lang.String,  kotlin.coroutines.Continuation);
    }
    

    A continuación, se muestra un ejemplo con ...:

    -keepclassmembers class com.example.repository.UserRepository {
    public java.lang.Object fetchUser(...);
    }
    

Especificación del miembro

La especificación de la clase incluye, de forma opcional, los miembros de la clase que se conservarán. Si especificas uno o más miembros para una clase, la regla solo se aplica a esos miembros.

Por ejemplo, para conservar una clase específica y todos sus miembros, usa lo siguiente:

-keep class com.myapp.MyClass { *; }

Para conservar solo la clase y no sus miembros, usa lo siguiente:

-keep class com.myapp.MyClass

La mayoría de las veces, querrás especificar algunos miembros. Por ejemplo, el siguiente ejemplo mantiene el campo público text y el método público updateText() dentro de la clase MyClass.

-keep class com.myapp.MyClass {
    public java.lang.String text;
    public void updateText(java.lang.String);
}

Para conservar todos los campos y métodos públicos, consulta el siguiente ejemplo:

-keep public class com.example.api.ApiClient {
    public *;
}

Métodos

La sintaxis para especificar un método en la especificación de miembro de una regla de conservación es la siguiente:

[<access_modifier>] [<return_type>] <method_name>(<parameter_types>);

Por ejemplo, la siguiente regla de conservación mantiene un método público llamado setLabel() que devuelve void y toma un String.

-keep class com.example.MyView {
    public void setLabel(java.lang.String);
}

Puedes usar <methods> como un atajo para hacer coincidir todos los métodos de una clase de la siguiente manera:

-keep class com.example.MyView {
    <methods>;
}

Para obtener más información sobre cómo especificar tipos para los tipos de parámetros y de devolución, consulta Tipos.

Constructores

Para especificar un constructor, usa <init>. La sintaxis para especificar un constructor en la especificación de miembro de una regla de conservación es la siguiente:

[<access_modifier>] <init>(parameter_types);

Por ejemplo, la siguiente regla de conservación mantiene un constructor View personalizado que toma un Context y un AttributeSet.

-keep class com.example.ui.MyCustomView {
    public <init>(android.content.Context, android.util.AttributeSet);
}

Para conservar todos los constructores públicos, usa el siguiente ejemplo como referencia:

-keep class com.example.ui.MyCustomView {
    public <init>(...);
}

Campos

La sintaxis para especificar un campo en la especificación de miembro de una regla de conservación es la siguiente:

[<access_modifier>...] [<type>] <field_name>;

Por ejemplo, la siguiente regla de conservación mantiene un campo de cadena privado llamado userId y un campo de número entero estático público llamado STATUS_ACTIVE:

-keep class com.example.models.User {
    private java.lang.String userId;
    public static int STATUS_ACTIVE;
}

Puedes usar <fields> como un acceso directo para que coincidan todos los campos de una clase de la siguiente manera:

-keep class com.example.models.User {
    <fields>;
}

Funciones a nivel del paquete

Para hacer referencia a una función de Kotlin definida fuera de una clase (comúnmente llamada función de nivel superior), asegúrate de usar el nombre de Java generado para la clase que el compilador de Kotlin agrega de forma implícita. El nombre de la clase es el nombre de archivo de Kotlin con Kt anexado. Por ejemplo, si tienes un archivo de Kotlin llamado MyClass.kt definido de la siguiente manera:

package com.example.myapp.utils

// A top-level function not inside a class
fun isEmailValid(email: String): Boolean {
    return email.contains("@")
}

Para escribir una regla de conservación para la función isEmailValid, la especificación de la clase debe segmentarse para la clase MyClassKt generada:

-keep class com.example.myapp.utils.MyClassKt {
    public static boolean isEmailValid(java.lang.String);
}

Tipos

En esta sección, se describe cómo especificar los tipos de devolución, los tipos de parámetros y los tipos de campos en las especificaciones de miembros de reglas de conservación. Recuerda usar los nombres de Java generados para especificar los tipos si son diferentes del código fuente de Kotlin.

Tipos primitivos

Para especificar un tipo primitivo, usa su palabra clave de Java. R8 reconoce los siguientes tipos primitivos: boolean, byte, short, char, int, long, float y double.

A continuación, se muestra un ejemplo de regla con un tipo primitivo:

# Keeps a method that takes an int and a float as parameters.
-keepclassmembers class com.example.Calculator {
    public void setValues(int, float);
}

Tipos genéricos

Durante la compilación, el compilador de Kotlin/Java borra la información de tipo genérico, por lo que, cuando escribes reglas de conservación que involucran tipos genéricos, debes orientar la representación compilada de tu código, y no el código fuente original. Para obtener más información sobre cómo se cambian los tipos genéricos, consulta Borrado de tipos.

Por ejemplo, si tienes el siguiente código con un tipo genérico no vinculado definido en Box.kt:

package com.myapp.data

class Box<T>(val item: T) {
    fun getItem(): T {
        return item
    }
}

Después del borrado de tipos, T se reemplaza por Object. Para conservar el constructor y el método de la clase, tu regla debe usar java.lang.Object en lugar del T genérico.

Una regla de conservación de ejemplo sería la siguiente:

# Keep the constructor and methods of the Box class.
-keep class com.myapp.data.Box {
    public init(java.lang.Object);
    public java.lang.Object getItem();
}

Si tienes el siguiente código con un tipo genérico vinculado en NumberBox.kt:

package com.myapp.data

// T is constrained to be a subtype of Number
class NumberBox<T : Number>(val number: T)

En este caso, el borrado de tipos reemplaza T por su límite, java.lang.Number.

Una regla de conservación de ejemplo sería la siguiente:

-keep class com.myapp.data.NumberBox {
    public init(java.lang.Number);
}

Cuando se usan tipos genéricos específicos de la app como clase base, también es necesario incluir reglas de conservación para las clases base.

Por ejemplo, para el siguiente código:

package com.myapp.data

data class UnpackOptions(val useHighPriority: Boolean)

// The generic Box class with UnpackOptions as the bounded type
class Box<T: UnpackOptions>(val item: T) {
}

Puedes usar una regla de conservación con includedescriptorclasses para conservar la clase UnpackOptions y el método de clase Box con una sola regla de la siguiente manera:

-keep,includedescriptorclasses class com.myapp.data.Box {
    public <init>(com.myapp.data.UnpackOptions);
}

Para conservar una función específica que procesa una lista de objetos, debes escribir una regla que coincida con precisión con la firma de la función. Ten en cuenta que, debido a que los tipos genéricos se borran, un parámetro como List<Product> se ve como java.util.List.

Por ejemplo, si tienes una clase de utilidad con una función que procesa una lista de objetos Product de la siguiente manera:

package com.myapp.utils

import com.myapp.data.Product
import android.util.Log

class DataProcessor {
    // This is the function we want to keep
    fun processProducts(products: List<Product>) {
        Log.d("DataProcessor", "Processing ${products.size} products.")
        // Business logic ...
    }
}

// The data class used in the list (from the previous example)
package com.myapp.data
data class Product(val id: String, val name: String)

Podrías usar la siguiente regla de conservación para proteger solo la función processProducts:

-keep class com.myapp.utils.DataProcessor {
    public void processProducts(java.util.List);
}

Tipos de array

Para especificar un tipo de array, agrega [] al tipo de componente para cada dimensión del array. Esto se aplica tanto a los tipos de clase como a los tipos primitivos.

  • Array de clases unidimensional: java.lang.String[]
  • Array primitivo bidimensional: int[][]

Por ejemplo, si tienes el siguiente código:

package com.example.data

class ImageProcessor {
  fun process(): ByteArray {
    // process image to return a byte array
  }
}

Podrías usar la siguiente regla de conservación:

# Keeps a method that returns a byte array.
-keepclassmembers class com.example.data.ImageProcessor {
    public byte[] process();
}

Comodines

En la siguiente tabla, se muestra cómo usar comodines para aplicar reglas de conservación a varias clases o miembros que coinciden con un patrón determinado.

Comodín Se aplica a clases o miembros Descripción
** Ambos Es el más común. Coincide con cualquier nombre de tipo, incluida cualquier cantidad de separadores de paquetes. Esto es útil para hacer coincidir todas las clases dentro de un paquete y sus subpaquetes.
* Ambos En el caso de las especificaciones de clase, coincide con cualquier parte de un nombre de tipo que no contenga separadores de paquetes (.).
En el caso de las especificaciones de miembros, coincide con cualquier nombre de método o campo. Cuando se usa por sí solo, también es un alias para **.
? Ambos Coincide con cualquier carácter único en un nombre de clase o miembro.
*** Miembros Coincide con cualquier tipo, incluidos los tipos primitivos (como int), los tipos de clase (como java.lang.String) y los tipos de array de cualquier dimensión (como byte[][]).
Miembros Coincide con cualquier lista de parámetros para un método.
% Miembros Coincide con cualquier tipo primitivo (como "int", "float", "boolean" o cualquier otro).

Estos son algunos ejemplos de cómo usar los comodines especiales:

  • Si tienes varios métodos con el mismo nombre que toman diferentes tipos primitivos como entradas, puedes usar % para escribir una regla de conservación que los conserve a todos. Por ejemplo, esta clase DataStore tiene varios métodos setValue:

    class DataStore {
        fun setValue(key: String, value: Int) { ... }
        fun setValue(key: String, value: Boolean) { ... }
        fun setValue(key: String, value: Float) { ... }
    }
    

    La siguiente regla de conservación mantiene todos los métodos:

    -keep class com.example.DataStore {
        public void setValue(java.lang.String, %);
    }
    
  • Si tienes varias clases con nombres que varían en un carácter, usa ? para escribir una regla de conservación que las conserve todas. Por ejemplo, si tienes las siguientes clases:

    com.example.models.UserV1 {...}
    com.example.models.UserV2 {...}
    com.example.models.UserV3 {...}
    

    La siguiente regla de conservación mantiene todas las clases:

    -keep class com.example.models.UserV?
    
  • Para que coincidan las clases Example y AnotherExample (si fueran clases de nivel raíz), pero no com.foo.Example, usa la siguiente regla de conservación:

    -keep class *Example
    
  • Si usas * por sí solo, actúa como alias de **. Por ejemplo, las siguientes reglas de conservación son equivalentes:

    -keepclasseswithmembers class * { public static void main(java.lang.String[];) }
    
    -keepclasseswithmembers class ** { public static void main(java.lang.String[];) }
    

Inspecciona los nombres de Java generados

Cuando escribas reglas de conservación, debes especificar clases y otros tipos de referencia con sus nombres después de que se compilen en bytecode de Java (consulta Especificación de clase y Tipos para ver ejemplos). Para verificar cuáles son los nombres de Java generados para tu código, usa cualquiera de las siguientes herramientas en Android Studio:

  • Analizador de APK
  • Con el archivo fuente de Kotlin abierto, inspecciona el código de bytes. Para ello, ve a Tools > Kotlin > Show Kotlin Bytecode > Decompile.