Tipi di regole aggiuntivi

R8 ti consente di aggiungere regole che influiscono sull'ottimizzazione della tua app, oltre alle regole keep. Aggiungi queste regole nello stesso file proguard-rules.pro in cui gestisci le regole di conservazione.

Le regole rientrano nelle seguenti categorie:

  • Presupposti
    • -assumevalues
    • -assumenosideeffects
  • Altre ottimizzazioni
    • -convertchecknotnull
    • -maximumremovedandroidloglevel

Ipotesi

Queste regole indicano a R8 che può fare ipotesi specifiche sul comportamento di codice specifico in fase di runtime.

-assumevalues

La regola -assumevalues indica a R8 che il valore di un campo o il valore restituito di un metodo è sempre una costante specifica o rientra in un intervallo definito in fase di runtime. -assumevalues è destinato a elementi come i valori dei flag che in fase di compilazione sono noti per avere valori specifici in fase di runtime.

L'analisi statica standard di R8 potrebbe non essere in grado di determinare i valori di runtime dei membri. Con -assumevalues, indichi a R8 di presupporre il valore o l'intervallo specificato durante l'ottimizzazione del codice. In questo modo, R8 esegue ottimizzazioni aggressive.

La sintassi per -assumevalues è simile a quella per mantenere un member_specification, ma include anche un return clause come segue:

<member_specification> return <value> | <range>

Gli argomenti <value> e <range> supportano i seguenti valori e tipi:

  • Valori speciali: true, false, null, @NonNull
  • Valori primitivi: int
  • Riferimenti ai campi statici (inclusi i campi enum)

Per definire un intervallo, utilizza il formato inclusivo min..max. Ad esempio, lo snippet seguente mostra che la variabile CUSTOM_VAL accetta valori compresi tra 26 e 2147483647:

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

Puoi utilizzare questa regola nelle seguenti situazioni:

  • Per le librerie: per garantire che quando le app vengono ottimizzate tutti gli hook di debug locali vengano rimossi dal codice della libreria pubblica.
  • Per le app: per rimuovere elementi come il codice di debug da un'app di rilascio. È preferibile utilizzare varianti di build e varianti di set di origini o costanti specifici, ma se i set di origini delle varianti non funzionano per il tuo caso o se hai bisogno di una garanzia più solida che i percorsi del codice vengano rimossi completamente, utilizza -assumevalues.

L'esempio seguente mostra una classe in cui R8 rimuove gli strumenti di debug dalla versione ottimizzata di un'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 seguente regola mostra come comunicare a R8 che la variabile IS_OPTIMIZED_VERSION deve sempre essere impostata su true.

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

-assumenosideeffects

La regola -assumenosideeffects indica a R8 che può presumere che i membri specificati non abbiano effetti collaterali. R8 può rimuovere completamente le chiamate a questi metodi che non hanno valori restituiti o che restituiscono un valore fisso.

La sintassi di -assumenosideeffects è simile a quella per mantenere un member_specification.

L'esempio seguente mostra come indicare a R8 che tutti i metodi public static denominati log all'interno della classe DebugLogger non devono avere effetti collaterali, il che consente di rimuovere le chiamate a questi metodi.

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

Altre ottimizzazioni

Si tratta di ottimizzazioni più avanzate che non sono attive per impostazione predefinita. Quando li attivi, consenti a R8 di ottimizzare il codice come indicato, oltre alle ottimizzazioni predefinite.

-convertchecknotnull

Puoi utilizzare la regola -convertchecknotnull per ottimizzare i controlli null. Ciò vale per qualsiasi metodo che accetta un parametro oggetto e genera un'eccezione se l'oggetto è nullo, in modo simile a un'asserzione Kotlin standard. Il tipo e il messaggio di eccezione non sono necessariamente gli stessi, ma il comportamento di arresto anomalo condizionale sì.

Se una regola -convertchecknotnull corrisponde a un determinato metodo, ogni chiamata a quel metodo viene sostituita da una chiamata a getClass() sul primo argomento. Le chiamate a getClass() fungono da controllo null sostitutivo e consentono a R8 di rimuovere eventuali argomenti aggiuntivi del controllo null originale, ad esempio allocazioni di stringhe costose.

La sintassi per -convertchecknotnull è la seguente:

-convertchecknotnull <class_specification> {
   <member_specification>;
}

Ad esempio, se hai la classe Preconditions con il metodo checkNotNull come segue:

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

Utilizza la seguente regola:

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

La regola converte tutte le chiamate a checkNotNull() in chiamate a getClass nel primo argomento. In questo esempio, una chiamata a checkNotNull(bar) viene sostituita da bar.getClass(). Se bar fosse null, bar.getClass() genererebbe un NullPointerException, ottenendo un effetto di controllo del valore nullo simile, ma in modo più efficiente.

-maximumremovedandroidloglevel

Questo tipo di regola rimuove le istruzioni di logging di Android (come Log.w(...) e Log.isLoggable(...)) a un determinato livello di log o inferiore.

La sintassi per maximumremovedandroidloglevel è la seguente:

-maximumremovedandroidloglevel <log_level> [<class_specification>]

Se non fornisci class_specification facoltativo, R8 applica la rimozione dei log all'intera app.

I livelli di log sono i seguenti:

Etichetta log

Livello di log

VERBOSE

2

DEBUG

3

INFO

4

AVVISO

5

ERRORE

6

ASSERT

7

Ad esempio, se hai il seguente codice:

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 seguente regola:

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

Il codice ottimizzato è il seguente:

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

Se fornisci più livelli di log massimi per lo stesso metodo, R8 utilizza il livello minimo. Ad esempio, date le seguenti regole:

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

allora il livello di log massimo rimosso per foo() è 4.