Configurare e risolvere i problemi relativi alle regole di conservazione di R8
Lettura di 7 minuti
Nello sviluppo Android moderno, la pubblicazione di un'applicazione piccola, veloce e sicura è un'aspettativa fondamentale degli utenti. Lo strumento principale del sistema di compilazione Android per raggiungere questo obiettivo è l'ottimizzatore R8 , il compilatore che gestisce la rimozione di codice e risorse inutilizzati per la riduzione, la ridenominazione o la minimizzazione del codice e l'ottimizzazione dell'app.
L'attivazione di R8 è un passaggio fondamentale nella preparazione di un'app per la release, ma richiede agli sviluppatori di fornire indicazioni sotto forma di "regole di conservazione".
Dopo aver letto questo articolo, guarda il video della settimana di Performance Spotlight sull'attivazione, il debug e la risoluzione dei problemi dell'ottimizzatore R8 su YouTube.
Perché sono necessarie le regole di conservazione
La necessità di scrivere regole di conservazione deriva da un conflitto fondamentale: R8 è uno strumento di analisi statica, ma le app Android spesso si basano su pattern di esecuzione dinamici come la reflection o le chiamate in entrata e in uscita dal codice nativo utilizzando JNI (Java Native Interface).
R8 crea un grafico del codice utilizzato analizzando le chiamate dirette. Quando si accede al codice in modo dinamico, l'analisi statica di R8 non può prevederlo, quindi identifica il codice come non utilizzato e lo rimuove, causando arresti anomali in fase di runtime.
Una regola di conservazione è un'istruzione esplicita per il compilatore R8 che indica: "Questa classe, questo metodo o questo campo specifico è un punto di ingresso a cui si accederà dinamicamente in fase di runtime. Devi conservarlo, anche se non riesci a trovare un riferimento diretto."
Per maggiori dettagli sulle regole di conservazione, consulta la guida ufficiale.
Dove scrivere le regole di conservazione
Le regole di conservazione personalizzate per un'applicazione vengono scritte in un file di testo. Per convenzione, questo file si chiama proguard-rules.pro e si trova nella radice del modulo dell'app o della libreria. Questo file viene quindi specificato nel tipo di build release del file build.gradle.kts del modulo.
release {
isShrinkResources = true
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}Utilizzare il file predefinito corretto
Il metodo getDefaultProguardFile importa un insieme predefinito di regole fornite dall'SDK Android. Se utilizzi il file sbagliato, l'app potrebbe non essere ottimizzata. Assicurati di utilizzare proguard-android-optimize.txt. Questo file fornisce le regole di conservazione predefinite per i componenti Android standard e abilita le ottimizzazioni del codice di R8. Il file obsoleto proguard-android.txt fornisce solo le regole di conservazione, ma non abilita le ottimizzazioni di R8.
Poiché si tratta di un grave problema di prestazioni, stiamo iniziando ad avvisare gli sviluppatori dell'utilizzo del file errato, a partire da Android Studio Narwhal 3 Feature Drop.Inoltre, a partire dalla versione 9.0 del plug-in Android per Gradle, non supporteremo più il file obsoleto proguard-android.txt. Assicurati quindi di eseguire l'upgrade alla versione ottimizzata.
Come scrivere le regole di conservazione
Una regola di conservazione è composta da tre parti principali:
- Un'opzione come
-keepo-keepclassmembers - Modificatori facoltativi come
allowshrinking - Una specifica di classe che definisce il codice da abbinare
Per la sintassi completa e gli esempi, consulta le indicazioni per aggiungere regole di conservazione.
Anti-pattern delle regole di conservazione
È importante conoscere le best practice, ma anche gli anti-pattern. Questi anti-pattern derivano spesso da incomprensioni o scorciatoie per la risoluzione dei problemi e possono essere catastrofici per il rendimento di una build di produzione.
Opzioni globali
Questi flag sono attivazioni/disattivazioni globali che non devono mai essere utilizzate in una build di release. Sono solo per il debug temporaneo per isolare un problema.
L'utilizzo di -dontotptimize disattiva efficacemente le ottimizzazioni del rendimento di R8, con conseguente rallentamento dell'app.
Quando utilizzi -dontobfuscate disattivi tutta la ridenominazione e quando utilizzi -dontshrink disattivi la rimozione del codice inutilizzato. Entrambe queste regole globali aumentano le dimensioni dell'app.
Per un'esperienza utente dell'app più performante, evita di utilizzare questi flag globali in un ambiente di produzione, se possibile.
Regole di conservazione eccessivamente ampie
Il modo più semplice per annullare i vantaggi di R8 è scrivere regole di conservazione eccessivamente ampie. Le regole di conservazione come quella riportata di seguito indicano all'ottimizzatore R8 di non ridurre, non offuscare e non ottimizzare nessuna classe in questo pacchetto o nessuno dei relativi sottopacchetti. In questo modo, i vantaggi di R8 vengono completamente rimossi per l'intero pacchetto. Prova invece a scrivere regole di conservazione specifiche e limitate.
-keep class com.example.package.** { *;} // WIDE KEEP RULES CAUSE PROBLEMSL'operatore di inversione (!)
L'operatore di inversione (!) sembra un modo efficace per escludere un pacchetto da una regola. Ma non è così semplice. Ecco un esempio:
-keep class !com.example.my_package.** { *; } // USE WITH CAUTIONPotresti pensare che questa regola significhi "non conservare le classi incom.example.package." Ma in realtà significa "conserva ogni classe, metodo e proprietà nell'intera applicazione che non si trova in com.example.package." Se questa cosa ti ha sorpreso, ti consigliamo di verificare la presenza di negazioni nella configurazione di R8.
Regole ridondanti per i componenti Android
Un altro errore comune è aggiungere manualmente regole di conservazione per le Activities, i Services o i BroadcastReceivers dell'app. Questo è inutile. Il file proguard-android-optimize.txt predefinito include già le regole pertinenti per il funzionamento di questi componenti Android standard.
Inoltre, molte librerie includono le proprie regole di conservazione. Pertanto, non dovresti dover scrivere le tue regole per queste librerie. Se si verifica un problema con le regole di conservazione di una libreria che stai utilizzando, ti consigliamo di contattare l'autore della libreria per capire qual è il problema.
Best practice per le regole di conservazione
Ora che sai cosa non fare, parliamo delle best practice.
Scrivere regole di conservazione limitate
Le regole di conservazione efficaci devono essere il più limitate e specifiche possibile. Devono conservare solo ciò che è necessario, consentendo a R8 di ottimizzare tutto il resto.
| Regola | Qualità |
|---|---|
| Bassa: conserva un intero pacchetto e i relativi sottopacchetti |
| Bassa: conserva un'intera classe, che probabilmente è ancora troppo ampia |
-keepclassmembers class com.example.MyClass {
private java.lang.String secretMessage;
public void onNativeEvent(java.lang.String);
} | Alta: vengono conservati solo i metodi e le proprietà pertinenti di una classe specifica |
Utilizzare antenati comuni
Invece di scrivere regole di conservazione separate per più modelli di dati diversi, scrivi una regola che abbia come target una classe base o un'interfaccia comune. La regola riportata di seguito indica a R8 di conservare tutti i membri delle classi che implementano questa interfaccia ed è altamente scalabile.
# Keep all fields of any class that implements SerializableModel
-keepclassmembers class * implements com.example.models.SerializableModel {
<fields>;
}Utilizzare le annotazioni per avere come target più classi
Crea un'annotazione personalizzata (ad es. @Serialize) e utilizzala per "taggare" le classi di cui è necessario conservare i campi. Si tratta di un altro pattern pulito, dichiarativo e altamente scalabile. Puoi anche creare regole di conservazione per le annotazioni già esistenti dei framework che stai utilizzando.
# Keep all fields of any class annotated with @Serialize
-keepclassmembers class * {
@com.example.annotations.Serialize <fields>;
}Scegliere l'opzione di conservazione corretta
L'opzione di conservazione è la parte più importante della regola. La scelta di quella sbagliata può disattivare inutilmente l'ottimizzazione.
| Opzione di conservazione | Cosa fa |
-keep | Impedisce la rimozione o la ridenominazione della classe e dei membri menzionati nella dichiarazione . |
-keepclassmembers | Impedisce la rimozione o la ridenominazione dei membri specificati, ma consente la rimozione della classe stessa, ma solo per le classi che non vengono rimosse in altro modo. |
-keepclasseswithmembers | Una combinazione: conserva la classe e i relativi membri, solo se sono presenti tutti i membri specificati. |
Per saperne di più sull'opzione di conservazione, consulta la documentazione relativa alle opzioni di conservazione.
Consentire l'ottimizzazione con i modificatori
I modificatori come allowshrinking e allowobfuscation rilassano una regola -keep ampia, restituendo a R8 la potenza di ottimizzazione. Ad esempio, se una libreria legacy ti costringe a utilizzare -keep su un'intera classe, potresti essere in grado di recuperare parte dell'ottimizzazione consentendo la riduzione e l'offuscamento:
# Keep this class, but allow R8 to remove it if it's unused and allow R8 to rename it. -keep,allowshrinking,allowobfuscation class com.example.LegacyClass
Aggiungere opzioni globali per un'ulteriore ottimizzazione
Oltre alle regole di conservazione, puoi aggiungere flag globali al file di configurazione di R8 per incoraggiare un'ulteriore ottimizzazione.
-repackageclasses è un'opzione efficace che indica a R8 di spostare tutte le classi offuscate in un unico pacchetto. In questo modo si risparmia spazio significativo nel file DEX rimuovendo le stringhe dei nomi dei pacchetti ridondanti.
-allowaccessmodification consente a R8 di ampliare l'accesso (ad es. da private a public) per consentire un'inlining più aggressiva. Questa opzione è ora abilitata per impostazione predefinita quando si utilizza proguard-android-optimize.txt.
Avviso: gli autori delle librerie non devono mai aggiungere questi flag di ottimizzazione globali alle regole dei consumatori, in quanto verrebbero applicati forzatamente all'intera app.
Inoltre, per rendere tutto ancora più chiaro, nella versione 9.0 del plug-in Android per Gradle inizieremo a ignorare completamente i flag di ottimizzazione globali delle librerie.
Best practice per le librerie
Ogni app Android si basa sulle librerie in un modo o nell'altro. Parliamo quindi delle best practice per le librerie.
Per gli sviluppatori di librerie
Se la tua libreria utilizza la reflection o JNI, hai la responsabilità di fornire le regole di conservazione necessarie ai suoi consumatori. Queste regole vengono inserite in un file consumer-rules.pro, che viene poi automaticamente incluso nel file AAR della libreria.
android {
defaultConfig {
consumerProguardFiles("consumer-rules.pro")
}
...
}Per i consumatori di librerie
Filtrare le regole di conservazione problematiche
Se devi utilizzare una libreria che include regole di conservazione problematiche, puoi filtrarle nel file build.gradle.kts a partire da AGP 9.0. In questo modo, R8 ignora le regole provenienti da una dipendenza specifica.
release {
optimization.keepRules {
// Ignore all consumer rules from this specific library
it.ignoreFrom("com.somelibrary:somelibrary")
}
}La migliore regola di conservazione è nessuna regola di conservazione
La strategia di configurazione di R8 definitiva consiste nel rimuovere completamente la necessità di scrivere regole di conservazione. Per molte app, questo obiettivo può essere raggiunto scegliendo librerie moderne che privilegiano la generazione di codice rispetto alla reflection. Con la generazione di codice, l'ottimizzatore può determinare più facilmente quale codice viene effettivamente utilizzato in fase di runtime e quale codice può essere rimosso. Inoltre, se non utilizzi alcuna reflection dinamica, non ci sono punti di ingresso "nascosti" e, di conseguenza, non sono necessarie regole di conservazione. Quando scegli una nuova libreria, preferisci sempre una soluzione che utilizzi la generazione di codice rispetto alla reflection.
Per ulteriori informazioni su come scegliere le librerie, consulta la pagina Scegliere le librerie con attenzione.
Debug e risoluzione dei problemi della configurazione di R8
Quando R8 rimuove il codice che avrebbe dovuto conservare o se l'APK è più grande del previsto, utilizza questi strumenti per diagnosticare il problema.
Trovare regole di conservazione duplicate e globali
Poiché R8 unisce le regole provenienti da decine di origini, può essere difficile sapere qual è l'insieme di regole "finale". L'aggiunta di questo flag al file proguard-rules.pro genera un report completo:
# Outputs the final, merged set of rules to the specified file -printconfiguration build/outputs/logs/configuration.txt
Puoi cercare in questo file le regole ridondanti o risalire a una regola problematica (come -dontoptimize) alla libreria specifica che la include.
Chiedere a R8: perché stai conservando questo?
Se una classe che ti aspettavi di rimuovere è ancora presente nell'app, R8 può dirti il motivo. Aggiungi questa regola:
# Asks R8 to explain why it's keeping a specific class class com.example.MyUnusedClass -whyareyoukeeping
Durante la build, R8 stamperà la catena esatta di riferimenti che ha causato la conservazione della classe, consentendoti di tracciare il riferimento e modificare le regole.
Per una guida completa, consulta la sezione Risolvere i problemi di R8.
Passaggi successivi
R8 è uno strumento efficace per migliorare il rendimento delle app Android. La sua efficacia dipende da una corretta comprensione del suo funzionamento come motore di analisi statica.
Scrivendo regole specifiche a livello di membro, sfruttando antenati e annotazioni e scegliendo con attenzione le opzioni di conservazione corrette, puoi conservare esattamente ciò che è necessario. La pratica più avanzata consiste nell'eliminare completamente la necessità di regole scegliendo librerie moderne basate su codegen rispetto alle loro predecessori basate su reflection.
Mentre segui la settimana di Performance Spotlight, assicurati di guardare il video di oggi su YouTube e continua con la nostra sfida R8. Utilizza #optimizationEnabled per eventuali domande sull'attivazione o sulla risoluzione dei problemi di R8. Siamo a tua disposizione.
È il momento di vedere i vantaggi in prima persona.
Ti invitiamo ad attivare la modalità completa di R8 per la tua app oggi.
- Per iniziare, segui le nostre guide per gli sviluppatori: Attivare l'ottimizzazione dell'app.
- Controlla se utilizzi ancora
proguard-android.txte sostituiscilo conproguard-android-optimize.txt. - Quindi, misura l'impatto. Non sentire solo la differenza, verificala. Misura i miglioramenti del rendimento adattando il codice della nostra app di esempio Macrobenchmark su GitHub per misurare i tempi di avvio prima e dopo.
Siamo certi che noterai un miglioramento significativo del rendimento della tua app.
Nel frattempo, utilizza il tag social #AskAndroid per porre le tue domande. Durante la settimana, i nostri esperti monitorano e rispondono alle tue domande.
Non perderti l'articolo di domani, in cui parleremo dell'ottimizzazione guidata dal profilo con i profili di baseline e di avvio, condivideremo il miglioramento del rendimento del rendering di Compose nelle release precedenti e condivideremo le considerazioni sul rendimento per il lavoro in background.
-
Novità sui prodottiA marzo abbiamo introdotto Android Bench, la nostra classifica LLM per le attività di sviluppo Android reali. Da allora, abbiamo migliorato il benchmark in base ai feedback ricevuti, tra cui la valutazione dei modelli open-weight e l'aggiunta di dimensioni di costo ed efficienza alla classifica.
Lettura di 3 minuti -
Novità sui prodottiIn Google Play, ci impegniamo a offrire la migliore esperienza possibile agli utenti, garantendo al contempo agli sviluppatori gli strumenti e l'adattabilità necessari per avere successo.
Paul Feng • Lettura di 3 minuti -
Novità sui prodottiL'anno scorso abbiamo introdotto la verifica dello sviluppatore Android per rafforzare la sicurezza dell'ecosistema e impedire agli utenti malintenzionati di nascondersi dietro l'anonimato per rilasciare app dannose.
Matthew Forsythe • Lettura di 2 minuti
Ricevi ogni settimana nella tua casella di posta gli ultimi approfondimenti sullo sviluppo Android