Scegli le librerie con cura

Per abilitare l'ottimizzazione delle app, devi utilizzare librerie compatibili con l'ottimizzazione di Android. Se una libreria non è configurata per l'ottimizzazione per Android, ad esempio se utilizza la reflection senza raggruppare le regole keep 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 delle app.

Preferire la generazione di codice alla reflection

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

Per ulteriori informazioni su codegen e reflection, consulta Ottimizzazione per gli autori di librerie.

Verifica l'utilizzo della riflessione (avanzato)

Puoi capire se una libreria utilizza la reflection ispezionando il suo codice. Se la libreria utilizza la reflection, verifica che fornisca regole di conservazione associate. Una libreria utilizza probabilmente la reflection se:

  • 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'operazione con value.
  • Chiama i metodi utilizzando il nome del metodo come stringa, come nel seguente esempio:

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

Controllare la presenza di problemi di ottimizzazione

Quando prendi in considerazione una nuova libreria, esamina il tracker dei problemi della libreria e le discussioni online per verificare se ci sono problemi relativi alla minificazione o alla configurazione dell'ottimizzazione dell'app. In caso affermativo, 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 reflection. Quando utilizzano la reflection, forniscono regole di conservazione minime per conservare solo il codice necessario.
  • Le librerie di serializzazione utilizzano spesso la reflection per evitare codice boilerplate quando istanziano o serializzano oggetti. Anziché approcci basati sulla reflection (come Gson per JSON), cerca librerie che utilizzano la generazione di codice per evitare questi problemi, ad esempio utilizzando Kotlin Serialization o Moshi con la 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 contribuire a risolvere gli errori, ma le regole di conservazione generali devono alla fine essere perfezionate per conservare solo il codice necessario. Per ulteriori informazioni, consulta Adottare le ottimizzazioni in modo incrementale.
  • Le librerie non devono richiedere di copiare e incollare le regole di conservazione dalla documentazione in un file del progetto, soprattutto non le regole di conservazione a livello di pacchetto. A lungo termine, queste regole diventano un onere di manutenzione per lo sviluppatore dell'app e sono difficili da ottimizzare e modificare nel tempo.

Attivare l'ottimizzazione dopo aver aggiunto una nuova libreria

Quando aggiungi una nuova libreria, attiva l'ottimizzazione in un secondo momento e verifica la presenza di 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 relativo alla libreria.

Filtra le regole di conservazione errate (avanzate)

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 dell'app. Ad esempio, se una libreria include una regola per disattivare le ottimizzazioni del codice, questa regola disattiva le ottimizzazioni per l'intero progetto.

Devi evitare librerie con regole di conservazione che mantengono 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")
        }
    }
}

Case study: perché Gson si interrompe con le ottimizzazioni

Gson è una libreria di serializzazione che spesso causa problemi di ottimizzazione delle app perché utilizza molto la reflection. Il seguente snippet di codice mostra come viene in genere utilizzato Gson, che può facilmente 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 senza una delle seguenti condizioni è un segnale che una libreria potrebbe utilizzare la reflection senza limiti:

  • 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 di consumo di Gson. Quando R8 analizza questo codice e non vede UserList o User istanziati da nessuna parte, può rinominare i campi o rimuovere i costruttori che non sembrano essere utilizzati, causando l'arresto anomalo dell'app. Se utilizzi altre librerie in modo simile, verifica che non interferiscano con l'ottimizzazione dell'app e, in caso contrario, evitale.

Per definire le classi in modo compatibile con le regole di consumo di Gson, utilizza il seguente snippet come riferimento:

class User(@com.google.gson.annotations.SerializedName("name") val name: String)
class UserList(@com.google.gson.annotations.SerializedName("users") val users: List<User>)

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