Riduci, offusca e ottimizza la tua app

Per ridurre il più possibile le dimensioni dell'app, devi attivare la contrazione nella build della release per rimuovere codice e risorse inutilizzati. Quando attivi la riduzione, puoi anche beneficiare dell'offuscamento, che abbrevia i nomi delle classi e dei membri dell'app, e dell'ottimizzazione, che applica strategie più aggressive per ridurre ulteriormente le dimensioni 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 tuo 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. Invece, il plug-in funziona con il compilatore R8 per gestire le seguenti attività in fase di compilazione:

  • La riduzione del codice (o la tremazione ad albero): rileva e rimuove in modo sicuro classi, campi, metodi e attributi inutilizzati dalla tua app e dalle sue dipendenze nelle librerie, il che lo rende uno strumento utile per aggirare il limite di riferimento di 64.000. Ad esempio, se utilizzi solo alcune API di una dipendenza dalla libreria, la riduzione può identificare il codice della libreria che l'app non utilizza e rimuovere solo quel codice dall'app. Per scoprire di più, consulta la sezione su come ridurre il codice.
  • Riduzione delle risorse: rimuove le risorse inutilizzate dall'app in pacchetto, incluse quelle non utilizzate nelle dipendenze della libreria dell'app. Funziona in combinazione con la riduzione del codice in modo che, una volta rimosso il codice inutilizzato, anche le risorse a cui non viene più fatto riferimento possano essere rimosse in sicurezza. Per scoprire di più, consulta la sezione su come ridurre le risorse.
  • Offuscamento: abbrevia il nome delle classi e dei membri, con conseguente riduzione delle dimensioni dei file DEX. Per scoprire di più, consulta la sezione su come offuscare il codice.
  • Ottimizzazione: ispeziona e riscrive il codice per ridurre ulteriormente le dimensioni dei file DEX dell'app. Ad esempio, se R8 rileva che il ramo else {} per una determinata istruzione if/else non viene mai utilizzata, R8 rimuove il codice per il ramo else {}. Per scoprire di più, consulta la sezione relativa all'ottimizzazione del codice.

Durante la creazione della versione di release dell'app, R8 può essere configurato per 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 ProGuard. Infatti, R8 funziona con tutti i file di regole ProGuard esistenti, pertanto l'aggiornamento del plug-in Android Gradle per utilizzare R8 non dovrebbe richiedere la modifica delle regole esistenti.

Attiva riduzione, 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 tuo progetto nel formato DEX eseguito sulla piattaforma Android. Tuttavia, quando crei un nuovo progetto utilizzando Android Studio, la riduzione, l'offuscamento e l'ottimizzazione del codice non sono attivi per impostazione predefinita. Questo perché queste ottimizzazioni in fase di compilazione aumentano i tempi di compilazione del progetto e potrebbero introdurre bug se non personalizzi sufficientemente il codice da conservare.

È consigliabile quindi abilitare queste attività in fase di compilazione durante la creazione della versione finale dell'app da testare prima della pubblicazione. Per attivare 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

            // 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"
            )
        }
    }
    ...
}

trendy

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. Sebbene sia possibile modificare alcuni di questi file delle regole, alcune regole possono essere generate automaticamente da strumenti in fase di compilazione, come AAPT2, oppure 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 root di quel modulo.

Per impostazione predefinita, questo file non applica alcuna regola. Pertanto, includi qui le tue regole ProGuard, ad esempio le regole di Keep personalizzate.

Plug-in Android per Gradle Generato dal plug-in Android per Gradle al momento della compilazione. Il plug-in Android Gradle genera proguard-android-optimize.txt, che include regole utili per la maggior parte dei progetti Android e attiva le annotazioni @Keep*.

Per impostazione predefinita, quando crei un nuovo modulo con 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 ulteriori file di regole ProGuard predefiniti, ma è consigliabile utilizzare proguard-android-optimize.txt.

Dipendenze libreria Librerie AAR: <library-dir>/proguard.txt

Librerie JAR: <library-dir>/META-INF/proguard/

Se una libreria AAR viene pubblicata con il proprio file di regole ProGuard e lo includi come dipendenza in fase di compilazione, R8 applica automaticamente le sue regole durante la compilazione del progetto.

L'utilizzo dei file di regole pacchettizzati con librerie AAR è utile se sono necessarie determinate regole di conservazione per il corretto funzionamento della libreria, ovvero se lo sviluppatore della libreria ha eseguito la procedura di risoluzione dei problemi per te.

Tuttavia, tieni presente che, poiché le regole ProGuard sono aggiuntive, alcune regole incluse da 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, questa disabilita le ottimizzazioni per l'intero progetto.

Strumento 2 del pacchetto di risorse Android (AAPT2) Dopo aver creato il progetto con minifyEnabled true: <module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt AAPT2 genera regole di conservazione basate sui riferimenti alle classi nel manifest, nei layout e in altre risorse 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 usando 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 in fase di 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, poiché altre dipendenze in fase di compilazione, come quelle di libreria, potrebbero introdurre modifiche al comportamento di R8 di cui non sei a conoscenza.

Per generare un report completo di tutte le regole che R8 si applica durante la creazione del tuo 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 con Android Studio, l'IDE crea un file <module-dir>/proguard-rules.pro per includere le tue regole. Puoi anche includere regole aggiuntive di altri file aggiungendole alla proprietà proguardFiles nello script di compilazione del modulo.

Ad esempio, puoi aggiungere regole specifiche per ogni variante di build aggiungendo un'altra proprietà proguardFiles nel blocco productFlavor corrispondente. Il seguente file Gradle aggiunge flavor2-rules.pro alla versione 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 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")
        }
    }
}

trendy

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 shaking dell'albero) è il processo di rimozione del codice che R8 determina non è necessario in fase di runtime. Questo processo può ridurre notevolmente le dimensioni dell'app se, ad esempio, include molte dipendenze di libreria, ma utilizza solo una piccola parte della loro funzionalità.

Per ridurre il codice dell'app, R8 determina innanzitutto tutti i punti di ingresso nel codice dell'app in base all'insieme di file di configurazione combinato. Questi punti di ingresso 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 esamina il codice dell'app per creare un grafico di tutti i metodi, le variabili dei membri e altre classi a cui la tua app potrebbe accedere in fase di runtime. Il codice che 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 suo metodo baz() non vengono mai utilizzati dalla tua app in fase di runtime e R8 rimuove questo codice quando riduce l'app.

Figura 1. In fase di compilazione, R8 crea un grafico basato sulle regole di conservazione combinate del progetto per determinare il codice non raggiungibile.

R8 determina i punti di ingresso tramite le regole -keep nei file di configurazione R8 del progetto. In altre parole, le regole di conservazione specificano le classi che R8 non deve eliminare quando riduci l'app e R8 le considera come possibili punti di ingresso nella tua app. Il plug-in Android Gradle e AAPT2 generano automaticamente le regole di conservazione richieste dalla maggior parte dei progetti di app per te, ad esempio attività, viste 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 ti interessa solo ridurre le dimensioni delle risorse della tua app, vai alla sezione su come ridurre le risorse.

Personalizza il codice da conservare

Nella maggior parte dei casi, il file di regole 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 di cui la tua app ha effettivamente bisogno. Ecco alcuni esempi di casi in cui potrebbe rimuovere erroneamente il codice:

  • Quando l'app chiama un metodo da JNI (Java Native Interface)
  • Quando la tua app cerca il codice in fase di runtime (ad esempio con la riflessione)

Il test dell'app dovrebbe rivelare eventuali errori causati da una rimozione inappropriata del codice, 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. L'aggiunta di @Keep a una classe mantiene l'intera classe così com'è. Se la aggiungi a un metodo o a un campo, il metodo/campo (e il relativo nome) e il nome della classe rimarranno invariati. Tieni presente che questa annotazione è disponibile solo quando si utilizza la libreria delle annotazioni AndroidX e quando includi il file delle regole ProGuard pacchettizzato con il plug-in Android Gradle, come descritto nella sezione su come attivare la riduzione.

Quando utilizzi l'opzione -keep devi tenere conto di molte considerazioni; 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.

Elimina le librerie native

Per impostazione predefinita, le librerie di codice nativo vengono eliminate nelle build di release della tua app. Questa eliminazione consiste nella rimozione della tabella dei simboli e delle informazioni di debug contenute in tutte le librerie native utilizzate dalla tua app. L'eliminazione delle librerie di codice nativo comporta un notevole risparmio di dimensioni; tuttavia, è impossibile diagnosticare gli arresti anomali su Google Play Console a causa delle informazioni mancanti (come i nomi di classi e funzioni).

Supporto degli arresti anomali nativi

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 di eseguire analisi dello stack in caso di arresti anomali nativi simbolizzati (che includono i 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 utilizzato 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 nativo dei simboli di debug al suo interno. Per includere questo file nelle build della 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 le seguenti opzioni:

  • Usa SYMBOL_TABLE per ottenere i nomi delle funzioni nelle analisi dello stack simbolizzate 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, usa l'impostazione di build build.gradle.kts mostrata in precedenza per generare separatamente il file dei simboli di debug nativi. Carica manualmente il file dei simboli di debug nativi su Google Play Console. Come parte del processo di compilazione, il plug-in Android Gradle restituisce questo file nel seguente percorso 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 strappate in una directory del progetto. La 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
  1. Comprimi i contenuti di questa directory:

    cd app/build/intermediates/cmake/universal/release/obj
    zip -r symbols.zip .
    
  2. Carica il file symbols.zip manualmente su Google Play Console.

Riduci le risorse

La riduzione delle risorse funziona solo in combinazione con la riduzione del codice. Una volta rimosso tutto il codice inutilizzato, lo strumento di shrinker può identificare le risorse che l'app utilizza ancora. Questo vale in particolare quando si aggiungono librerie di codice che includono risorse: è necessario rimuovere il codice delle librerie inutilizzato in modo che le risorse di libreria non presentino più riferimenti e, di conseguenza, possano essere rimosse dallo strumento di riduzione delle risorse.

Per attivare la riduzione delle risorse, imposta la proprietà shrinkResources su true nello script di build (oltre 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"
            )
        }
    }
}

trendy

android {
    ...
    buildTypes {
        release {
            shrinkResources true
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android.txt'),
                'proguard-rules.pro'
        }
    }
}

Se non hai ancora creato la tua app utilizzando minifyEnabled per la riduzione del codice, prova a farlo prima di attivare shrinkResources, in quanto potrebbe essere necessario modificare il file proguard-rules.pro per mantenere le classi o i metodi 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 eliminare, crea un file XML nel progetto con un tag <resources> e specifica ogni risorsa da mantenere nell'attributo tools:keep e ogni risorsa da annullare nell'attributo tools:discard. Entrambi gli attributi accettano un elenco di nomi di risorse separati da virgole. Puoi usare l'asterisco come un 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 questo file nelle risorse del progetto, ad esempio in res/raw/keep.xml. La build non pacchettizza questo file nella tua app.

Specificare quali risorse ignorare può sembrare sciocco quando puoi eliminarle, ma può essere utile quando utilizzi varianti di build. Ad esempio, potresti inserire tutte le risorse nella directory del progetto comune e poi creare un file keep.xml diverso per ogni variante di build quando sai che una determinata risorsa sembra essere utilizzata nel codice (e quindi non rimossa dallo strumento di restringimento), ma sai che in realtà non verrà utilizzata per la variante di build specificata. È anche possibile che gli strumenti di creazione abbiano identificato in modo errato una risorsa come necessario, il che è possibile perché il compilatore aggiunge gli ID risorsa in linea e, in seguito, l'analizzatore delle risorse potrebbe non conoscere la differenza tra una risorsa di riferimento originale e un valore intero nel codice che presenta lo stesso valore.

Abilita controlli del riferimento rigorosi

Normalmente, lo strumento di riduzione delle risorse può determinare con precisione se viene utilizzata una risorsa. Tuttavia, se il tuo codice effettua una chiamata a Resources.getIdentifier() (o se una delle tue librerie lo fa, la libreria AppCompat sì), significa che il tuo codice cerca i nomi delle risorse sulla base di stringhe generate dinamicamente. Quando esegui questa operazione, per impostazione predefinita lo strumento di riduzione delle risorse contrassegna tutte le risorse con un formato di nome corrispondente come potenzialmente utilizzate e non disponibili per la rimozione.

Ad esempio, il codice seguente 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 strumento di riduzione delle risorse esamina anche tutte le costanti stringhe nel codice e varie risorse res/raw/, cercando gli 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 potrebbero essere utilizzate per creare URL come questo, non le rimuove.

Questi sono esempi della modalità di riduzione sicura attivata per impostazione predefinita. Tuttavia, puoi disattivare questa gestione più sicura che non sia e specificare che lo strumento di riduzione delle risorse conserva solo le risorse che è certo che vengano utilizzate. A questo scopo, 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 abiliti la modalità di riduzione rigida e il tuo codice fa riferimento anche a risorse con stringhe generate dinamicamente, come mostrato sopra, devi conservare manualmente queste risorse utilizzando l'attributo tools:keep.

Rimuovi le risorse alternative non utilizzate

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 le diverse configurazioni dei dispositivi. Se necessario, puoi utilizzare la proprietà resConfigs del plug-in Android Gradle per rimuovere i file di risorse alternative non necessari per la tua app.

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 quelle librerie, indipendentemente dal fatto che il resto dell'app sia tradotto o meno nelle stesse lingue. Se vuoi mantenere solo le lingue supportate ufficialmente dalla tua app, puoi specificare queste lingue utilizzando la proprietà resConfig. Tutte le risorse per le lingue non specificate vengono rimosse.

Il seguente snippet mostra come limitare le risorse delle lingue solo all'inglese e al francese:

Kotlin

android {
    defaultConfig {
        ...
        resourceConfigurations.addAll(listOf("en", "fr"))
    }
}

trendy

android {
    defaultConfig {
        ...
        resConfigs "en", "fr"
    }
}

Quando rilasci un'app in formato Android App Bundle, per impostazione predefinita vengono scaricate solo le lingue configurate sul dispositivo di un utente durante l'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 corrispondenti all'ABI del dispositivo. Per ulteriori informazioni, consulta la pagina Configurazione di Android App Bundle.

Per le app legacy che vengono rilasciate con APK (creati prima di agosto 2021), puoi personalizzare la densità dello schermo o le risorse ABI da includere nell'APK creando più APK che hanno come target una configurazione dispositivo diversa.

Unire risorse duplicate

Per impostazione predefinita, Gradle unisce anche risorse con nomi identici, ad esempio elementi disegnati 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 della risorsa. Gradle seleziona il file che considera la scelta migliore tra i duplicati (in base all'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 origine principale, generalmente si trovano in src/main/res/.
  • Gli overlay delle varianti, dal tipo di build e dai sapori.
  • Le dipendenze del progetto libreria.

Gradle unisce le risorse duplicate nel seguente ordine di priorità a cascata:

Dipendenze → Principale → Versione di build → Tipo di build

Ad esempio, se una risorsa duplicata viene visualizzata sia nelle risorse principali che in una versione di build, Gradle seleziona quella nella versione di build.

Se nello stesso set di origini sono presenti risorse identiche, Gradle non può unirle e genera un errore di unione delle risorse. Questo può accadere se definisci più set di origine 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 accorciando i nomi delle classi, dei metodi e dei 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

L'offuscamento non rimuove il codice dall'app, ma si può riscontrare un notevole risparmio in termini di dimensioni nelle app con file DEX che indicizzano molti metodi, classi e campi. Tuttavia, poiché l'offuscamento rinomina diverse parti del codice, determinate attività, come l'ispezione delle analisi dello stack, richiedono strumenti aggiuntivi. Per comprendere lo 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 dell'app, ad esempio quando utilizzi gli attributi riflesso, devi trattare queste firme come punti di contatto e specificare 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 file DEX finale dell'app, ma anche di mantenere la sua denominazione originale.

Decodificare un'analisi dello stack offuscata

Dopo che R8 offusca il codice, è difficile comprendere un'analisi dello stack (se non impossibile) perché i nomi di classi e metodi potrebbero essere stati modificati. Per ottenere l'analisi dello stack originale, devi ritracciare l'analisi dello stack.

Ottimizzazione del codice

Per ridurre ulteriormente le dimensioni dell'app, R8 esamina il codice a un livello più approfondito per rimuovere la parte di codice inutilizzato o, se possibile, riscrivere il codice per renderlo meno dettagliato. Di seguito sono riportati alcuni esempi di queste ottimizzazioni:

  • Se il tuo codice non accetta mai il ramo else {} per una determinata istruzione if/else, R8 potrebbe rimuovere il codice per il ramo else {}.
  • Se il codice chiama un metodo in un solo posto, R8 potrebbe rimuovere il metodo e incorporarlo nel sito della chiamata singola.
  • Se R8 determina che una classe ha una sola sottoclasse univoca e che la classe stessa non crea un'istanza (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 ulteriori informazioni, 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. Di fatto, R8 ignora qualsiasi regola ProGuard che tenti di modificare ottimizzazioni predefinite, come -optimizations e - optimizationpasses. Questa limitazione è importante perché, dato che R8 continua a migliorare, mantenere un comportamento standard per le ottimizzazioni aiuta il team di Android Studio a 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'opzione inline rimuove gli stack frame. Per informazioni su come ottenere le analisi dello stack originali, consulta la sezione sul ritracciamento.

Consenti ottimizzazioni più aggressive

R8 include un insieme di ottimizzazioni aggiuntive (chiamate "modalità completa") che lo fanno funzionare in modo diverso da ProGuard. Queste ottimizzazioni sono abilitate per impostazione predefinita a partire dalla versione del plug-in Android per Gradle 8.0.0.

Puoi disabilitare queste ottimizzazioni aggiuntive includendo quanto segue nel file gradle.properties del tuo 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 codice faccia riferimento a una classe tramite l'API Java Reflection. Quando non utilizzi la "modalità completa", R8 presuppone che tu voglia esaminare e manipolare gli oggetti di quella classe in fase di runtime, anche se il tuo codice in realtà non è così, e conserva automaticamente la classe e il relativo inizializzatore statico.

Tuttavia, quando utilizzi la "modalità completa", R8 non fa questo presupposto e, se R8 afferma che il tuo codice non utilizza mai la classe in fase di runtime, la rimuove dal file DEX finale dell'app. In altre parole, se vuoi mantenere la classe e il relativo inizializzatore statico, devi includere una regola di 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.

Ritracciare le analisi dello stack

Il codice elaborato da R8 viene modificato in vari modi che possono rendere le analisi dello stack più difficili da comprendere, perché le analisi dello stack non corrispondono esattamente al codice sorgente. Questo può accadere per modifiche ai numeri di riga quando le informazioni di debug non vengono conservate. Ciò può essere dovuto a ottimizzazioni come l'allineamento e la strutturazione. Il fattore principale è 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, in bundle con il pacchetto di strumenti a riga di comando.

Per supportare il ritracciamento delle analisi dello stack dell'applicazione, devi assicurarti che la build conservi informazioni sufficienti da usare 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 affinché queste posizioni vengano stampate nelle analisi dello stack. L'attributo SourceFile assicura che tutti i potenziali runtime stampino effettivamente le informazioni relative alla posizione. L'istruzione -renamesourcefileattribute imposta il nome del file di origine nelle analisi dello stack solo su SourceFile. Il nome effettivo del file di origine originale non è richiesto durante il ritracciamento perché il file di mappatura contiene il file di origine originale.

A ogni esecuzione, R8 crea un file mapping.txt contenente 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. Quando pubblichi l'app utilizzando Android App Bundle, questo file viene incluso automaticamente nei contenuti dell'app bundle. Dopodiché Google Play ripercorre le analisi dello stack in arrivo dai problemi segnalati dall'utente, in modo che tu possa 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

In questa sezione vengono descritte alcune strategie per la risoluzione dei problemi relativi all'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 sul codice rimosso (o conservato)

Per aiutarti a risolvere determinati problemi relativi a R8, potrebbe essere utile visualizzare un report di tutto il codice che R8 ha rimosso dall'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 file specificati. 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 sui punti di ingresso che R8 determina dalle regole di Keep del progetto , includi -printseeds <output-dir>/seeds.txt nel file di regole personalizzate. Quando abiliti R8 e crei la tua app, R8 genera un report con il percorso e il nome file che hai specificato. Il report sui punti di ingresso mantenuti è 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 le risorse che fanno riferimento ad altre risorse e le risorse utilizzate o rimosse.

Ad esempio, per scoprire perché @drawable/ic_plus_anim_016 è ancora nella tua app, apri il file resources.txt e cerca quel nome. Potresti scoprire che vi viene fatto riferimento da un'altra risorsa, come segue:

16:25:48.005 [QUIET] [system.out] &#64;drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out]     &#64;drawable/ic_plus_anim_016

Ora devi sapere perché @drawable/add_schedule_fab_icon_anim è raggiungibile e, se effettui una ricerca verso l'alto, vedrai che la risorsa è elencata in "Le risorse principali raggiungibili sono:". Questo significa che esiste 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 nel caso in cui siano presenti costanti stringa che potrebbero essere utilizzate per creare 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 la risorsa specificata in modo dinamico, puoi utilizzare l'attributo tools:discard per comunicare al sistema di compilazione di rimuoverla, come descritto nella sezione su come personalizzare le risorse da conservare.