Per ridurre le dimensioni della tua app e velocizzare il più possibile, devi ottimizzare e minimizzare
la build della release con isMinifyEnabled = true
.
In questo modo si consente il restringimento, che rimuove il codice e le risorse inutilizzati, l'offuscamento, che abbrevia i nomi delle classi e dei membri dell'app, e l'ottimizzazione, che applica strategie più aggressive per ridurre ulteriormente le dimensioni e migliorare le prestazioni dell'app. Questa pagina descrive in che modo R8 esegue queste attività in fase di compilazione per il tuo progetto e come puoi personalizzarle.
Quando crei il progetto utilizzando il plug-in Android Gradle 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 utilizza invece il compilatore R8 per gestire le seguenti attività in fase di compilazione:
- Riduzione del codice (o Tree-shaking): rileva e rimuove in modo sicuro classi, campi, metodi e attributi inutilizzati dalla tua app e dalle sue dipendenze dalla libreria (il che la rende uno strumento prezioso per aggirare il limite di 64.000 riferimenti). Ad esempio, se usi solo poche API di una dipendenza dalla libreria, la riduzione può identificare il codice libreria che la tua app non utilizza e rimuovere solo quel codice dalla tua app. Per scoprire di più, vai alla sezione su come comprimere il codice.
- Riduzione delle risorse: rimuove le risorse inutilizzate dall'app in pacchetto, incluse quelle inutilizzate nelle dipendenze di libreria 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 condurre le risorse.
- Ottimizzazione: controlla e riscrive il codice per migliorare le prestazioni 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 è mai stato preso, R8 rimuove il codice per il ramoelse {}
. Per scoprire 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ù, vai alla 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 disabilitare determinate attività o personalizzare il comportamento di R8 tramite i file di 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.
Attiva restringimento, offuscamento e 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 eseguito sulla piattaforma Android. Tuttavia, quando crei un nuovo progetto utilizzando Android Studio, le operazioni di riduzione, offuscamento e ottimizzazione del codice non sono abilitate per impostazione predefinita. Il motivo è che 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à in fase di compilazione durante la creazione della versione finale dell'app da testare prima della pubblicazione. Per abilitare la riduzione, l'offuscamento e l'ottimizzazione, includi quanto segue nello script di build 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" ) } } ... }
Alla moda
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 di regole ProGuard per modificare il comportamento predefinito e comprendere meglio la struttura dell'app, ad esempio le classi che fungono da punti di ingresso nel codice dell'app. Anche se puoi modificare alcuni di questi file di regole, alcune potrebbero essere generate automaticamente da strumenti di compilazione, come AAPT2, o ereditate dalle dipendenze di libreria dell'app. La tabella seguente descrive le origini dei file di regole 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 file proguard-rules.pro nella directory principale di quel 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 Gradle al momento della 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*
le annotazioni.
Per impostazione predefinita, quando crei un nuovo modulo utilizzando Android Studio, lo script di build a livello di modulo include questo file delle regole nella build della release.
Nota:il plug-in Android Gradle include altri file di regole ProGuard predefiniti, ma è consigliabile utilizzare |
Dipendenze libreria | Librerie AAR: <library-dir>/proguard.txt
Librerie JAR: |
Se una libreria AAR viene pubblicata con il proprio file di regole ProGuard e includi tale AAR come dipendenza in fase di compilazione, R8 applica automaticamente le proprie regole durante la compilazione del progetto.
L'utilizzo di file di regole pacchettizzati con librerie AAR è utile se per il corretto funzionamento della libreria sono necessarie determinate regole di conservazione; in altre parole, lo sviluppatore della libreria ha eseguito la procedura di risoluzione dei problemi per te. Tuttavia, tieni presente che, poiché le regole ProGuard sono additive, alcune regole incluse in una dipendenza della libreria AAR non possono essere rimosse e potrebbero influire sulla compilazione di altre parti dell'app. Ad esempio, se una libreria include una regola per disabilitare le ottimizzazioni del codice, tale regola disabilita le ottimizzazioni per l'intero progetto. |
Strumento 2 per i pacchetti di asset Android (AAPT2) | Dopo aver creato il progetto con minifyEnabled true :
<module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt
|
AAPT2 genera regole di mantenimento basate sui riferimenti alle classi nel manifest, nei layout e in altre risorse dell'app dell'app. Ad esempio, AAPT2 include una regola di conservazione per ogni attività registrata nel file manifest dell'app come punto di ingresso. |
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 le applica al momento della compilazione. |
Quando imposti la proprietà minifyEnabled
su true
, R8 combina le regole di tutte
le origini disponibili elencate sopra. Questo è importante da ricordare durante la risoluzione dei problemi con R8, perché altre dipendenze del tempo di compilazione, ad esempio le dipendenze della libreria, potrebbero introdurre modifiche al comportamento di R8 a cui non sei a conoscenza.
Per generare un report completo di tutte le regole applicate da R8 durante la creazione 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
Includi configurazioni aggiuntive
Quando crei un nuovo progetto o modulo utilizzando Android Studio, l'IDE crea un
file <module-dir>/proguard-rules.pro
in cui puoi includere le tue regole. Puoi
anche includere regole aggiuntive di altri file aggiungendole alla
proprietà proguardFiles
nello script di build del tuo modulo.
Ad esempio, puoi aggiungere regole specifiche per ogni variante della build aggiungendo
un'altra proprietà proguardFiles
nel blocco productFlavor
corrispondente. Il
file Gradle seguente aggiunge flavor2-rules.pro
alla versione di prodotto flavor2
.
Ora flavor2
utilizza tutte e tre le regole 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 prova:
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") } } }
Alla moda
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
La riduzione del codice con R8 è abilitata per impostazione predefinita quando imposti la proprietà minifyEnabled
su true
.
La riduzione del codice (nota anche come scossa degli alberi) è il processo di rimozione del codice che R8 ritiene non necessario in fase di runtime. 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 dell'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
raggiungibili dal punto di ingresso MainActivity.class
. Tuttavia, la classe OkayApi.class
o il relativo metodo baz()
non viene mai utilizzato dall'app in fase di runtime e R8 rimuove questo codice quando si riduce l'app.
R8 determina i punti di ingresso tramite le regole -keep
nei
file di configurazione R8 del progetto. In altre parole, le regole di mantenimento specificano le classi che R8 non deve ignorare quando si riduce la tua app e R8 considera queste classi come possibili punti di ingresso nella tua app. Il plug-in Android Gradle e AAPT2 generano automaticamente regole di conservazione richieste dalla maggior parte dei progetti dell'app, come le attività, le viste e i servizi della tua app. Tuttavia, se hai bisogno di 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 di libreria viene ridotto, un'app che dipende da tale libreria include classi di 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 da cui dipende la libreria non vengono ridotti nel file AAR.
Personalizza il codice da conservare
Nella maggior parte dei casi, il file di regole di ProGuard predefinito (proguard-android-
optimize.txt
) è sufficiente per consentire a R8 di rimuovere solo il codice inutilizzato. Tuttavia,
alcune situazioni sono difficili da analizzare correttamente per R8 e potrebbero rimuovere
il codice effettivamente necessario alla tua app. Ecco alcuni esempi di casi in cui il codice potrebbe
essere rimosso in modo errato:
- Quando la tua app chiama un metodo dalla Java Native Interface (JNI)
- Quando la tua app cerca codice in fase di runtime (ad esempio con riflessione)
Il test dell'app dovrebbe rilevare eventuali errori causati da codice rimosso in modo non appropriato, ma puoi anche controllare quale codice è stato rimosso generando un report sul codice rimosso.
Per correggere gli errori e forzare R8 a conservare un determinato codice, aggiungi una riga -keep
nel file delle regole di ProGuard. Ecco alcuni esempi:
-keep public class MyClass
In alternativa, puoi aggiungere l'annotazione @Keep
al codice che vuoi conservare. Se aggiungi @Keep
a una classe, l'intera classe rimane invariata.
Se la aggiungi a un metodo o a un campo, il metodo/campo (e il relativo nome) e il nome della classe rimangono invariati. Tieni presente che questa annotazione è disponibile solo se utilizzi
la
libreria di annotazioni AndroidX
e quando includi il file di regole ProGuard pacchettizzato con il plug-in
Android Gradle, come descritto nella sezione su come
attivare la riduzione.
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 tuo codice viene eliminato.
Rimuovi 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 è impossibile diagnosticare arresti anomali su Google Play Console a causa di informazioni mancanti (ad esempio i nomi delle classi e delle funzioni).
Supporto nativo degli arresti anomali
Google Play Console segnala 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 consente analisi dello stack di arresti anomali nativi simbolizzate (che includono nomi di classi e funzioni) in Android vitals per aiutarti a eseguire il debug della tua app in produzione. Questi passaggi variano a seconda della versione del plug-in Android per Gradle utilizzata nel progetto e dell'output della build 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 di simboli di debug nativi 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:
- Usa
SYMBOL_TABLE
per recuperare i nomi delle funzioni nelle analisi dello stack simbolizzate di Play Console. Questo livello supporta le lapidi. - Utilizza
FULL
per recuperare i nomi delle funzioni, i file e i numeri di riga nelle analisi dello stack simbolizzate di Play Console.
Se il progetto crea un APK, usa l'impostazione di build build.gradle.kts
mostrata in precedenza per generare separatamente il file di simboli di debug nativo. Carica manualmente il file di simboli di debug nativi in Google Play Console. Nell'ambito del processo di compilazione, il plug-in Android per Gradle restituisce 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 precedente (e altri sistemi di build)
Nell'ambito del processo di compilazione, il plug-in Android per Gradle conserva una copia delle librerie senza dati in una directory di progetto. La struttura di questa 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 libreria inutilizzato in modo che le risorse della libreria non vengano più referenziate e, di conseguenza, rimovibili dall'shrinker.
Per abilitare la riduzione delle risorse, imposta la proprietà shrinkResources
su true
nello script di build (insieme a minifyEnabled
per la riduzione del codice). Ecco alcuni esempi:
Kotlin
android { ... buildTypes { getByName("release") { isShrinkResources = true isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ) } } }
Alla moda
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.
Personalizza le risorse da conservare
Se ci sono risorse specifiche che vuoi conservare o ignorare, 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.
Ecco alcuni esempi:
<?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 il file nelle risorse del progetto, ad esempio in
res/raw/keep.xml
. La build non pacchettizza questo file
nella tua app.
Specificare quali risorse scartare potrebbe sembrare un po' sciocco quando invece si potrebbe
eliminarle, ma questo può essere utile quando si utilizzano varianti di build. Ad esempio, puoi inserire tutte le risorse nella directory comune del progetto, quindi creare un file keep.xml
diverso per ogni variante della build quando sai che una determinata risorsa sembra essere utilizzata nel codice (e pertanto non rimossa dallo shrinker), ma sai che in realtà non verrà utilizzata per la variante della build specificata. È anche possibile che gli strumenti di build abbiano identificato erroneamente una risorsa in base alle esigenze, il che è possibile perché il compilatore aggiunge gli ID risorsa in linea e quindi l'analizzatore di risorse potrebbe non conoscere la differenza tra una risorsa indicata in modo autentico e un valore intero nel codice che ha lo stesso valore.
Attiva controlli rigidi dei riferimenti
Normalmente, lo shrinker 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 caso, 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 e specificare
che la riduzione delle risorse mantiene solo quelle che è certo siano 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 shrinker di 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 le diverse configurazioni dei dispositivi. Se necessario,
puoi utilizzare la proprietà resConfigs
del plug-in Android Gradle per
rimuovere file di risorse alternativi di cui la tua app non ha bisogno.
Ad esempio, se usi una libreria che include risorse linguistiche (come AppCompat o Google Play Services), la tua app include tutte le stringhe della lingua tradotte per i messaggi in queste librerie, indipendentemente dal fatto che il resto dell'app sia tradotto o meno nelle stesse lingue. Se vuoi conservare 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")) } }
Alla moda
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 di Android App Bundle.
Per le app precedenti che vengono rilasciate con APK (create prima di agosto 2021), puoi personalizzare la densità dello schermo o le risorse ABI da includere nell'APK creando diversi APK che hanno come target una configurazione dispositivo diversa.
Unire le risorse duplicate
Per impostazione predefinita, Gradle unisce anche risorse con nomi identici, ad esempio drawables 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 considera la scelta migliore tra i duplicati (in base a un ordine di priorità descritto di seguito) e passa solo quella risorsa all'AAPT per la distribuzione nell'artefatto finale.
Gradle cerca risorse duplicate nelle seguenti posizioni:
- Le risorse principali, associate al set di origini principale, in genere
si trovano in
src/main/res/
. - Le varianti si sovrappongono in base al tipo e alle versioni di build.
- Le dipendenze del progetto di libreria.
Gradle unisce le risorse duplicate nel seguente ordine di priorità a cascata:
Dipendenze → Principale → Versione build → Tipo di build
Ad esempio, se viene visualizzata una risorsa duplicata sia nelle risorse principali sia in una versione build, Gradle seleziona quella nella versione build.
Se nello stesso set di origini vengono visualizzate risorse identiche, Gradle non può unirle
e genera un errore di unione delle risorse. Ciò può verificarsi se definisci più
set di origini nella proprietà sourceSet
del
file build.gradle.kts
, ad esempio se src/main/res/
e src/main/res2/
contengono risorse identiche.
Offuscare il codice
Lo scopo dell'offuscamento è ridurre le dimensioni dell'app abbreviando i nomi delle classi, dei metodi e dei campi dell'app. Di seguito è riportato un esempio di offuscamento mediante 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 analisi dello stack, richiedono strumenti aggiuntivi. Per comprendere la tua stacktrace dopo l'offuscamento, leggi la sezione su come decodificare un'analisi dello stack offuscata.
Inoltre, se il codice si basa su una denominazione prevedibile per metodi e classi della tua app, ad esempio quando utilizzi la riflessione, devi trattare queste firme come punti di ingresso e specificare le relative regole di conservazione, come descritto nella sezione su come personalizzare il codice da conservare. Queste regole indicano a R8 non solo di mantenere quel codice nel DEX finale dell'app, ma anche di mantenere la sua denominazione originale.
Decodifica un'analisi dello stack offuscata
Dopo che R8 ha offuscato il codice, comprendere un'analisi dello stack è difficile (se non impossibile) perché i nomi delle classi e dei metodi potrebbero essere stati modificati. Per ottenere l'analisi dello stack originale, devi ritracciare l'analisi dello stack.
Ottimizzazione del codice
Per ottimizzare ulteriormente la tua app, R8 ispeziona il codice a un livello più approfondito per rimuovere più codice inutilizzato o, ove possibile, riscrivi il codice per renderlo meno dettagliato. Di seguito sono riportati alcuni esempi di queste ottimizzazioni:
- Se il tuo codice non prende mai il ramo
else {}
per una determinata istruzione if/else, R8 potrebbe rimuovere il codice per il ramoelse {}
. - Se il codice chiama un metodo in pochi punti, R8 potrebbe rimuovere il metodo e incorporarlo in alcuni siti delle chiamate.
- 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 disabilitare o abilitare ottimizzazioni discrete né di modificare il
comportamento di un'ottimizzazione. Infatti, R8 ignora qualsiasi regola ProGuard che tenta di modificare le ottimizzazioni predefinite, come -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 individuare e risolvere facilmente eventuali problemi che potrebbero verificarsi.
Tieni presente che l'abilitazione dell'ottimizzazione modificherà le analisi dello stack per la tua applicazione. Ad esempio, l'incorporamento rimuove gli stack frame. Consulta la sezione sul recupero per scoprire come ottenere le analisi dello stack originali.
Impatto sulle prestazioni di runtime
Se le operazioni di riduzione, offuscamento e ottimizzazione sono tutte abilitate, R8 migliorerà le prestazioni in fase di runtime del codice (inclusi avvio e 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.
Consenti ottimizzazioni più aggressive
R8 include una serie di ottimizzazioni aggiuntive (denominate "modalità completa") che la rendono in modo diverso rispetto a ProGuard. Queste ottimizzazioni sono attivate per impostazione predefinita a partire dalla versione 8.0.0 del plug-in Android per Gradle.
Puoi disabilitare 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 presuppone che tu intenda esaminare e manipolare gli oggetti di quella classe in fase di runtime, anche se il tuo codice in realtà non la utilizza, e conserva automaticamente la classe e il relativo inizializzatore statico.
Tuttavia, quando si utilizza la "modalità completa", R8 non fa questa ipotesi e, se R8 asserisce che il tuo codice non utilizza mai la classe in fase di runtime, rimuove la classe dal file DEX finale dell'app. Vale a dire, per mantenere la classe e il suo inizializzatore statico, devi includere una regola Keep nel file delle regole per farlo.
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, il che può rendere più difficile la comprensione delle analisi dello stack, poiché le analisi dello stack non corrispondono esattamente al codice sorgente. Questo può essere il caso di modifiche ai numeri di riga quando non vengono conservate le informazioni di debug. Ciò può essere dovuto a ottimizzazioni come l'incorporamento e la struttura. Il contributo maggiore è l'offuscamento, in cui anche le classi e i metodi cambiano nome.
Per recuperare l'analisi 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 sulla posizione
nei metodi per far sì che queste posizioni vengano stampate nelle analisi dello stack. L'attributo SourceFile
garantisce che tutti i potenziali runtime stampino effettivamente le informazioni sulla posizione.
L'istruzione -renamesourcefileattribute
imposta il nome del file di origine nelle analisi dello 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. Dopodiché Google
Play rileverà le analisi dello stack in arrivo dai problemi segnalati dagli utenti per consentirti
di esaminarle in Play Console. Per ulteriori informazioni, consulta l'articolo del Centro assistenza su come deoffuscare le analisi dello stack in caso di arresto anomalo.
Risolvere i problemi con R8
Questa sezione descrive alcune strategie per la risoluzione dei problemi durante l'abilitazione della riduzione, dell'offuscamento e dell'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 relativo al codice rimosso (o conservato)
Per aiutarti a risolvere determinati problemi R8, può essere utile visualizzare un report di tutto il codice che R8 ha rimosso dalla tua app. Per ogni modulo per cui vuoi generare questo report, aggiungi -printusage <output-dir>/usage.txt
al file delle regole personalizzate. Quando abiliti R8 e crei la tua app, R8 genera un report con il percorso e il nome del file che hai specificato. Il report sul 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 ingresso che R8 determina dalle
regole di conservazione del tuo progetto , includi -printseeds <output-dir>/seeds.txt
nel
file delle regole personalizzate. Quando abiliti R8 e crei la tua 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 alla riduzione delle risorse
Quando riduci le risorse, la finestra Crea mostra un riepilogo delle risorse rimosse dall'app. Devi prima fare clic su Attiva/disattiva visualizzazione sul lato sinistro della finestra per visualizzare un output di testo dettagliato da Gradle. Ecco alcuni esempi:
:android:shrinkDebugResources
Removed unused resources: Binary 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
cartella 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 nella tua app, apri il file resources.txt
e cerca il nome del file. Potresti notare che viene fatto riferimento a questo elemento da 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 è presente un riferimento di codice a add_schedule_fab_icon_anim
(ovvero, il suo 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 stringhe che sembrano 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 build, potresti visualizzare 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.