Riduci, offusca e ottimizza la tua app

Per ridurre il più possibile le dimensioni dell'app, devi attivare la compressione nella build di 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 tempo di compilazione per il tuo progetto e come puoi personalizzarle.

Quando crei il tuo progetto utilizzando il plug-in Android per 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 funziona invece con il compilatore R8 per gestire le seguenti attività in fase di compilazione:

  • Riduzione del codice (o scuotimento degli alberi): rileva e rimuove in modo sicuro classi, campi, metodi e attributi inutilizzati dalla tua app e dalle sue dipendenze di libreria, il che la rende uno strumento utile per aggirare il limite di riferimento di 64.000. Ad esempio, se utilizzi solo alcune API di una dipendenza della libreria, la riduzione può identificare il codice di libreria che la tua app non utilizza e rimuovere solo quel codice dalla tua app. Per scoprire di più, consulta la sezione su come ridurre il codice.
  • Riduzione delle risorse: rimuove le risorse non utilizzate dall'app in pacchetto, incluse quelle non utilizzate nelle dipendenze della libreria dell'app. Funziona insieme alla riduzione del codice, in modo che, una volta rimosso il codice inutilizzato, anche le risorse a cui non viene più fatto riferimento possono essere rimosse in sicurezza. Per scoprire di più, vai alla sezione su come ridurre le risorse.
  • Offuscamento: abbrevia il nome delle classi e dei membri, riducendo di conseguenza le dimensioni dei file DEX. Per saperne di più, vai alla 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 saperne di più, vai alla 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. In effetti, R8 funziona con tutti i file di regole ProGuard esistenti, pertanto l'aggiornamento del plug-in Android per Gradle per utilizzare R8 non dovrebbe richiedere la modifica delle regole esistenti.

Abilita riduzione, offuscamento e ottimizzazione

Quando utilizzi Android Studio 3.4 o il plug-in Android per 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 il tempo 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 che testerai 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

            // 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 suo comportamento predefinito e comprendere meglio la struttura della tua app, ad esempio le classi che fungono da punti di ingresso al codice dell'app. Anche se puoi modificare alcuni di questi file di regole, alcune regole potrebbero essere generate automaticamente da strumenti in fase di compilazione, come AAPT2, o ereditate dalle dipendenze di libreria della tua app. La tabella seguente descrive le origini dei file delle 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. Quindi, 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 per 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 per 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 di file di regole pacchettizzati con librerie AAR è utile se sono necessarie determinate regole di conservazione affinché la libreria funzioni correttamente, ovvero se lo sviluppatore della libreria ha eseguito la procedura di risoluzione dei problemi al posto tuo.

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 regola disattiva le ottimizzazioni per l'intero progetto.

Strumento 2 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 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 come punto di ingresso nel file manifest dell'app.
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 e 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 quando esegui la risoluzione dei problemi con R8, perché altre dipendenze in fase di compilazione, come le dipendenze di libreria, potrebbero introdurre modifiche al comportamento di R8 di cui non sei a conoscenza.

Per generare un report completo di tutte le regole applicate da R8 durante la 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 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 build 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 di prodotto flavor2. Ora flavor2 utilizza tutte e tre le regole di ProGuard perché vengono applicate anche quelle del blocco release.

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"
            )
        }
    }
    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'
        }
    }
    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 contrazione del codice (nota anche come scuotimento degli alberi) è il processo di rimozione del codice che R8 ritiene non sia 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 della tua app, R8 determina innanzitutto tutti i punti di ingresso al codice della tua app in base all'insieme combinato di file di configurazione. 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 controlla il codice della tua app per creare un grafico di tutti i metodi, le variabili membro 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 relativo metodo baz() non vengono mai utilizzati dalla tua app in fase di runtime e R8 rimuove quel 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, mantieni le regole che specificano le classi che R8 non deve eliminare quando riduce la tua app e R8 le considera come possibili punti di ingresso per la tua app. Il plug-in Android per 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 potrebbe rimuovere il codice di cui la tua app ha effettivamente bisogno. Ecco alcuni esempi di casi in cui potrebbe rimuovere il codice in modo errato:

  • 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 stato riflesso)

Il test dell'app dovrebbe rilevare 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 mantenere. L'aggiunta di @Keep a una classe mantiene l'intera classe così com'è. Se la aggiungi a un metodo o campo, il metodo/campo (e il relativo nome) e il nome della classe rimangono invariati. Tieni presente che questa annotazione è disponibile solo quando utilizzi 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.

Ci sono molte considerazioni da fare quando si utilizza l'opzione -keep; 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 release delle release della tua app. Questa rimozione comporta la rimozione della tabella dei simboli e il debug delle informazioni contenute in tutte le librerie native utilizzate dall'app. L'eliminazione delle librerie di codice nativo comporta un notevole risparmio di dimensioni; tuttavia, è impossibile diagnosticare arresti anomali su Google Play Console a causa della mancanza di informazioni (come i nomi di classi e funzioni).

Supporto nativo per gli 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 di utilizzare analisi dello stack native simbolizzate (che includono i nomi di classi e funzioni) in Android vitals per aiutarti a eseguire il debug dell'app in produzione. Questi passaggi variano a seconda della versione del plug-in Android per Gradle 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. 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 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 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 prive di 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 in combinazione con la riduzione del codice. Una volta rimosso tutto il codice inutilizzato, questo strumento può identificare le risorse che l'app utilizza ancora. Ciò è particolarmente vero quando si aggiungono librerie di codice che includono risorse: è necessario rimuovere il codice della libreria inutilizzato in modo che le risorse di libreria non vengano utilizzate come riferimento e, pertanto, possano essere rimosse dallo strumento di riduzione delle risorse.

Per abilitare 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 questa procedura prima di attivare shrinkResources, in quanto potrebbe essere necessario modificare il file proguard-rules.pro per conservare 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 scartare nell'attributo tools:discard. Entrambi gli attributi accettano un elenco di nomi di risorse separati da virgole. Puoi usare l'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 questo file nelle risorse del progetto, ad esempio, in res/raw/keep.xml. La build non pacchettizza questo file nella tua app.

Specificare le risorse da eliminare potrebbe sembrare una cosa sciocco quando invece potresti eliminarle, ma questo 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 riduzione dei dati), ma sai che in realtà non verrà utilizzata per la variante di build specificata. È anche possibile che gli strumenti di creazione abbiano identificato erroneamente una risorsa come necessaria, il che è possibile perché il compilatore aggiunge gli ID risorsa in linea e quindi l'analizzatore delle risorse potrebbe non sapere la differenza tra una risorsa a cui viene fatto riferimento in modo autentico e un valore intero nel codice che ha lo stesso valore.

Abilita controlli rigorosi dei riferimenti

Normalmente, lo strumento per la 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, come la libreria AppCompat), significa che il tuo codice cerca i nomi delle risorse sulla base di stringhe generate dinamicamente. In questo caso, per impostazione predefinita lo strumento per la riduzione delle risorse si comporta in modo difensivo e 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 per la riduzione delle risorse esamina anche tutte le costanti di stringa 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 e specificare che lo strumento per la riduzione delle risorse conserva solo le risorse che è certo di utilizzare. A tale 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 codice fa anche riferimento a risorse con stringhe generate dinamicamente, come mostrato sopra, devi mantenere 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 fanno riferimento il codice dell'app, il che significa che non verranno rimosse le risorse alternative per le diverse configurazioni dei dispositivi. Se necessario, puoi utilizzare la proprietà resConfigs del plug-in Android per 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 tali 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 tali lingue utilizzando la proprietà resConfig. Tutte le risorse per le lingue non specificate vengono rimosse.

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

Kotlin

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

Trendy

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 durante l'installazione dell'app. Allo stesso modo, nel download vengono incluse solo le risorse corrispondenti alla densità dello schermo del dispositivo e le librerie native corrispondenti all'ABI del dispositivo. Per ulteriori informazioni, consulta la pagina relativa alla configurazione di Android App Bundle.

Per le app precedenti 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.

Unisci risorse duplicate

Per impostazione predefinita, Gradle unisce anche risorse con nomi identici, ad esempio elementi disegnabili 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 che il codice cerca.

L'unione delle risorse si verifica solo quando due o più file condividono lo stesso nome, tipo e qualificatore della risorsa. Gradle seleziona il file che considera 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 origini principale, si trovano generalmente in src/main/res/.
  • Le varianti si sovrappongono, dal tipo di build e dalle versioni di build.
  • 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 viene visualizzata una risorsa duplicata sia nelle risorse principali sia in una versione di build, Gradle seleziona quella nella versione 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.

Offusca 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

L'offuscamento non rimuove il codice dall'app, ma consente un notevole risparmio di dimensioni nelle app con file DEX che indicizzano molte classi, metodi e campi. Tuttavia, poiché l'offuscamento rinomina diverse parti del codice, alcune attività, come l'ispezione delle analisi dello stack, richiedono strumenti aggiuntivi. Per comprendere l'analisi dello stack dopo l'offuscamento, leggi la sezione su come decodificare un'analisi dello stack offuscata.

Inoltre, se il codice si basa su nomi prevedibili per metodi e classi dell'app, ad esempio quando utilizzi la riflessione, dovresti trattare queste firme come punti di contatto 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 il suo nome originale.

Decodifica di un'analisi dello stack offuscata

Dopo che R8 offusca 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 ridurre ulteriormente le dimensioni dell'app, R8 analizza il codice a un livello più approfondito per rimuovere la maggior parte del 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 un solo posto, R8 potrebbe rimuovere il metodo e incorporarlo nel sito di chiamata singola.
  • Se R8 stabilisce che una classe ha una sola sottoclasse univoca e che non è stata 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 ulteriori informazioni, leggi i post del blog sull'ottimizzazione R8 di Jake Wharton.

R8 non consente di disattivare o attivare ottimizzazioni discrete o di modificare il comportamento di un'ottimizzazione. In effetti, 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, mantenere un comportamento standard per le ottimizzazioni aiuta il team di Android Studio a individuare e risolvere facilmente eventuali problemi che potrebbero verificarsi.

Tieni presente che l'abilitazione dell'ottimizzazione modificherà le analisi dello stack per l'applicazione. Ad esempio, l'opzione "inline" rimuoverà gli stack frame. Consulta la sezione sul ritracciamento per informazioni su come ottenere le analisi dello stack originali.

Consenti ottimizzazioni più aggressive

R8 include una serie di ottimizzazioni aggiuntive (denominate "modalità completa") che lo fanno in modo diverso da ProGuard. Queste ottimizzazioni sono attive 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 funzionare R8 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 utilizza la "modalità completa", R8 presuppone che tu voglia esaminare e manipolare gli oggetti di quella classe in fase di runtime, anche se il codice in realtà non lo è, e mantiene automaticamente la classe e il relativo inizializzatore statico.

Tuttavia, quando utilizzi la "modalità completa", R8 non fa questo presupposto e, se afferma che il tuo codice non utilizza mai la classe in esecuzione, la rimuove dal DEX finale dell'app. In altre parole, se vuoi conservare 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.

Tracciamento delle analisi dello stack

Il codice elaborato da R8 viene modificato in vari modi per rendere le analisi dello stack più difficili da comprendere, poiché le analisi dello stack non corrispondono esattamente al codice sorgente. Questo può riguardare le modifiche ai numeri di riga quando le informazioni di debug non vengono conservate. Ciò può essere dovuto a ottimizzazioni come l'inserimento in linea e con i contenuti. Il fattore 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, 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 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 relative alla posizione nei metodi che consentono di stampare queste posizioni 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 su SourceFile. Il nome effettivo del file di origine originale non è necessario durante il ritracciamento perché il file di mappatura contiene il file di origine originale.

R8 crea un file mapping.txt ogni volta che viene eseguito, 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. Se utilizzi Android App Bundle per la pubblicazione, questo file viene incluso automaticamente nei contenuti dell'app bundle. Dopodiché Google Play ripercorre le analisi dello stack in entrata 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 che si verificano quando si abilita la riduzione, l'offuscamento e l'ottimizzazione utilizzando R8. Se di seguito non trovi una soluzione al tuo problema, leggi anche la pagina delle domande frequenti su R8 e la guida alla risoluzione dei problemi di ProGuard.

Generare un report sul codice rimosso (o mantenuto)

Per aiutarti a risolvere determinati problemi relativi a R8, potrebbe 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 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 sui 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 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 restringi 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 l'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 riportato di seguito:

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, troverai che la risorsa è elencata in "Le risorse principali raggiungibili 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 nel caso in cui esistano costanti di stringa che potrebbero essere utilizzate per creare nomi per le risorse caricate dinamicamente. In questo caso, se cerchi il nome della risorsa nell'output della build, potresti visualizzare un messaggio come questo:

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 viene utilizzata per caricare la risorsa in modo dinamico, puoi usare l'attributo tools:discard per comunicare al sistema di build di rimuoverla, come descritto nella sezione su come personalizzare le risorse da conservare.