Types de règles supplémentaires

R8 vous permet d'ajouter des règles qui affectent l'optimisation de votre application, en plus des règles de conservation. Ajoutez ces règles dans le même fichier proguard-rules.pro où vous gérez vos règles de conservation.

Les règles se répartissent dans les catégories suivantes :

  • Hypothèses
    • -assumevalues
    • -assumenosideeffects
  • Autres optimisations
    • -convertchecknotnull
    • -maximumremovedandroidloglevel

Hypothèses

Ces règles indiquent à R8 qu'il peut faire des hypothèses spécifiques sur le comportement d'un code spécifique au moment de l'exécution.

-assumevalues

La règle -assumevalues indique à R8 que la valeur d'un champ ou la valeur de retour d'une méthode est toujours une constante spécifique ou se situe dans une plage définie au moment de l'exécution. -assumevalues est destiné aux éléments tels que les valeurs d'indicateur qui, au moment de la compilation, sont connues pour avoir des valeurs spécifiques au moment de l'exécution.

L'analyse statique standard de R8 n'est peut-être pas en mesure de déterminer les valeurs d'exécution des membres. Avec -assumevalues, vous indiquez à R8 d'assumer la valeur ou la plage spécifiée lors de l'optimisation du code. Cela permet à R8 d'effectuer des optimisations agressives.

La syntaxe de -assumevalues est semblable à celle permettant de conserver un member_specification, mais inclut également un return clause, comme suit :

<member_specification> return <value> | <range>

Les arguments <value> et <range> acceptent les valeurs et types suivants :

  • Valeurs spéciales : true, false, null, @NonNull
  • Valeurs primitives : int
  • Références de champ statiques (y compris les champs d'énumération)

Pour définir une plage, utilisez le format inclusif min..max. Par exemple, l'extrait suivant montre que la variable CUSTOM_VAL accepte les valeurs comprises entre 26 et 2147483647 :

-assumevalues public class com.example.Foo {
    public static int CUSTOM_VAL return 26..2147483647;
}

Vous pouvez utiliser cette règle dans les situations suivantes :

  • Pour les bibliothèques : pour s'assurer que, lorsque les applications sont optimisées, tous les hooks de débogage locaux sont supprimés du code de la bibliothèque publique.
  • Pour les applications : permet de supprimer des éléments tels que le code de débogage d'une application de version. Il est préférable d'utiliser des variantes de compilation et des variantes de jeux de sources ou de constantes spécifiques, mais si les jeux de sources de variantes ne fonctionnent pas dans votre cas, ou si vous avez besoin d'une garantie plus forte que les chemins de code sont entièrement supprimés, utilisez -assumevalues.

L'exemple suivant montre une classe dans laquelle R8 supprime les outils de débogage de la version optimisée d'une application :

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 règle suivante montre comment indiquer à R8 que la variable IS_OPTIMIZED_VERSION doit toujours être définie sur true.

-assumevalues class com.example.MyConfig {
    public static final boolean IS_OPTIMIZED_VERSION return true;
}

-assumenosideeffects

La règle -assumenosideeffects indique à R8 qu'il peut supposer que les membres spécifiés n'ont aucun effet secondaire. R8 peut supprimer complètement les appels à de telles méthodes qui n'ont pas de valeurs de retour ou qui renvoient une valeur fixe.

La syntaxe de -assumenosideeffects est semblable à celle de la conservation d'un member_specification.

L'exemple suivant montre comment indiquer à R8 que toutes les méthodes public static nommées log dans la classe DebugLogger ne doivent avoir aucun effet secondaire, ce qui lui permet de supprimer les appels à ces méthodes.

-assumenosideeffects class com.example.DebugLogger {
    public static void log(...);
}

Autres optimisations

Il s'agit d'optimisations plus avancées qui ne sont pas activées par défaut. Lorsque vous les activez, vous autorisez R8 à optimiser le code comme indiqué, en plus des optimisations par défaut.

-convertchecknotnull

Vous pouvez utiliser la règle -convertchecknotnull pour optimiser les vérifications de valeurs nulles. Cela s'applique à toute méthode qui accepte un paramètre d'objet et génère une exception si l'objet est nul, comme une assertion Kotlin standard. Le type et le message d'exception ne sont pas nécessairement les mêmes, mais le comportement de plantage conditionnel l'est.

Si une règle -convertchecknotnull correspond à une méthode donnée, chaque appel à cette méthode est remplacé par un appel à getClass() sur le premier argument. Les appels à getClass() servent de vérification de valeur nulle de remplacement et permettent à R8 de supprimer tous les arguments supplémentaires de la vérification de valeur nulle d'origine, tels que les allocations de chaînes coûteuses.

Voici la syntaxe à utiliser pour -convertchecknotnull :

-convertchecknotnull <class_specification> {
   <member_specification>;
}

Par exemple, si vous avez une classe Preconditions avec la méthode checkNotNull comme suit :

class Preconditions {
    fun <T> checkNotNull(value: T?): T {
        if (value == null) {
            throw NullPointerException()
        } else {
            return value
        }
    }
}

Utilisez la règle suivante :

-convertchecknotnull class com.example.package.Preconditions {
  void checkNotNull(java.lang.Object);
}

La règle convertit tous les appels à checkNotNull() en appels à getClass sur le premier argument. Dans cet exemple, un appel à checkNotNull(bar) est remplacé par bar.getClass(). Si bar était null, bar.getClass() générerait une NullPointerException, ce qui aurait un effet de vérification de la valeur nulle similaire, mais de manière plus efficace.

-maximumremovedandroidloglevel

Ce type de règle supprime les instructions de journalisation Android (comme Log.w(...) et Log.isLoggable(...)) à un niveau de journalisation donné ou inférieur.

Voici la syntaxe à utiliser pour maximumremovedandroidloglevel :

-maximumremovedandroidloglevel <log_level> [<class_specification>]

Si vous ne fournissez pas l'option class_specification, R8 applique la suppression des journaux à l'ensemble de l'application.

Voici les niveaux de journalisation :

Libellé du journal

Niveau de journalisation

VERBOSE

2

DEBUG

3

INFO

4

MISE EN GARDE

5

ERROR

6

ASSERT

7

Par exemple, si vous disposez du code suivant :

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");
  }
}

Avec la règle suivante :

# A level of 5 corresponds to a log level of WARNING.
-maximumremovedandroidloglevel 5 class Foo { void logSomething(); }

Le code optimisé est le suivant :

class Foo {
  private static final String TAG = "Foo";
  void logSomething() {
    Log.e(TAG, "Will be logged");
  }
}

Si vous fournissez plusieurs niveaux de journaux maximaux pour la même méthode, R8 utilise le niveau minimal. Par exemple, prenons les règles suivantes :

-maximumremovedandroidloglevel 7 class ** { void foo(); }
-maximumremovedandroidloglevel 4 class ** { void foo(); }

Le niveau de journalisation maximal supprimé pour foo() est alors de 4.