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.