In qualità di autore di una libreria, devi assicurarti che gli sviluppatori di app possano incorporare facilmente la tua libreria nella loro app, mantenendo al contempo un'esperienza utente finale di alta qualità. Ciò significa che la tua libreria deve essere compatibile con l'ottimizzazione di Android (R8) senza richiedere configurazioni aggiuntive da parte dello sviluppatore oppure documentare che la libreria potrebbe non essere adatta all'utilizzo su Android. È fondamentale che le librerie destinate all'uso su Android non impediscano importanti ottimizzazioni delle app e rispettino ulteriori requisiti di ottimizzazione.
Questa documentazione è rivolta agli sviluppatori di librerie pubblicate, ma potrebbe essere utile anche per gli sviluppatori di moduli di librerie interni in un'app di grandi dimensioni e modulare.
Se sei uno sviluppatore di app e vuoi scoprire di più sull'ottimizzazione della tua app per Android, consulta Attivare l'ottimizzazione delle app. Per scoprire quali librerie sono appropriate da utilizzare, consulta Scegliere le librerie con saggezza.
Comprendere i tipi di regole di conservazione
Esistono due tipi distinti di regole di conservazione che puoi avere nelle raccolte:
- Le regole di conservazione dei consumatori devono specificare le regole che conservano ciò che riflette la raccolta. Se una libreria utilizza la reflection o JNI per chiamare il proprio codice o
il codice definito da un'app client, queste regole devono descrivere il codice da
conservare. Le librerie devono includere le regole di conservazione dei consumatori, che utilizzano lo stesso formato delle regole di conservazione delle app. Queste regole sono raggruppate in artefatti della libreria
(AAR o JAR) e vengono utilizzate automaticamente durante l'ottimizzazione
dell'app per Android quando viene utilizzata la libreria. Queste regole vengono mantenute nel file specificato con la proprietà
consumerProguardFilesnel filebuild.gradle.kts(obuild.gradle). Per saperne di più, vedi Scrivere regole di conservazione per i consumatori. - Le regole di conservazione della build della libreria vengono applicate durante la creazione della libreria. Sono
necessari solo se decidi di ottimizzare parzialmente la libreria in fase di compilazione. Devono impedire la rimozione dell'API pubblica della libreria, altrimenti
l'API pubblica non sarà presente nella distribuzione della libreria, il che significa che gli sviluppatori di app non potranno utilizzare la libreria. Queste regole vengono mantenute nel file
specificato con la proprietà
proguardFilesnel filebuild.gradle.kts(obuild.gradle). Per saperne di più, consulta Ottimizzare la build della libreria AAR.
Requisiti e linee guida per l'ottimizzazione
La configurazione di R8 nelle librerie ha un impatto globale sulle dimensioni e sulle prestazioni del binario finale dell'app di consumo. Oltre alle best practice generali per le regole di conservazione, gli autori delle librerie devono rispettare requisiti specifici e prendere in considerazione linee guida aggiuntive.
Rispettare i requisiti di ottimizzazione
L'inefficienza delle librerie contribuisce in modo significativo all'aumento delle dimensioni delle app, allo spreco di memoria, all'avvio lento e agli errori ANR (L'applicazione non risponde). Le librerie devono evitare di violare i seguenti requisiti per non ridurre significativamente la qualità dell'app e l'esperienza utente.
Nessuna regola di conservazione generale o a livello di pacchetto:la tua libreria non deve includere regole di conservazione generali che conservino la maggior parte del codice nella tua libreria o in un'altra libreria. Regole di conservazione generiche potrebbero risolvere i blocchi nel breve termine, ma aumentano le dimensioni dell'app di tutte le app che utilizzano la tua libreria.
Non includere regole di conservazione a livello di pacchetto (ad esempio
-keep class com.mylibrary.** {*; }) per i pacchetti nella tua libreria o in altre librerie a cui viene fatto riferimento. Queste regole limitano l'ottimizzazione di questi pacchetti in tutte le app che utilizzano la tua libreria.Nessuna regola globale inappropriata: non utilizzare mai opzioni globali come
-dontobfuscateo-allowaccessmodification.Utilizzo di codegen rispetto alla reflection, quando possibile: quando possibile, utilizza la generazione di codice (codegen) rispetto alla reflection. Codegen e reflection sono entrambi approcci comuni per evitare codice boilerplate durante la programmazione, ma codegen è più compatibile con un ottimizzatore di app come R8.
Con la generazione di codice, il codice viene analizzato e modificato durante il processo di build. Poiché non vengono apportate modifiche importanti dopo la compilazione, l'ottimizzatore sa quale codice è necessario e quale può essere rimosso in sicurezza.
Con la reflection, il codice viene analizzato e manipolato in fase di runtime. Poiché il codice non viene finalizzato fino all'esecuzione, l'ottimizzatore non sa quale codice può essere rimosso in sicurezza. Probabilmente rimuoverà il codice utilizzato dinamicamente tramite reflection durante l'esecuzione, causando arresti anomali dell'app per gli utenti.
Molte librerie moderne utilizzano la generazione di codice anziché la reflection. Consulta KSP per un punto di ingresso comune, utilizzato da Room, Dagger2 e molti altri.
Supporto della modalità completa R8:la libreria non deve arrestarsi in modo anomalo quando è attivata la modalità completa R8. La modalità completa di R8 è la modalità consigliata per l'utilizzo di R8 ed è la modalità predefinita a partire da AGP 8.0, che è stata resa stabile nel 2023. Se la tua libreria si arresta in modo anomalo in R8, la soluzione è identificare il punto di ingresso specifico di reflection o JNI e aggiungere una regola mirata, non conservare l'intero pacchetto.
Altri consigli
Oltre ai requisiti di ottimizzazione, di seguito sono riportati ulteriori consigli.
- Non utilizzare
-repackageclassesnel file delle regole di conservazione per i consumatori della tua raccolta. Tuttavia, per ottimizzare la creazione della libreria, puoi utilizzare-repackageclassescon un nome di pacchetto interno, ad esempio<your.library.package>.internal, nel file delle regole di conservazione della build della libreria. In questo modo, l'efficienza della libreria può migliorare nelle app non ottimizzate. Tuttavia, in genere non è necessario, perché anche le app devono essere ottimizzate. - Dichiara tutti gli attributi necessari per il funzionamento della libreria nei file delle regole di conservazione della libreria, anche se potrebbe esserci una sovrapposizione con gli attributi definiti in
proguard-android-optimize.txt. - Se richiedi i seguenti attributi nella distribuzione della libreria,
mantienili nel file delle regole di conservazione della build della libreria e non nel file delle regole di conservazione dei consumatori della libreria:
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
- Gli autori delle librerie devono mantenere l'attributo
RuntimeVisibleAnnotationsnelle regole di conservazione dei consumatori se le annotazioni vengono utilizzate in fase di runtime. - Gli autori delle librerie non devono utilizzare le seguenti opzioni globali nelle regole di conservazione dei consumatori:
-include-basedirectory-injars-outjars-libraryjars-repackageclasses-flattenpackagehierarchy-allowaccessmodification-renamesourcefileattribute-ignorewarnings-addconfigurationdebugging-printconfiguration-printmapping-printusage-printseeds-applymapping-obfuscationdictionary-classobfuscationdictionary-packageobfuscationdictionary
Quando la riflessione è accettabile
Se devi utilizzare la reflection, devi riflettere solo in uno dei seguenti elementi:
- Tipi di target specifici (implementatori o sottoclassi di interfacce specifici)
- Codice che utilizza un'annotazione di runtime specifica
L'utilizzo della reflection in questo modo limita il costo di runtime e consente di scrivere regole di conservazione mirate per i consumatori.
Questa forma di reflection specifica e mirata è un pattern che puoi vedere sia nel framework Android (ad esempio, quando vengono gonfiate attività, visualizzazioni e drawables) sia nelle librerie AndroidX (ad esempio, quando vengono costruiti WorkManager
ListenableWorkers o RoomDatabases). Al contrario, la reflection aperta di Gson non è adatta all'utilizzo nelle app per Android.
Errori comuni
Alcune convinzioni errate comuni potrebbero portarti a configurare R8 in modo errato. Questi includono:
Comprensione errata delle ottimizzazioni di R8: contrariamente a quanto si pensa, le ottimizzazioni di R8 non si limitano all'offuscamento, ma includono anche la riduzione del codice e le ottimizzazioni logiche con tecniche di incorporamento dei metodi e unione delle classi. Per saperne di più, consulta la panoramica dell'ottimizzazione R8.
Ignorare l'ottimizzazione delle librerie offuscate: un errore comune è omettere una libreria dall'ottimizzazione, perché la libreria è stata ottimizzata o offuscata durante la compilazione in un file AAR (Android Archive) o JAR (Java Archive). Le ottimizzazioni durante la creazione della libreria sono limitate e la tua app non deve disattivare l'ottimizzazione della libreria includendola in una regola di conservazione. Per saperne di più, consulta Ottimizzare la build della libreria AAR.
Comprensione errata dell'opzione
-keepLa regola-keepimpedisce a R8 di eseguire uno qualsiasi dei suoi passaggi di ottimizzazione. Per saperne di più, vedi Scegliere l'opzione di conservazione giusta.
Configurare il packaging delle regole
Per garantire che le regole di conservazione dei consumatori vengano applicate correttamente, devi raggrupparle in modo appropriato a seconda del formato della libreria.
Librerie AAR
Per aggiungere regole per i consumatori per una libreria AAR, utilizza l'opzione consumerProguardFiles
nel file di build del modulo della libreria Android. Per saperne di più, consulta le nostre
indicazioni per la creazione di moduli della libreria.
Kotlin
android {
defaultConfig {
consumerProguardFiles("consumer-proguard-rules.pro")
}
...
}
Groovy
android {
defaultConfig {
consumerProguardFiles 'consumer-proguard-rules.pro'
}
...
}
Librerie JAR
Per raggruppare le regole con la libreria Kotlin o Java fornita come JAR, inserisci il file delle regole nella directory META-INF/proguard/ del JAR finale, con qualsiasi nome file.
Ad esempio, se il codice si trova in <libraryroot>/src/main/kotlin, inserisci un file di regole per i consumatori in <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro e le regole verranno raggruppate nella posizione corretta nel file JAR di output.
Verifica che le regole dei bundle JAR finali siano corrette controllando che si trovino
nella directory META-INF/proguard.
Ottimizzare la creazione della libreria AAR (avanzato)
In genere, non è necessario ottimizzare direttamente la creazione di una libreria perché le possibili ottimizzazioni durante la creazione della libreria sono molto limitate. In qualità di sviluppatore di librerie, devi considerare più fasi di ottimizzazione e mantenere il comportamento sia in fase di compilazione della libreria che dell'app prima di ottimizzare la libreria.
Se vuoi comunque ottimizzare la libreria in fase di build, questa operazione è supportata dal plug-in Android Gradle.
Kotlin
android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
configureEach {
consumerProguardFiles("consumer-rules.pro")
}
}
}
Groovy
android {
buildTypes {
release {
minifyEnabled true
proguardFiles
getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
configureEach {
consumerProguardFiles "consumer-rules.pro"
}
}
}
Tieni presente che il comportamento di proguardFiles è molto diverso da quello di
consumerProguardFiles:
proguardFilesvengono utilizzati in fase di compilazione, spesso insieme agetDefaultProguardFile("proguard-android-optimize.txt"), per definire quale parte della libreria deve essere conservata durante la compilazione. Come minimo, si tratta della tua API pubblica.consumerProguardFiles, al contrario, vengono inseriti nella libreria per influire sulle ottimizzazioni successive, durante la build di un'app che utilizza la tua libreria.
Ad esempio, se la tua libreria utilizza la reflection per costruire classi interne, potresti dover definire le regole di conservazione sia in proguardFiles che in consumerProguardFiles.
Se utilizzi -repackageclasses nella build della tua libreria, ricomponi i pacchetti delle classi in un
sottopacchetto all'interno del pacchetto della tua libreria. Ad esempio, utilizza -repackageclasses
'com.example.mylibrary.internal' invece di -repackageclasses 'internal'.
Supportare versioni diverse di R8 (avanzato)
Puoi personalizzare le regole in modo che abbiano come target versioni specifiche di R8. In questo modo, la tua libreria funziona in modo ottimale nei progetti che utilizzano versioni R8 più recenti, consentendo l'utilizzo delle regole esistenti nei progetti con versioni R8 precedenti.
Per specificare le regole R8 mirate, devi includerle nella directory META-INF/com.android.tools all'interno di classes.jar di un AAR o nella directory META-INF/com.android.tools di un JAR.
In an AAR library:
proguard.txt (legacy location, the file name must be "proguard.txt")
classes.jar
└── META-INF
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
In a JAR library:
META-INF
├── proguard/<ProGuard-rule-files> (legacy location)
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
Nella directory META-INF/com.android.tools possono essere presenti più sottodirectory con nomi nel formato r8-from-<X>-upto-<Y> per indicare per quali versioni di R8 sono scritte le regole. Ogni sottodirectory può contenere uno o più file contenenti le regole R8, con qualsiasi nome ed estensione.
Tieni presente che le parti -from-<X> e -upto-<Y> sono facoltative, la versione <Y>
è esclusiva e gli intervalli di versioni sono in genere continui, ma possono anche
sovrapporsi.
Ad esempio, r8, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0 e
r8-from-8.2.0 sono nomi di directory che rappresentano un insieme di regole R8 mirate. Le regole
nella directory r8 possono essere utilizzate da qualsiasi versione R8. Le regole nella directory r8-from-8.0.0-upto-8.2.0 possono essere utilizzate da R8 dalla versione 8.0.0 fino alla versione 8.2.0, esclusa.
Il plug-in Android Gradle utilizza queste informazioni per selezionare tutte le regole che possono essere utilizzate dalla versione attuale di R8. Se una libreria non specifica regole R8 mirate, il plug-in Android Gradle selezionerà le regole dalle posizioni legacy (proguard.txt per un AAR o META-INF/proguard/<ProGuard-rule-files> per un JAR).