A livello generale, una regola di conservazione specifica una classe (o una sottoclasse o un'implementazione) e poi i membri (metodi, costruttori o campi) all'interno di quella classe da conservare.
La sintassi generale per una regola di conservazione è la seguente, tuttavia
alcune opzioni di conservazione non accettano keep_option_modfier
facoltativo.
-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>
Di seguito è riportato un esempio di regola di conservazione che utilizza keepclassmembers
come
opzione di conservazione, allowoptimization
come modificatore e conserva
someSpecificMethod()
da com.example.MyClass
:
-keepclassmembers,allowoptimization class com.example.MyClass {
void someSpecificMethod();
}
Opzione Mantieni
L'opzione di conservazione è la prima parte della regola di conservazione. Specifica gli aspetti
di una classe da conservare. Esistono sei diverse opzioni di conservazione, ovvero keep
,
keepclassmembers
, keepclasseswithmembers
, keepnames
,
keepclassmembernames
, keepclasseswithmembernames
.
La tabella seguente descrive queste opzioni di conservazione:
Opzione Keep | Descrizione |
---|---|
keepclassmembers |
Conserva solo i membri specificati se la classe esiste dopo l'ottimizzazione. |
keep |
Conserva le classi specificate e i membri specificati (campi e metodi), impedendone l'ottimizzazione. Nota: keep in genere deve essere utilizzato solo con i modificatori dell'opzione di conservazione perché keep da solo impedisce qualsiasi tipo di ottimizzazione delle classi corrispondenti. |
keepclasseswithmembers |
Conserva un corso e i relativi membri specificati solo se il corso ha tutti i membri della specifica del corso. |
keepclassmembernames |
Impedisce la ridenominazione dei membri del corso specificati, ma non impedisce la rimozione del corso o dei suoi membri. Nota:il significato di questa opzione viene spesso frainteso. Ti consigliamo di utilizzare l'opzione equivalente -keepclassmembers,allowshrinking . |
keepnames |
Impedisce la ridenominazione dei corsi e dei relativi membri, ma non la loro rimozione completa se vengono considerati inutilizzati. Nota:il significato di questa opzione viene spesso frainteso. Ti consigliamo di utilizzare l'opzione equivalente -keep,allowshrinking . |
keepclasseswithmembernames |
Impedisce la ridenominazione delle classi e dei relativi membri specificati, ma solo se i membri esistono nel codice finale. Non impedisce la rimozione del codice. Nota:il significato di questa opzione viene spesso frainteso. Ti consigliamo di utilizzare l'opzione equivalente -keepclasseswithmembers,allowshrinking . |
Scegliere l'opzione di conservazione giusta
La scelta dell'opzione di conservazione corretta è fondamentale per determinare l'ottimizzazione giusta per la tua app. Alcune opzioni di conservazione riducono il codice, un processo mediante il quale viene rimosso il codice non referenziato, mentre altre offuscano o ridenominano il codice. La tabella seguente indica le azioni delle varie opzioni di conservazione:
Opzione Keep | Riduce le classi | Offusca le classi | Riduce i membri | Offusca i membri |
---|---|---|---|---|
keep |
||||
keepclassmembers |
||||
keepclasseswithmembers |
||||
keepnames |
||||
keepclassmembernames |
||||
keepclasseswithmembernames |
Modificatore dell'opzione Mantieni
Un modificatore dell'opzione di conservazione viene utilizzato per controllare l'ambito e il comportamento di una regola di conservazione. Puoi aggiungere zero o più modificatori dell'opzione di conservazione alla regola di conservazione.
I possibili valori per un modificatore dell'opzione di conservazione sono descritti nella tabella seguente:
Valore | Descrizione |
---|---|
allowoptimization |
Consente l'ottimizzazione degli elementi specificati. Tuttavia, gli elementi specificati non vengono rinominati o rimossi. |
allowobfucastion |
Consente di rinominare gli elementi specificati. Tuttavia, gli elementi non vengono rimossi o ottimizzati in altro modo. |
allowshrinking |
Consente la rimozione degli elementi specificati se R8 non trova riferimenti. Tuttavia, gli elementi non vengono rinominati o ottimizzati in altro modo. |
includedescriptorclasses |
Indica a R8 di conservare tutte le classi che compaiono nei descrittori dei metodi (tipi di parametri e tipi restituiti) e dei campi (tipi di campi) che vengono conservati. |
allowaccessmodification |
Consente a R8 di modificare (in genere ampliare) i modificatori di accesso (public , private , protected ) di classi, metodi e campi durante il processo di ottimizzazione. |
allowrepackage |
Consente a R8 di spostare le classi in pacchetti diversi, incluso il pacchetto predefinito (root). |
Specifica della classe
Devi specificare una classe, una superclasse o un'interfaccia implementata come parte di una regola keep. Tutte le classi, incluse quelle dello spazio dei nomi java.lang
come
java.lang.String
, devono essere specificate utilizzando il nome Java completo. Per
comprendere i nomi da utilizzare, ispeziona il bytecode utilizzando gli strumenti
descritti in Recuperare i nomi Java generati.
L'esempio seguente mostra come specificare la classe MaterialButton
:
- Corretto:
com.google.android.material.button.MaterialButton
- Errato:
MaterialButton
Le specifiche della classe specificano anche i membri all'interno di una classe che
devono essere mantenuti. La seguente regola mantiene la classe MaterialButton
e tutti i relativi
membri:
-keep class com.google.android.material.button.MaterialButton { *; }
Sottoclassi e implementazioni
Per scegliere come target una sottoclasse o una classe che implementa un'interfaccia, utilizza rispettivamente extend
e
implements
.
Ad esempio, se hai la classe Bar
con la sottoclasse Foo
come segue:
class Foo : Bar()
La seguente regola di conservazione conserva tutte le sottoclassi di Bar
. Tieni presente che la regola di conservazione non include la superclasse Bar
stessa.
-keep class * extends Bar
Se hai la classe Foo
che implementa Bar
:
class Foo : Bar
La seguente regola di conservazione conserva tutte le classi che implementano Bar
. Tieni presente che
la regola di conservazione non include l'interfaccia Bar
stessa.
-keep class * implements Bar
Modificatore di accesso
Puoi specificare modificatori di accesso come public
, private
, static
e
final
per rendere più precise le regole di conservazione.
Ad esempio, la seguente regola mantiene tutte le classi public
all'interno del pacchetto api
e dei relativi pacchetti secondari, nonché tutti i membri pubblici e protetti di queste classi.
-keep public class com.example.api.** { public protected *; }
Puoi anche utilizzare modificatori per i membri all'interno di una classe. Ad esempio, la seguente regola mantiene solo i metodi public static
di una classe Utils
:
-keep class com.example.Utils {
public static void *(...);
}
Modificatori specifici di Kotlin
R8 non supporta modificatori specifici di Kotlin come internal
e suspend
.
Segui le seguenti linee guida per conservare questi campi.
Per mantenere una classe, un metodo o un campo
internal
, consideralo pubblico. Ad esempio, considera il seguente codice sorgente Kotlin:package com.example internal class ImportantInternalClass { internal f: Int internal fun m() {} }
Le classi, i metodi e i campi
internal
sonopublic
nei file.class
prodotti dal compilatore Kotlin, quindi devi utilizzare la parola chiavepublic
come mostrato nell'esempio seguente:-keepclassmembers public class com.example.ImportantInternalClass { public int f; public void m(); }
Quando viene compilato un membro
suspend
, la relativa firma compilata viene abbinata alla regola di conservazione.Ad esempio, se hai definito la funzione
fetchUser
come mostrato nello snippet seguente:suspend fun fetchUser(id: String): User
Una volta compilata, la sua firma nel bytecode ha il seguente aspetto:
public final Object fetchUser(String id, Continuation<? super User> continuation);
Per scrivere una regola di conservazione per questa funzione, devi corrispondere a questa firma compilata o utilizzare
...
.Di seguito è riportato un esempio di utilizzo della firma compilata:
-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(java.lang.String, kotlin.coroutines.Continuation); }
Ecco un esempio di utilizzo di
...
:-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(...); }
Specifica del membro
La specifica della classe include facoltativamente i membri della classe da conservare. Se specifichi uno o più membri per un corso, la regola si applica solo a questi membri.
Ad esempio, per conservare una classe specifica e tutti i suoi membri, utilizza il seguente comando:
-keep class com.myapp.MyClass { *; }
Per conservare solo il corso e non i suoi membri, utilizza quanto segue:
-keep class com.myapp.MyClass
La maggior parte delle volte, vorrai specificare alcuni membri. Ad esempio, il seguente esempio mantiene il campo pubblico text
e il metodo pubblico updateText()
all'interno della classe MyClass
.
-keep class com.myapp.MyClass {
public java.lang.String text;
public void updateText(java.lang.String);
}
Per conservare tutti i campi pubblici e i metodi pubblici, vedi l'esempio seguente:
-keep public class com.example.api.ApiClient {
public *;
}
Metodi
La sintassi per specificare un metodo nella specifica del membro per una regola di conservazione è la seguente:
[<access_modifier>] [<return_type>] <method_name>(<parameter_types>);
Ad esempio, la seguente regola di conservazione mantiene un metodo pubblico chiamato setLabel()
che restituisce void e accetta un String
.
-keep class com.example.MyView {
public void setLabel(java.lang.String);
}
Puoi utilizzare <methods>
come scorciatoia per abbinare tutti i metodi in una classe come segue:
-keep class com.example.MyView {
<methods>;
}
Per scoprire di più su come specificare i tipi per i tipi restituiti e i tipi di parametri, vedi Tipi.
Costruttori
Per specificare un costruttore, utilizza <init>
. La sintassi per specificare un costruttore
nella specifica del membro per una regola di conservazione è la seguente:
[<access_modifier>] <init>(parameter_types);
Ad esempio, la seguente regola di conservazione mantiene un costruttore View
personalizzato che
accetta un Context
e un AttributeSet
.
-keep class com.example.ui.MyCustomView {
public <init>(android.content.Context, android.util.AttributeSet);
}
Per conservare tutti i costruttori pubblici, utilizza il seguente esempio come riferimento:
-keep class com.example.ui.MyCustomView {
public <init>(...);
}
Campi
La sintassi per specificare un campo nella specifica del membro per una regola di conservazione è la seguente:
[<access_modifier>...] [<type>] <field_name>;
Ad esempio, la seguente regola di conservazione mantiene un campo stringa privato denominato userId
e un campo intero statico pubblico denominato STATUS_ACTIVE
:
-keep class com.example.models.User {
private java.lang.String userId;
public static int STATUS_ACTIVE;
}
Puoi utilizzare <fields>
come scorciatoia per abbinare tutti i campi di una classe come segue:
-keep class com.example.models.User {
<fields>;
}
Funzioni a livello di pacchetto
Per fare riferimento a una funzione Kotlin definita al di fuori di una classe (comunemente
chiamate funzioni di primo livello), assicurati di utilizzare il nome Java generato per
la classe aggiunta implicitamente dal compilatore Kotlin. Il nome della classe è il nome file Kotlin
con Kt
aggiunto. Ad esempio, se hai un file Kotlin denominato
MyClass.kt
definito come segue:
package com.example.myapp.utils
// A top-level function not inside a class
fun isEmailValid(email: String): Boolean {
return email.contains("@")
}
Per scrivere una regola di conservazione per la funzione isEmailValid
, la specifica della classe
deve avere come target la classe generata MyClassKt
:
-keep class com.example.myapp.utils.MyClassKt {
public static boolean isEmailValid(java.lang.String);
}
Tipi
Questa sezione descrive come specificare i tipi restituiti, i tipi di parametri e i tipi di campi nelle specifiche dei membri delle regole di conservazione. Ricorda di utilizzare i nomi Java generati per specificare i tipi se sono diversi dal codice sorgente Kotlin.
Tipi primitivi
Per specificare un tipo primitivo, utilizza la parola chiave Java corrispondente. R8 riconosce i seguenti
tipi primitivi: boolean
, byte
, short
, char
, int
, long
, float
,
double
.
Di seguito è riportata una regola di esempio 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);
}
Tipi generici
Durante la compilazione, il compilatore Kotlin/Java cancella le informazioni sul tipo generico, quindi quando scrivi regole di conservazione che coinvolgono tipi generici, devi scegliere come target la rappresentazione compilata del codice e non il codice sorgente originale. Per scoprire di più su come vengono modificati i tipi generici, consulta Cancellazione del tipo.
Ad esempio, se hai il seguente codice con un tipo generico senza limiti
definito in Box.kt
:
package com.myapp.data
class Box<T>(val item: T) {
fun getItem(): T {
return item
}
}
Dopo l'eliminazione del tipo, T
viene sostituito da Object
. Per mantenere il costruttore e il metodo della classe, la regola deve utilizzare java.lang.Object
al posto di T
generico.
Una regola di conservazione di esempio potrebbe essere la seguente:
# 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();
}
Se hai il seguente codice con un tipo generico limitato in NumberBox.kt
:
package com.myapp.data
// T is constrained to be a subtype of Number
class NumberBox<T : Number>(val number: T)
In questo caso, la cancellazione del tipo sostituisce T
con il relativo limite, java.lang.Number
.
Una regola di conservazione di esempio potrebbe essere la seguente:
-keep class com.myapp.data.NumberBox {
public init(java.lang.Number);
}
Quando utilizzi tipi generici specifici dell'app come classe base, è necessario includere anche le regole di conservazione per le classi base.
Ad esempio, per il seguente codice:
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) {
}
Puoi utilizzare una regola di conservazione con includedescriptorclasses
per conservare sia la classe UnpackOptions
sia il metodo della classe Box
con una sola regola, come segue:
-keep,includedescriptorclasses class com.myapp.data.Box {
public <init>(com.myapp.data.UnpackOptions);
}
Per conservare una funzione specifica che elabora un elenco di oggetti, devi scrivere
una regola che corrisponda esattamente alla firma della funzione. Tieni presente che poiché i tipi generici
vengono cancellati, un parametro come List<Product>
viene visualizzato come
java.util.List
.
Ad esempio, se hai una classe di utilità con una funzione che elabora un elenco
di oggetti Product
nel seguente modo:
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)
Puoi utilizzare la seguente regola di conservazione per proteggere solo la funzione processProducts
:
-keep class com.myapp.utils.DataProcessor {
public void processProducts(java.util.List);
}
Tipi di array
Specifica un tipo di array aggiungendo []
al tipo di componente per ogni dimensione
dell'array. Questo vale sia per i tipi di classe sia per i tipi primitivi.
- Array di classi unidimensionale:
java.lang.String[]
- Array primitivo bidimensionale:
int[][]
Ad esempio, se hai il seguente codice:
package com.example.data
class ImageProcessor {
fun process(): ByteArray {
// process image to return a byte array
}
}
Potresti utilizzare la seguente regola di conservazione:
# Keeps a method that returns a byte array.
-keepclassmembers class com.example.data.ImageProcessor {
public byte[] process();
}
Caratteri jolly
La seguente tabella mostra come utilizzare i caratteri jolly per applicare le regole di conservazione a più classi o membri che corrispondono a un determinato pattern.
Jolly | Si applica a corsi o membri | Descrizione |
---|---|---|
** | Entrambi | Utilizzato più di frequente. Corrisponde a qualsiasi nome di tipo, incluso qualsiasi numero di separatori di pacchetti. Questa opzione è utile per trovare corrispondenze per tutte le classi all'interno di un pacchetto e dei relativi pacchetti secondari. |
* | Entrambi | Per le specifiche della classe, corrisponde a qualsiasi parte di un nome di tipo che non contiene separatori di pacchetti (. ) Per le specifiche dei membri, corrisponde a qualsiasi nome di metodo o campo. Se utilizzato da solo, è anche un alias di ** . |
? | Entrambi | Corrisponde a qualsiasi singolo carattere nel nome di una classe o di un membro. |
*** | Abbonati | Corrisponde a qualsiasi tipo, inclusi i tipi primitivi (come int ), i tipi di classe (come java.lang.String ) e i tipi di array di qualsiasi dimensione (come byte[][] ). |
… | Abbonati | Corrisponde a qualsiasi elenco di parametri per un metodo. |
% | Abbonati | Corrisponde a qualsiasi tipo primitivo (ad esempio `int`, `float`, `boolean` o altri). |
Ecco alcuni esempi di come utilizzare i caratteri jolly speciali:
Se hai più metodi con lo stesso nome che accettano diversi tipi primitivi come input, puoi utilizzare
%
per scrivere una regola di conservazione che li conservi tutti. Ad esempio, questa classeDataStore
ha più metodisetValue
:class DataStore { fun setValue(key: String, value: Int) { ... } fun setValue(key: String, value: Boolean) { ... } fun setValue(key: String, value: Float) { ... } }
La seguente regola di conservazione mantiene tutti i metodi:
-keep class com.example.DataStore { public void setValue(java.lang.String, %); }
Se hai più corsi con nomi che variano di un carattere, utilizza
?
per scrivere una regola di conservazione che li conservi tutti. Ad esempio, se hai le seguenti classi:com.example.models.UserV1 {...} com.example.models.UserV2 {...} com.example.models.UserV3 {...}
La seguente regola di conservazione mantiene tutte le classi:
-keep class com.example.models.UserV?
Per corrispondere alle classi
Example
eAnotherExample
(se fossero classi di livello principale), ma non acom.foo.Example
, utilizza la seguente regola di conservazione:-keep class *Example
Se utilizzi * da solo, funge da alias per **. Ad esempio, le seguenti regole di conservazione sono equivalenti:
-keepclasseswithmembers class * { public static void main(java.lang.String[];) } -keepclasseswithmembers class ** { public static void main(java.lang.String[];) }
Ispeziona i nomi Java generati
Quando scrivi regole di conservazione, devi specificare classi e altri tipi di riferimento utilizzando i loro nomi dopo la compilazione in bytecode Java (vedi Specifica della classe e Tipi per esempi). Per controllare quali sono i nomi Java generati per il tuo codice, utilizza uno dei seguenti strumenti in Android Studio:
- Strumento di analisi APK
- Con il file sorgente Kotlin aperto, esamina il bytecode andando su Strumenti > Kotlin > Mostra bytecode Kotlin > Decompila.