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.

Preferisci la generazione di codice alla reflection

In genere, dovresti scegliere 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.

Suggerimenti generali per la scelta delle librerie

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

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 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 codegen anziché 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 durante l'istanziamento o la serializzazione degli 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.
  • 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 generiche devono essere perfezionate per conservare solo il codice necessario. Per maggiori informazioni, consulta Adotta 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, in particolare 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.

Le regole sono cumulative

Tieni presente che 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.

Verifica l'utilizzo della riflessione (avanzata)

Potresti essere in grado di 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 probabilmente utilizza 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, ad esempio:

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

Filtra le regole di conservazione errate (avanzato)

Dovresti evitare librerie con regole di conservazione che mantengono codice che dovrebbe essere rimosso. Tuttavia, se devi utilizzarli, 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 è un segno 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()

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.

Tieni presente che Room e Hilt creano entrambi tipi definiti dall'app, ma utilizzano la generazione di codice per evitare la necessità di reflection.