Riduci, offusca e ottimizza la tua app

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 ramo else {}. 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 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 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.

Figura 1. Durante la compilazione, R8 crea un grafico basato sulle regole di conservazione combinate del tuo 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 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
  1. Comprimi i contenuti di questa directory:

    cd app/build/intermediates/cmake/universal/release/obj
    zip -r symbols.zip .
    
  2. 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 ramo else {}.
  • 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.