Scegli le librerie con cura

Per attivare l'ottimizzazione delle app, devi utilizzare librerie compatibili con l'ottimizzazione di Android. Se una libreria non è configurata per l'ottimizzazione di Android, ad esempio se utilizza la reflection senza raggruppare le regole di conservazione associate, potrebbe non essere adatta a un'app per Android. Questa pagina spiega perché alcune librerie sono più adatte all'ottimizzazione delle app e fornisce suggerimenti generali per aiutarti a scegliere.

Suggerimenti generali per la scelta delle librerie

Utilizza questi suggerimenti per assicurarti che le tue librerie siano compatibili con l'ottimizzazione dell'app.

Preferisci la generazione di codice alla riflessione

Scegli librerie che utilizzano la generazione di codice (codegen) anziché la reflection. Con la generazione di codice, l'ottimizzatore può determinare quale codice viene effettivamente utilizzato in fase di runtime e quale codice può essere rimosso. Può essere difficile capire se una libreria utilizza la generazione di codice o la riflessione, ma ci sono alcuni segnali. Per assistenza, consulta i suggerimenti.

Per ulteriori informazioni sulla generazione di codice rispetto alla riflessione, consulta Ottimizzazione per gli autori di librerie.

Verifica l'utilizzo della riflessione (avanzato)

Puoi capire se una libreria utilizza la riflessione esaminando il suo codice. Se la libreria utilizza la riflessione, verifica che fornisca regole di conservazione associate. Una libreria probabilmente utilizza la reflection se esegue le seguenti operazioni:

  • Utilizza classi o metodi dei pacchetti kotlin.reflect o java.lang.reflect.
  • Utilizza le funzioni Class.forName o classLoader.getClass.
  • Legge le annotazioni in fase di runtime, ad esempio se memorizza un valore di annotazione utilizzando val value = myClass.getAnnotation() o val value = myMethod.getAnnotation() e poi esegue un'azione con value.
  • Chiama i metodi utilizzando il nome del metodo come stringa, come nell'esempio seguente:

    // Calls the private `processData` API with reflection
    myObject.javaClass.getMethod("processData", DataType::class.java)
    ?.invoke(myObject, data)
    

Verifica la presenza di problemi di ottimizzazione

Quando valuti una nuova libreria, consulta il relativo Issue Tracker e le discussioni online per verificare se sono presenti problemi relativi alla minimizzazione o alla configurazione dell'ottimizzazione dell'app. In questo caso, dovresti provare a cercare alternative a quella libreria. Tieni presente quanto segue:

  • Le librerie AndroidX e librerie come Hilt funzionano bene con l'ottimizzazione delle app perché utilizzano principalmente la generazione di codice anziché la riflessione. Quando utilizzano la riflessione, forniscono regole di conservazione minime per mantenere solo il codice necessario.
  • Le librerie di serializzazione utilizzano spesso la riflessione per evitare il codice boilerplate durante la creazione di istanze o la serializzazione degli oggetti. Anziché approcci basati sulla riflessione (come Gson per JSON), cerca librerie che utilizzano la generazione di codice per evitare questi problemi, ad esempio utilizzando Kotlin Serialization o Moshi con generazione di codice.
  • Se possibile, evita le librerie che includono regole di conservazione a livello di pacchetto. Le regole di conservazione a livello di pacchetto possono aiutare a risolvere gli errori, ma le regole di conservazione generali dovrebbero essere perfezionate per conservare solo il codice necessario. Per saperne di più, consulta Adotta le ottimizzazioni in modo incrementale.
  • Prima di pubblicare un'app che utilizza una libreria di terze parti, utilizza R8 Configuration Analyzer per controllare le regole di conservazione fornite. Esaminando il report, puoi verificare se le regole di conservazione della libreria sono troppo ampie, impedendo a R8 di eseguire ottimizzazioni critiche sul tuo codebase. Questo controllo garantisce che le librerie selezionate siano in linea con gli obiettivi di rendimento della tua app e non introducano un inutile sovraccarico di configurazione.
  • Le librerie non dovrebbero richiedere di copiare e incollare le regole keep dalla documentazione in un file del progetto, in particolare non le regole keep a livello di pacchetto. Queste regole diventano un onere di manutenzione per lo sviluppatore di app nel lungo termine e sono difficili da ottimizzare e modificare nel tempo.

Attiva l'ottimizzazione dopo aver aggiunto una nuova raccolta

Quando aggiungi una nuova libreria, attiva l'ottimizzazione in un secondo momento e verifica se sono presenti errori. Se sono presenti errori, cerca alternative alla libreria o scrivi regole di conservazione. Se una libreria non è compatibile con l'ottimizzazione, segnala un bug per quella libreria.

Filtra le regole di conservazione errate (avanzato)

Le regole di conservazione sono cumulative. Ciò significa che alcune regole incluse in una dipendenza della libreria non possono essere rimosse e potrebbero influire sulla compilazione di altre parti della tua app. Ad esempio, se una libreria include una regola per disattivare le ottimizzazioni del codice, questa regola disattiva le ottimizzazioni per l'intero progetto.

Dovresti evitare le librerie con regole di conservazione che mantengono il codice che dovrebbe essere rimosso. Tuttavia, se devi utilizzarle, puoi filtrare le regole come mostrato nel seguente codice:

// If you're using AGP 8.4 and higher
buildTypes {
    release {
        optimization.keepRules {
          it.ignoreFrom("com.somelibrary:somelibrary")
        }
    }
}

// If you're using AGP 7.3-8.3
buildTypes {
    release {
        optimization.keepRules {
          it.ignoreExternalDependencies("com.somelibrary:somelibrary")
        }
    }
}

Workflow consigliato per le regole generali della libreria

Se R8 Configuration Analyzer rivela che una libreria raggruppa regole di conservazione troppo ampie, filtra queste regole solo nelle build locali, non in quelle di produzione. Per verificare l'impatto delle regole sulla tua app, segui questi passaggi:

  1. Aggiorna la libreria: assicurati di utilizzare l'ultima versione della libreria segnalata da R8 Configuration Analyzer, perché le versioni più recenti potrebbero includere regole di conservazione dei consumer più restrittive. Per trovare l'ultima versione, consulta la documentazione della libreria o il relativo repository Maven. Android Studio segnala anche le versioni più recenti di molte dipendenze. Dopo l'aggiornamento, esegui di nuovo l'analizzatore della configurazione R8 per verificare se le regole di conservazione ampie rimangono.
  2. Verifica il potenziale miglioramento localmente: copia le regole consumer pacchettizzate della libreria nel file delle regole keep locali, filtra le regole pacchettizzate come mostrato in Filtra le regole keep non valide e restringi le regole copiate. Esegui R8 Configuration Analyzer prima e dopo aver ristretto le regole per confrontare i punteggi di riduzione, ottimizzazione e offuscamento della tua app.
  3. Segnala i risultati: invia una segnalazione di bug al gestore della libreria contenente i dati empirici del report di R8 Configuration Analyzer (ad esempio, dimostrando il numero specifico di classi o metodi non referenziati conservati dalla regola con caratteri jolly).

Case study: perché Gson non funziona con le ottimizzazioni

Gson è una libreria di serializzazione che spesso causa problemi con l'ottimizzazione delle app perché utilizza molto la reflection. Il seguente snippet di codice mostra come viene utilizzato in genere Gson, il che può causare arresti anomali in fase di runtime. Tieni presente che quando utilizzi Gson per ottenere un elenco di oggetti User, non chiami il costruttore né passi una factory alla funzione fromJson(). La creazione o l'utilizzo di classi definite dall'app per utenti finali senza uno dei seguenti elementi è un segno che una libreria potrebbe utilizzare la reflection aperta:

  • Classe dell'app che implementa una libreria o un'interfaccia o una classe standard
  • Plug-in di generazione del codice come KSP
class User(val name: String)
class UserList(val users: List<User>)

// This code runs in debug mode, but crashes when optimizations are enabled
Gson().fromJson("""[{"name":"myname"}]""", User::class.java).toString()

Per capire come funziona R8 su Gson, consulta le regole per i consumer di Gson. Quando R8 analizza questo codice e non vede UserList o User di cui è stata creata un'istanza, può rinominare i campi o rimuovere i costruttori che non sembrano essere utilizzati, causando l'arresto anomalo dell'app. Se utilizzi altre librerie in modi simili, devi verificare che non interferiscano con l'ottimizzazione dell'app e, in caso affermativo, evitarle.

Utilizza @SerializedName con versioni Gson successive

Per definire i modelli di dati in modo compatibile con le regole consumer di Gson, annota i campi con @SerializedName, come mostrato nello snippet seguente:

import com.google.gson.annotations.SerializedName

class User(@SerializedName("name") val name: String)
class UserList(@SerializedName("users") val users: List<User>)

Utilizzando l'annotazione @SerializedName, consenti a R8 di abbinare le classi del modello alle regole di conservazione incluse in Gson versione 2.11.0 e successive. R8 conserva automaticamente i campi annotati e i costruttori necessari, consentendoti di fare affidamento sulle regole incluse nella libreria senza dover gestire configurazioni ProGuard manuali nel tuo progetto.

Tieni presente che Room, Hilt e Moshi con codegen creano tipi definiti dall'app, ma utilizzano codegen per evitare la necessità di reflection.