Per rendere la tua app il più piccola e veloce possibile, devi ottimizzare e ridurre al minimo la compilazione della release con isMinifyEnabled = true
.
In questo modo vengono attivate la riduzione, che rimuove il codice inutilizzato, l'offuscamento, che abbrevia i nomi delle classi e dei membri dell'app, e l'ottimizzazione, che applica strategie di ottimizzazione del codice migliorate per ridurre ulteriormente le dimensioni e migliorare le prestazioni dell'app. Questa pagina descrive in che modo R8 esegue queste attività di compilazione per il progetto e come puoi personalizzarle.
Quando crei il progetto utilizzando Android Gradle Plugin 3.4.0 o versioni successive, il plug-in non utilizza più ProGuard per eseguire l'ottimizzazione del codice in fase di compilazione. Il plug-in funziona invece con il compilatore R8 per gestire le seguenti attività di compilazione:
- Riduzione del codice (o tree-shaking): rileva e rimuove in modo sicuro classi, campi, metodi e attributi inutilizzati dall'app e dalle dipendenze della libreria (il che lo rende uno strumento prezioso per aggirare il limite di 64.000 riferimenti). Ad esempio, se utilizzi solo alcune API di una dipendenza della libreria, la riduzione può identificare il codice della libreria che la tua app non utilizza e rimuoverlo solo da quest'ultima. Per scoprire di più, vai alla sezione su come ridurre il codice.
- Riduzione delle risorse:rimuove le risorse inutilizzate dall'app pacchettizzata, incluse quelle nelle dipendenze delle librerie dell'app. Funziona insieme alla riduzione del codice, in modo che una volta rimosso il codice inutilizzato, sia possibile rimuovere in sicurezza anche tutte le risorse a cui non si fa più riferimento. Per saperne di più, vai alla sezione su come ridurre le risorse.
- Ottimizzazione:ispeziona e riscrivi il codice per migliorare le prestazioni in fase di runtime e ridurre ulteriormente le dimensioni dei file DEX dell'app. Questo migliora le prestazioni di runtime del codice fino al 30%, migliorando drasticamente l'avvio e la tempistica dei frame. Ad esempio, se R8 rileva che il ramo
else {}
per una determinata istruzione if/else non viene mai eseguito, rimuove il codice per il brancoelse {}
. Per saperne di più, vai alla sezione sull'ottimizzazione del codice. - Offuscamento (o minimizzazione degli identificatori): abbrevia il nome delle classi e dei membri, con conseguente riduzione delle dimensioni dei file DEX. Per saperne di più, consulta la sezione su come offuscare il codice.
Quando crei la versione di release dell'app, R8 può essere configurato in modo da eseguire le attività in fase di compilazione descritte sopra. Puoi anche disattivare determinate attività o personalizzare il comportamento di R8 tramite i file delle regole di ProGuard. Infatti, R8 funziona con tutti i file di regole di ProGuard esistenti, quindi l'aggiornamento del plug-in Android Gradle in modo che utilizzi R8 non dovrebbe richiedere la modifica delle regole esistenti.
Attivare la riduzione, l'offuscamento e l'ottimizzazione
Quando utilizzi Android Studio 3.4 o il plug-in Android Gradle 3.4.0 e versioni successive, R8 è il compilatore predefinito che converte il bytecode Java del progetto nel formato DEX che viene eseguito sulla piattaforma Android. Tuttavia, quando crei un nuovo progetto con Android Studio, la riduzione, l'oscuramento e l'ottimizzazione del codice non sono attivati per impostazione predefinita. Questo perché queste ottimizzazioni in fase di compilazione aumentano il tempo di compilazione del progetto e potrebbero introdurre bug se non personalizzi sufficientemente il codice da conservare.
Pertanto, è meglio attivare queste attività di compilazione quando crei la versione finale della tua app che testerai prima della pubblicazione. Per attivare la riduzione, l'oscuramento e l'ottimizzazione, includi quanto segue nello script di compilazione a livello di progetto.
Kotlin
android { buildTypes { getByName("release") { // Enables code shrinking, obfuscation, and optimization for only // your project's release build type. Make sure to use a build // variant with `isDebuggable=false`. isMinifyEnabled = true // Enables resource shrinking, which is performed by the // Android Gradle plugin. isShrinkResources = true proguardFiles( // Includes the default ProGuard rules files that are packaged with // the Android Gradle plugin. To learn more, go to the section about // R8 configuration files. getDefaultProguardFile("proguard-android-optimize.txt"), // Includes a local, custom Proguard rules file "proguard-rules.pro" ) } } ... }
Groovy
android { buildTypes { release { // Enables code shrinking, obfuscation, and optimization for only // your project's release build type. Make sure to use a build // variant with `debuggable false`. minifyEnabled true // Enables resource shrinking, which is performed by the // Android Gradle plugin. shrinkResources true // Includes the default ProGuard rules files that are packaged with // the Android Gradle plugin. To learn more, go to the section about // R8 configuration files. proguardFiles getDefaultProguardFile( 'proguard-android-optimize.txt'), 'proguard-rules.pro' } } ... }
File di configurazione R8
R8 utilizza i file delle regole di ProGuard per modificare il comportamento predefinito e comprendere meglio la struttura dell'app, ad esempio le classi che fungono da punti di contatto nel codice dell'app. Sebbene tu possa modificare alcuni di questi file di regole, alcune regole potrebbero essere generate automaticamente dagli strumenti di compilazione, come AAPT2, o ereditate dalle dipendenze della libreria dell'app. La tabella seguente descrive le origini dei file delle regole di ProGuard utilizzati da R8.
Fonte | Posizione | Descrizione |
Android Studio | <module-dir>/proguard-rules.pro
|
Quando crei un nuovo modulo utilizzando Android Studio, l'IDE crea un
proguard-rules.pro file nella directory principale del modulo.
Per impostazione predefinita, questo file non applica alcuna regola. Pertanto, includi qui le tue regole ProGuard, come le regole di conservazione personalizzate. |
Plug-in Android per Gradle | Generato dal plug-in Android per Gradle in fase di compilazione. | Il plug-in Android per Gradle genera
proguard-android-optimize.txt , che include regole utili per la maggior parte dei progetti Android e attiva
@Keep*
annotazioni.
Per impostazione predefinita, quando crei un nuovo modulo utilizzando Android Studio, lo script di compilazione a livello di modulo include automaticamente questo file di regole nella build di release.
Nota:il plug-in Android Gradle include altri file di regole ProGuard predefiniti, ma è consigliabile utilizzare |
Dipendenze libreria |
In una raccolta AAR:
In una libreria JAR: Oltre a queste posizioni, il plug-in Gradle per Android 3.6 o versioni successive supporta anche le regole di riduzione mirate. |
Se una libreria AAR o JAR viene pubblicata con il proprio file di regole e includi questa libreria come dipendenza in fase di compilazione, R8 applica automaticamente queste regole al momento della compilazione del progetto. Oltre alle regole ProGuard convenzionali, il plug-in Android Gradle 3.6 o versioni successive supporta anche regole di riduzione mirate. Queste sono regole che hanno come target specifici shrinker (R8 o ProGuard) e versioni specifiche di shrinker. L'utilizzo di file di regole inclusi nelle librerie è utile se per il corretto funzionamento della libreria sono necessarie determinate regole; in altre parole, lo sviluppatore della libreria ha eseguito la procedura di risoluzione dei problemi per te. Tuttavia, tieni presente che, poiché le regole sono additive, alcune regole incluse in una dipendenza della libreria non possono essere rimosse e potrebbero influire sulla compilazione di altre parti dell'app. Ad esempio, se una libreria include una regola per disattivare le ottimizzazioni del codice, questa regola disattiva le ottimizzazioni per l'intero progetto. |
Android Asset Package Tool 2 (AAPT2) | Dopo aver creato il progetto con minifyEnabled true :
<module-dir>/build/intermediates/aapt_proguard_file/.../aapt_rules.txt
|
AAPT2 genera regole di mantenimento in base ai riferimenti alle classi nel manifest, nei layout e in altre risorse dell'app. Ad esempio, AAPT2 include una regola di mantenimento per ogni attività registrata nel file manifest dell'app come punto di contatto. |
File di configurazione personalizzati | Per impostazione predefinita, quando crei un nuovo modulo utilizzando Android Studio, l'IDE crea <module-dir>/proguard-rules.pro per consentirti di aggiungere le tue regole.
|
Puoi includere configurazioni aggiuntive, che R8 applica in fase di compilazione. |
Quando imposti la proprietà minifyEnabled
su true
, R8 combina le regole di tutte le fonti disponibili elencate sopra. È importante ricordare questo aspetto quando risolvi i problemi relativi a R8, perché altre dipendenze di compilazione, come le dipendenze delle librerie, potrebbero introdurre modifiche al comportamento di R8 di cui non sei a conoscenza.
Per generare un report completo di tutte le regole applicate da R8 durante la compilazione del progetto, includi quanto segue nel file proguard-rules.pro
del modulo:
// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt
Regole di riduzione target
Il plug-in Android Gradle 3.6 o versioni successive supporta le regole delle librerie che hanno come target rieseguitori specifici (R8 o ProGuard), nonché versioni specifiche di rieseguitori. In questo modo, gli sviluppatori di librerie possono personalizzare le proprie regole in modo che funzionino in modo ottimale nei progetti che utilizzano le nuove versioni degli strumenti di compressione, consentendo al contempo di continuare a utilizzare le regole esistenti nei progetti con versioni precedenti degli strumenti di compressione.
Per specificare le regole di riduzione mirate, gli sviluppatori delle librerie dovranno includerle in posizioni specifiche all'interno di una libreria AAR o JAR, come descritto di seguito.
In an AAR library:
proguard.txt (legacy location)
classes.jar
└── META-INF
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
In a JAR library:
META-INF
├── proguard/<ProGuard-rules-file> (legacy location)
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
Ciò significa che le regole di riduzione mirate vengono archiviate nella directory META-INF/com.android.tools
di un file JAR o nella directory META-INF/com.android.tools
all'interno di classes.jar
di un file AAR.
In questa directory possono essere presenti più directory con nomi nel formato r8-from-<X>-upto-<Y>
o proguard-from-<X>-upto-<Y>
per indicare per quali versioni di quale compressore sono scritte le regole all'interno delle directory.
Tieni presente che le parti -from-<X>
e -upto-<Y>
sono facoltative, la versione <Y>
è esclusiva e gli intervalli di versione devono essere continui.
Ad esempio, r8-upto-8.0.0
, r8-from-8.0.0-upto-8.2.0
e r8-from-8.2.0
formano un insieme valido di regole di riduzione scelte come target. Le regole nella directoryr8-from-8.0.0-upto-8.2.0
verranno utilizzate da R8 dalla versione 8.0.0 fino alla versione 8.2.0, esclusa.
In base a queste informazioni, il plug-in Android Gradle 3.6 o versioni successive selezionerà le regole dalle directory R8 corrispondenti. Se una libreria non specifica le regole di compressione mirate, il plug-in Gradle per Android seleziona le regole dalle posizioni precedenti (proguard.txt
per un file AAR oMETA-INF/proguard/<ProGuard-rules-file>
per un file JAR).
Gli sviluppatori di librerie possono scegliere di includere nelle loro librerie regole di riduzione mirate o regole ProGuard precedenti oppure entrambi i tipi se vogliono mantenere la compatibilità con il plug-in Gradle per Android precedente alla versione 3.6 o con altri strumenti.
Includi configurazioni aggiuntive
Quando crei un nuovo progetto o modulo utilizzando Android Studio, l'IDE crea un
<module-dir>/proguard-rules.pro
file in cui includere le tue regole. Puoi anche includere regole aggiuntive da altri file aggiungendole alla proprietà proguardFiles
nello script di compilazione del modulo.
Ad esempio, puoi aggiungere regole specifiche per ogni variante di compilazione aggiungendo un'altra proprietà proguardFiles
nel blocco productFlavor
corrispondente. Il
seguente file Gradle aggiunge flavor2-rules.pro
al flavor del prodotto flavor2
.
Ora flavor2
utilizza tutte e tre le regole di ProGuard perché vengono applicate anche quelle del blocco release
.
Inoltre, puoi aggiungere la proprietà testProguardFiles
, che specifica un elenco di file ProGuard inclusi solo nell'APK di test:
Kotlin
android { ... buildTypes { getByName("release") { isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), // List additional ProGuard rules for the given build type here. By default, // Android Studio creates and includes an empty rules file for you (located // at the root directory of each module). "proguard-rules.pro" ) testProguardFiles( // The proguard files listed here are included in the // test APK only. "test-proguard-rules.pro" ) } } flavorDimensions.add("version") productFlavors { create("flavor1") { ... } create("flavor2") { proguardFile("flavor2-rules.pro") } } }
Groovy
android { ... buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), // List additional ProGuard rules for the given build type here. By default, // Android Studio creates and includes an empty rules file for you (located // at the root directory of each module). 'proguard-rules.pro' testProguardFiles // The proguard files listed here are included in the // test APK only. 'test-proguard-rules.pro' } } flavorDimensions "version" productFlavors { flavor1 { ... } flavor2 { proguardFile 'flavor2-rules.pro' } } }
Riduci il codice
Il codice ridotto con R8 è attivato per impostazione predefinita quando imposti la proprietà minifyEnabled
su true
.
La riduzione del codice (detta anche tree shaking) è il processo di rimozione del codice che R8 determina non essere necessario in fase di esecuzione. Questo processo può ridurre notevolmente le dimensioni della tua app se, ad esempio, l'app include molte dipendenze di libreria, ma utilizza solo una piccola parte delle sue funzionalità.
Per ridurre il codice della tua app, R8 determina innanzitutto tutti i punti di ingresso nel codice dell'app in base all'insieme combinato di file di configurazione. Questi punti di accesso includono tutti i corsi che la piattaforma Android potrebbe utilizzare per aprire le attività o i servizi della tua app. A partire da ogni punto di ingresso, R8 ispeziona il codice dell'app per creare un grafico di tutti i metodi, delle variabili dei membri e di altre classi a cui l'app potrebbe accedere in fase di runtime. Il codice non collegato al grafico è considerato non raggiungibile e potrebbe essere rimosso dall'app.
La figura 1 mostra un'app con una dipendenza dalla libreria di runtime. Durante l'ispezione del codice dell'app, R8 determina che i metodi foo()
, faz()
e bar()
sono accessibili dal punto di contatto MainActivity.class
. Tuttavia, la classeOkayApi.class
o il relativo metodo baz()
non viene mai utilizzata dall'app in fase di esecuzione e R8 rimuove questo codice durante il ridimensionamento dell'app.
R8 determina i punti di contatto tramite le regole -keep
nei file di configurazione R8 del progetto. In altre parole, le regole keep specificano le classi che R8 non deve eliminare durante il ridimensionamento dell'app e R8 le considera come possibili punti di contatto dell'app. Il plug-in Gradle per Android e AAPT2 generano automaticamente le regole keep richieste dalla maggior parte dei progetti di app, ad esempio attività, visualizzazioni e servizi dell'app. Tuttavia, se devi personalizzare questo comportamento predefinito con regole di conservazione aggiuntive, leggi la sezione su come personalizzare il codice da conservare.
Se invece vuoi solo ridurre le dimensioni delle risorse della tua app, vai alla sezione su come condurre le risorse.
Tieni presente che se un progetto della libreria viene ridotto, un'app che dipende da quella libreria include le classi della libreria ridotte. Potrebbe essere necessario modificare le regole di conservazione della libreria se mancano classi nell'APK della libreria. Se stai creando e pubblicando una libreria in formato AAR, i file JAR locali di cui la libreria dipende non vengono ridotti nel file AAR.
Personalizzare il codice da conservare
Per la maggior parte delle situazioni, il file di regole di ProGuard predefinito (proguard-android-optimize.txt
) è sufficiente per consentire a R8 di rimuovere solo il codice inutilizzato. Tuttavia,
in alcune situazioni è difficile per R8 analizzare correttamente il codice e potrebbe rimuovere
il codice di cui la tua app ha effettivamente bisogno. Ecco alcuni esempi di casi in cui il codice potrebbe essere rimosso erroneamente:
- Quando l'app chiama un metodo dall'interfaccia nativa Java (JNI)
- Quando l'app cerca il codice in fase di esecuzione (ad esempio con la riflessione)
Il test dell'app dovrebbe rilevare eventuali errori causati da codice rimosso in modo improprio, ma puoi anche controllare il codice rimosso generando un report sul codice rimosso.
Per correggere gli errori e forzare R8 a mantenere un determinato codice, aggiungi una riga
-keep
nel file delle regole di ProGuard. Ad esempio:
-keep public class MyClass
In alternativa, puoi aggiungere l'annotazione @Keep
al codice che vuoi conservare. L'aggiunta di @Keep
a una classe mantiene invariata l'intera classe.
L'aggiunta a un metodo o a un campo manterrà invariati il metodo/campo (e il relativo nome) nonché il nome della classe. Tieni presente che questa annotazione è disponibile solo se utilizzi la libreria di annotazioni AndroidX e se includi il file delle regole di ProGuard pacchettizzato con il plug-in Android Gradle, come descritto nella sezione su come abilitare il ridimensionamento.
Quando utilizzi l'opzione -keep
, ci sono molte considerazioni da fare; per
ulteriori informazioni sulla personalizzazione del file delle regole, consulta il
manuale di ProGuard.
La sezione Risoluzione dei problemi illustra altri problemi comuni che potresti riscontrare quando il codice viene rimosso.
Stacca le librerie native
Per impostazione predefinita, le librerie di codice native vengono eliminate nelle build di release della tua app. Questa operazione consiste nella rimozione della tabella dei simboli e delle informazioni di debug contenute in eventuali librerie native utilizzate dalla tua app. Rimuovere le librerie di codice native permette di risparmiare notevolmente sulle dimensioni, ma non è possibile diagnosticare arresti anomali su Google Play Console a causa di informazioni mancanti (ad esempio nomi di classi e funzioni).
Assistenza per gli arresti anomali nativi
Google Play Console registra gli arresti anomali nativi in Android vitals. Con pochi passaggi, puoi generare e caricare un file di simboli di debug nativo per la tua app. Questo file attiva le tracce dello stack degli arresti anomali nativi simbolizzati (che includono i nomi di classi e funzioni) in Android Vitals per aiutarti a eseguire il debug dell'app in produzione. Questi passaggi variano a seconda della versione del plug-in Android per Gradle utilizzata nel progetto e dell'output della compilazione del progetto.
Plug-in Android per Gradle versione 4.1 o successive
Se il tuo progetto crea un Android App Bundle, puoi includere automaticamente il
file dei simboli di debug nativo al suo interno. Per includere questo file nelle build di release, aggiungi quanto segue al file build.gradle.kts
dell'app:
android.buildTypes.release.ndk.debugSymbolLevel = { SYMBOL_TABLE | FULL }
Seleziona il livello del simbolo di debug tra i seguenti:
- Utilizza
SYMBOL_TABLE
per ottenere i nomi delle funzioni nelle analisi dello stack simboliche di Play Console. Questo livello supporta le tombstone. - Utilizza
FULL
per ottenere i nomi delle funzioni, i file e i numeri di riga nelle analisi dello stack simboliche di Play Console.
Se il tuo progetto crea un APK, utilizza l'impostazione di build build.gradle.kts
mostrata
in precedenza per generare il file dei simboli di debug nativi separatamente. Carica manualmente il file di simboli di debug nativi in Google Play Console. Durante il processo di compilazione, il plug-in Android per Gradle genera questo file nella seguente posizione del progetto:
app/build/outputs/native-debug-symbols/variant-name/native-debug-symbols.zip
Plug-in Android per Gradle versione 4.0 o precedenti (e altri sistemi di compilazione)
Nell'ambito del processo di compilazione, il plug-in Android per Gradle conserva una copia delle librerie senza dati in una directory di progetto. Questa struttura di directory è simile alla seguente:
app/build/intermediates/cmake/universal/release/obj/
├── armeabi-v7a/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
├── arm64-v8a/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
├── x86/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
└── x86_64/
├── libgameengine.so
├── libothercode.so
└── libvideocodec.so
Comprimi i contenuti di questa directory:
cd app/build/intermediates/cmake/universal/release/obj
zip -r symbols.zip .
Carica manualmente il file
symbols.zip
in Google Play Console.
Riduci le risorse
La riduzione delle risorse funziona solo insieme alla riduzione del codice. Dopo aver rimosso tutto il codice inutilizzato, lo shrinker di risorse può identificare le risorse ancora utilizzate dall'app. Questo è particolarmente vero quando aggiungi librerie di codice che includono risorse: devi rimuovere il codice della libreria inutilizzato in modo che le risorse della libreria non vengano referenziate e, di conseguenza, possano essere rimosse dallo Shrinker delle risorse.
Per abilitare la riduzione delle risorse, imposta la proprietà shrinkResources
su true
nello script di build (insieme a minifyEnabled
per la riduzione del codice). Ad esempio:
Kotlin
android { ... buildTypes { getByName("release") { isShrinkResources = true isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ) } } }
Groovy
android { ... buildTypes { release { shrinkResources true minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
Se non hai già creato l'app utilizzando minifyEnabled
per la riduzione del codice, prova a farlo prima di abilitare shrinkResources
, perché potresti dover modificare il file proguard-rules.pro
per conservare le classi o i metodi che vengono creati o richiamati in modo dinamico prima di iniziare a rimuovere le risorse.
Personalizzare le risorse da conservare
Se ci sono risorse specifiche che vuoi conservare o eliminare, crea un file XML nel progetto con un tag <resources>
e specifica ogni risorsa da conservare nell'attributo tools:keep
e ogni risorsa da eliminare nell'attributo tools:discard
. Entrambi gli attributi accettano un
elenco di nomi di risorse separati da virgole. Puoi utilizzare il carattere asterisco
come carattere jolly.
Ad esempio:
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*" tools:discard="@layout/unused2" />
Salva questo file nelle risorse del progetto, ad esempio in
res/raw/my.package.keep.xml
. La compilazione non include questo file nella
app.
Nota: assicurati di utilizzare un nome univoco per il file keep
. Se
vengono collegate librerie diverse, le regole di Keep entrano in conflitto
altrimenti, causando potenziali problemi con regole ignorate o risorse conservate
non necessarie.
Specificare le risorse da eliminare potrebbe sembrare inutile, quando invece potresti eliminarle, ma può essere utile quando utilizzi le varianti di build. Ad esempio, potresti inserire tutte le risorse nella directory del progetto comune, quindi creare un file my.package.build.variant.keep.xml
diverso per ogni variante di compilazione quando sai che una determinata risorsa sembra essere utilizzata nel codice (e quindi non viene rimossa dallo shrinker), ma sai che in realtà non verrà utilizzata per la variante di compilazione in questione. È anche possibile che gli strumenti di compilazione abbiano identificato erroneamente una risorsa come necessaria, il che è possibile perché il compilatore aggiunge gli ID risorsa in linea e l'analizzatore delle risorse potrebbe non conoscere la differenza tra una risorsa a cui viene fatto effettivamente riferimento e un valore intero nel codice che ha lo stesso valore.
Attiva i controlli rigorosi dei riferimenti
In genere, lo strumento di riduzione delle risorse può determinare con precisione se una risorsa viene utilizzata. Tuttavia, se il codice effettua una chiamata a
Resources.getIdentifier()
(o se una delle tue librerie lo fa, la libreria AppCompat
lo fa), significa che il tuo codice cerca i nomi delle risorse in base a
stringhe generate dinamicamente. In questo modo, lo Shrinker delle risorse si comporta in modo difensivo per impostazione predefinita e contrassegna tutte le risorse con un formato del nome corrispondente come potenzialmente utilizzate e non disponibili per la rimozione.
Ad esempio, il seguente codice fa sì che tutte le risorse con il prefisso img_
vengano contrassegnate come utilizzate.
Kotlin
val name = String.format("img_%1d", angle + 1) val res = resources.getIdentifier(name, "drawable", packageName)
Java
String name = String.format("img_%1d", angle + 1); res = getResources().getIdentifier(name, "drawable", getPackageName());
Lo shrinker delle risorse esamina anche tutte le costanti stringa nel codice e in varie risorse res/raw/
, cercando URL delle risorse in un formato simile a file:///android_res/drawable//ic_plus_anim_016.png
. Se trova stringhe come questa o altre che sembrano utilizzabili per creare URL di questo tipo, non le rimuove.
Questi sono esempi della modalità di riduzione sicura abilitata per impostazione predefinita.
Tuttavia, puoi disattivare questa gestione "meglio prevenire che curare" e specificare
che lo strumento di riduzione delle risorse mantenga solo le risorse di cui è certo che vengono utilizzate. Per farlo, imposta shrinkMode
su strict
nel file keep.xml
, come segue:
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:shrinkMode="strict" />
Se attivi la modalità di riduzione rigorosa e il codice fa riferimento anche a risorse con stringhe generate dinamicamente, come mostrato sopra, devi mantenere manualmente queste risorse utilizzando l'attributo tools:keep
.
Rimuovi le risorse alternative inutilizzate
Lo strumento di riduzione delle risorse Gradle rimuove solo le risorse a cui non fa riferimento il codice dell'app, il che significa che non rimuoverà le
risorse alternative per configurazioni di dispositivi diverse. Se necessario,
puoi utilizzare la proprietà resConfigs
del plug-in Android Gradle per
rimuovere i file di risorse alternative di cui la tua app non ha bisogno.
Ad esempio, se utilizzi una libreria che include risorse linguistiche
(come AppCompat o Google Play Services), la tua app include tutte
le stringhe di lingua tradotte per i messaggi in queste librerie, indipendentemente dal fatto che
il resto dell'app sia tradotto nelle stesse lingue o meno. Se vuoi mantenere solo le lingue supportate ufficialmente dalla tua app, puoi specificarle utilizzando la proprietà resConfig
. Tutte le risorse per le lingue non specificate vengono rimosse.
Lo snippet seguente mostra come limitare le risorse linguistiche solo all'inglese e al francese:
Kotlin
android { defaultConfig { ... resourceConfigurations.addAll(listOf("en", "fr")) } }
Groovy
android { defaultConfig { ... resConfigs "en", "fr" } }
Quando rilasci un'app utilizzando il formato Android App Bundle, per impostazione predefinita vengono scaricate solo le lingue configurate sul dispositivo di un utente al momento dell'installazione dell'app. Allo stesso modo, nel download sono incluse solo le risorse che corrispondono alla densità dello schermo del dispositivo e le librerie native che corrispondono all'ABI del dispositivo. Per ulteriori informazioni, consulta la configurazione del bundle di app Android.
Per le app legacy rilasciate con APK (create prima di agosto 2021), puoi personalizzare le risorse ABI o di densità dello schermo da includere nell'APK creando più APK ciascuno con un target di configurazione del dispositivo diverso.
Unisci le risorse duplicate
Per impostazione predefinita, Gradle unisce anche le risorse con lo stesso nome, ad esempio gli elementi drawable con lo stesso nome che potrebbero trovarsi in cartelle di risorse diverse. Questo comportamento non è controllato dalla proprietà shrinkResources
e non può essere disattivato perché è necessario evitare errori quando più risorse corrispondono al nome cercato dal codice.
L'unione delle risorse avviene solo quando due o più file condividono lo stesso nome, tipo e qualificatore di risorsa. Gradle seleziona il file che ritiene essere la scelta migliore tra i duplicati (in base a un ordine di priorità descritto di seguito) e passa solo quella risorsa ad AAPT per la distribuzione nell'elemento finale.
Gradle cerca risorse duplicate nelle seguenti posizioni:
- Le risorse principali, associate all'insieme di origini principale, generalmente situate in
src/main/res/
. - Le sovrapposizioni delle varianti, dal tipo di build e dai relativi gusti.
- Le dipendenze del progetto di libreria.
Gradle unisce le risorse duplicate nel seguente ordine di priorità a cascata:
Dipendenze → Principale → Gusto di compilazione → Tipo di compilazione
Ad esempio, se una risorsa duplicata viene visualizzata sia nelle risorse principali sia in un flavor di build, Gradle seleziona quella nel flavor di build.
Se nello stesso set di origine vengono visualizzate risorse identiche, Gradle non può unificarle e genera un errore di unione delle risorse. Questo può accadere se definisci più set di origini nella proprietà sourceSet
del file build.gradle.kts
, ad esempio se sia src/main/res/
sia src/main/res2/
contengono risorse identiche.
Offuscare il codice
Lo scopo dell'offuscamento è ridurre le dimensioni dell'app accorciando i nomi di classi, metodi e campi dell'app. Di seguito è riportato un esempio di offuscamento utilizzando R8:
androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
android.content.Context mContext -> a
int mListItemLayout -> O
int mViewSpacingRight -> l
android.widget.Button mButtonNeutral -> w
int mMultiChoiceItemLayout -> M
boolean mShowTitle -> P
int mViewSpacingLeft -> j
int mButtonPanelSideLayout -> K
Anche se l'offuscamento non rimuove il codice dall'app, si possono verificare risparmi significativi per le dimensioni nelle app con file DEX che indicizzano molti metodi, classi e campi. Tuttavia, poiché l'offuscamento rinomina parti diverse del codice, alcune attività, come l'ispezione delle tracce dello stack, richiedono strumenti aggiuntivi. Per comprendere la traccia dello stack dopo l'offuscamento, leggi la sezione su come decodificare un'analisi dello stack offuscata.
Inoltre, se il codice si basa su nomi prevedibili per i metodi e le classi dell'app, ad esempio quando utilizzi la riflessione, devi trattare queste firme come punti di contatto e specificare regole di conservazione per loro, come descritto nella sezione su come personalizzare il codice da conservare. Queste regole di mantenimento indicano a R8 di non solo conservare il codice nel file DEX finale dell'app, ma anche di mantenere la denominazione originale.
Decodificare un'analisi dello stack offuscata
Dopo che R8 ha offuscato il codice, è difficile comprendere una traccia dello stack (se non impossibile) perché i nomi di classi e metodi potrebbero essere stati modificati. Per ottenere la traccia dello stack originale, devi ripercorrere la traccia dello stack.
Ottimizzazione del codice
Per ottimizzare ulteriormente l'app, R8 ispeziona il codice a un livello più approfondito per rimuovere altro codice inutilizzato o, se possibile, riscriverlo per renderlo meno prolisso. Di seguito sono riportati alcuni esempi di queste ottimizzazioni:
- Se il codice non utilizza mai il ramo
else {}
per una determinata istruzione if/else, R8 potrebbe rimuovere il codice per il ramoelse {}
. - Se il codice chiama un metodo solo in alcuni punti, R8 potrebbe rimuoverlo e inserirlo in linea nei pochi siti di chiamata.
- Se R8 determina che una classe ha una sola sottoclasse univoca e non viene creata un'istanza della classe stessa (ad esempio, una classe base astratta utilizzata solo da una classe di implementazione concreta), R8 può combinare le due classi e rimuovere una classe dall'app.
- Per saperne di più, leggi i post del blog sull'ottimizzazione R8 di Jake Wharton.
R8 non consente di disattivare o attivare ottimizzazioni distinte né di modificare il comportamento di un'ottimizzazione. In effetti, R8 ignora le regole di ProGuard che tentano di modificare le ottimizzazioni predefinite, ad esempio -optimizations
e
-optimizationpasses
. Questa limitazione è importante perché, man mano che R8 continua a migliorare, il mantenimento di un comportamento standard per le ottimizzazioni consente al team di Android Studio di risolvere facilmente eventuali problemi che potresti riscontrare.
Tieni presente che l'attivazione dell'ottimizzazione modificherà le tracce dello stack per la tua applicazione. Ad esempio, l'inserimento in linea rimuoverà i frame dello stack. Consulta la sezione sul recupero per scoprire come ottenere le analisi dello stack originali.
Impatto sulle prestazioni di runtime
Se la riduzione, l'oscuramento e l'ottimizzazione sono tutti attivati, R8 migliorerà le prestazioni di runtime del codice (incluse l'avvio e il tempo di frame nel thread dell'interfaccia utente) fino al 30%. La disattivazione di una di queste opzioni limita drasticamente l'insieme di ottimizzazioni utilizzate da R8.
Se R8 è abilitato, devi anche creare profili di avvio per prestazioni di avvio ancora migliori.
Attivare le ottimizzazioni avanzate
R8 include un insieme di ottimizzazioni aggiuntive (chiamate "modalità completa") che fanno sì che si comporti in modo diverso da ProGuard. Queste ottimizzazioni sono attivate per impostazione predefinita dalla versione 8.0.0 del plug-in Android per Gradle.
Puoi disattivare queste ottimizzazioni aggiuntive includendo quanto segue nel file gradle.properties
del progetto:
android.enableR8.fullMode=false
Poiché le ottimizzazioni aggiuntive fanno sì che R8 si comporti in modo diverso da ProGuard, potrebbe essere necessario includere regole ProGuard aggiuntive per evitare problemi di runtime, se utilizzi regole progettate per ProGuard. Ad esempio, supponiamo che il tuo codice faccia riferimento a una classe tramite l'API Java Reflection. Quando non utilizzi la "modalità completa", R8 presume che tu intenda esaminare e manipolare gli oggetti di quella classe in fase di runtime, anche se il codice non lo fa, e conserva automaticamente la classe e il relativo inizializzante statico.
Tuttavia, quando utilizzi la "modalità completa", R8 non fa questa supposizione e, se afferma che il codice non utilizza mai la classe in fase di esecuzione, rimuove la classe dal file DEX finale dell'app. In altre parole, se vuoi conservare la classe e il suo inizializzatore statico, devi includere una regola keep nel file delle regole.
Se riscontri problemi durante l'utilizzo della "modalità completa" di R8, consulta la pagina delle domande frequenti su R8 per una possibile soluzione. Se non riesci a risolvere il problema, segnala un bug.
Ripercorrere le analisi dello stack
Il codice elaborato da R8 viene modificato in vari modi che possono rendere più difficili da comprendere le tracce dello stack perché non corrispondono esattamente al codice sorgente. Questo può accadere per le modifiche ai numeri di riga quando le informazioni di debug non vengono conservate. Ciò può essere dovuto a ottimizzazioni come l'inserimento in linea e la creazione di schemi. Il contributo più importante è l'offuscamento, in cui anche le classi e i metodi cambieranno nome.
Per recuperare la traccia dello stack originale, R8 fornisce lo strumento a riga di comando retrace, incluso nel pacchetto degli strumenti a riga di comando.
Per supportare il ritracciamento delle analisi dello stack dell'applicazione, devi assicurarti che la build conservi informazioni sufficienti per il ritracciamento aggiungendo le seguenti regole al file proguard-rules.pro
del modulo:
-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile
L'attributo LineNumberTable
conserva le informazioni sulle posizioni
nei metodi in modo che queste posizioni vengano stampate nelle tracce dello stack. L'attributo SourceFile
garantisce che tutte le potenziali istanze di runtime stampino effettivamente le informazioni sulla posizione. La direttiva -renamesourcefileattribute
imposta il nome del file di origine nelle tracce di stack su SourceFile
. Il nome effettivo del file di origine originale non è richiesto per il ritracciamento, perché il file di mappatura contiene il file di origine originale.
Ogni volta che viene eseguito, R8 crea un file mapping.txt
, che contiene le informazioni necessarie per mappare le analisi dello stack alle analisi dello stack originali. Android Studio salva il file nella directory<module-name>/build/outputs/mapping/<build-type>/
.
Quando pubblichi la tua app su Google Play, puoi caricare il file mapping.txt
per ogni versione dell'app. Se la pubblichi usando gli Android App Bundle, questo
file viene incluso automaticamente nei contenuti dell'app bundle. Google
Play ripercorre le analisi dello stack in arrivo dai problemi segnalati dagli utenti per
poter esaminare le analisi in Play Console. Per ulteriori informazioni, consulta l'articolo del Centro assistenza su come deobfuscare le tracce dello stack degli arresti anomali.
Risolvere i problemi relativi a R8
Questa sezione descrive alcune strategie per la risoluzione dei problemi relativi all'attivazione di riduzione, offuscamento e ottimizzazione utilizzando R8. Se di seguito non trovi una soluzione al tuo problema, leggi anche la pagina delle domande frequenti su R8 e la guida alla risoluzione dei problemi di ProGuard.
Generare un report sul codice rimosso (o mantenuto)
Per aiutarti a risolvere determinati problemi di R8, può essere utile visualizzare un report di tutto il codice rimosso da R8 dalla tua app. Per ogni modulo per cui vuoi generare questo report, aggiungi -printusage <output-dir>/usage.txt
al file delle regole personalizzate. Quando attivi R8 e compili l'app, R8 genera un
report con il percorso e il nome del file specificati. Il report del codice rimosso è simile al seguente:
androidx.drawerlayout.R$attr
androidx.vectordrawable.R
androidx.appcompat.app.AppCompatDelegateImpl
public void setSupportActionBar(androidx.appcompat.widget.Toolbar)
public boolean hasWindowFeature(int)
public void setHandleNativeActionModesEnabled(boolean)
android.view.ViewGroup getSubDecor()
public void setLocalNightMode(int)
final androidx.appcompat.app.AppCompatDelegateImpl$AutoNightModeManager getAutoNightModeManager()
public final androidx.appcompat.app.ActionBarDrawerToggle$Delegate getDrawerToggleDelegate()
private static final boolean DEBUG
private static final java.lang.String KEY_LOCAL_NIGHT_MODE
static final java.lang.String EXCEPTION_HANDLER_MESSAGE_SUFFIX
...
Se invece vuoi visualizzare un report dei punti di contatto determinati da R8 dalle regole di conservazione del progetto , includi -printseeds <output-dir>/seeds.txt
nel file delle regole personalizzate. Quando attivi R8 e compili l'app, R8 genera un report con il percorso e il nome del file specificati. Il report dei punti di ingresso conservati è simile al seguente:
com.example.myapplication.MainActivity
androidx.appcompat.R$layout: int abc_action_menu_item_layout
androidx.appcompat.R$attr: int activityChooserViewStyle
androidx.appcompat.R$styleable: int MenuItem_android_id
androidx.appcompat.R$styleable: int[] CoordinatorLayout_Layout
androidx.lifecycle.FullLifecycleObserverAdapter
...
Risolvere i problemi relativi al ridimensionamento delle risorse
Quando riduci le risorse, la finestra Build mostra un riepilogo delle risorse che vengono rimosse dall'app. Per visualizzare l'output di testo dettagliato di Gradle, devi prima fare clic su Attiva/disattiva visualizzazione sul lato sinistro della finestra. Ad esempio:
:android:shrinkDebugResources
Removed unused resources: Resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning
Gradle crea anche un file di diagnostica denominato resources.txt
in
<module-name>/build/outputs/mapping/release/
(la stessa
directory dei file di output di ProGuard). Questo file include dettagli come quali risorse fanno riferimento ad altre risorse e quali risorse vengono utilizzate o rimosse.
Ad esempio, per scoprire perché @drawable/ic_plus_anim_016
è ancora presente nella tua app, apri il file resources.txt
e cerca il nome del file. Potresti scoprire che viene fatto riferimento a un'altra risorsa, come segue:
16:25:48.005 [QUIET] [system.out] @drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out] @drawable/ic_plus_anim_016
Ora devi sapere perché @drawable/add_schedule_fab_icon_anim
è raggiungibile e se cerchi verso l'alto vedrai che la risorsa è elencata in "Le risorse raggiungibili principali sono:". Ciò significa che esiste un riferimento a add_schedule_fab_icon_anim
(ovvero il relativo ID R.drawable è stato trovato nel codice raggiungibile).
Se non utilizzi il controllo rigoroso, gli ID risorsa possono essere contrassegnati come raggiungibili se sono presenti costanti di stringa che sembrano poter essere utilizzate per creare i nomi delle risorse per le risorse caricate dinamicamente. In questo caso, se cerchi il nome della risorsa nell'output della compilazione, potresti trovare un messaggio simile al seguente:
10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506
used because it format-string matches string pool constant ic_plus_anim_%1$d.
Se vedi una di queste stringhe e hai la certezza che la stringa non venga utilizzata per caricare dinamicamente la risorsa specificata, puoi utilizzare l'attributo tools:discard
per indicare al sistema di build di rimuoverla, come descritto nella sezione su come personalizzare le risorse da conservare.