Meny
R8 Optimized Resource Shrinking i AGP 9: Når statisk analyse møter dynamisk tilgang

R8 Optimized Resource Shrinking i AGP 9: Når statisk analyse møter dynamisk tilgang

av Nikolay Vlasov

Overgangen til Android Gradle Plugin (AGP) 9.0 innebar et betydelig skifte i hvordan ressursforminskning (resource shrinking) fungerer. Når isShrinkResources = true er aktivert i din build-type, bruker AGP nå optimized resource shrinking. Denne mekanismen kobler ressursanalyse tett sammen med kodeanalyse, og fjerner alle ressurser som bare refereres til av ubrukt kode. Selv om dette fører til mindre APK-filer, kan det også forårsake problemer i prosjekter som er avhengige av dynamisk ressurstilgang – problemer som kan ha gått upåaktet hen i tidligere versjoner. (Android Developers)

Hvorfor forsvinner ressurser i Dynamic Feature Modules?

Moderne ressursforminskere (resource shrinkers) støtter seg tungt på statisk analyse. Hvis en ressurs refereres direkte i koden din (f.eks. R.raw.my_resource), kan forminskeren enkelt spore bruken. Men når du får tilgang til ressurser dynamisk ved hjelp av strengnavn og oppslag ved kjøretid (runtime lookup), kan ikke statisk analyse lenger bevise pålitelig at ressursen er nødvendig. Med AGP 9 blir dette enda tydeligere, ettersom optimized resource shrinking nå er en del av den forente optimaliseringsflyten for kode og ressurser. (Android Developers)

I prosjekter som bruker Dynamic Feature Modules (DFM), mangler ofte basismodulen compile-time-avhengigheter til funksjonens R-klasser, spesielt når moduler leveres separat via Play Feature Delivery. Selv om den nye ressursforminskeren har støttet dynamiske funksjoner en god stund, er det nettopp i denne arkitekturen at problemer med "manglende ressurser" forekommer hyppigst på grunn av dynamiske oppslag. (Android Developers)

Ta for eksempel et utdrag fra prosjektet Smart Directory Agent Center. Lasting av en fulltekstsøksindeks er implementert slik:

private fun getFtsId(): Int {
    return dynamicContextWrapper.getContextForFts().resources.getIdentifier(
        "fts",
        "raw",
        featuresManager.ftsModulePackage,
    )
}

For ressursforminskeren er dette kallet en "svart boks": ressursnavnet sendes som en streng i stedet for via en statisk referanse. Som et resultat kan byggeverktøyene feilaktig anta at ressursen er ubrukt og fjerne den under release-build, noe som fører til en Resources$NotFoundException ved kjøring. (Android Developers)

R8 Optimized Resource Shrinking
Optimalisert ressursforminskning kobler kodeanalyse tett sammen med tilgjengelige ressurser.

Evolusjonen av ressursforminskning i AGP

Det er viktig å forstå at AGP 9.0 ikke fant opp ressursforminskning på nytt fra bunnen av; i stedet gjorde den oppførselen mer streng og forutsigbar innenfor den optimaliserte flyten. Precise resource shrinking var allerede aktivert som standard i AGP 8.3. I AGP 9.0 utløses optimized resource shrinking automatisk når isShrinkResources = true. For versjoner mellom 8.6 og 9.0 krevde denne oppførselen vanligvis det eksplisitte flagget android.r8.optimizedResourceShrinking=true. (Android Developers)

Vær oppmerksom på regresjonen i AGP 8.9: release notes dokumenterte en feil i ressursforminskning som førte til at ressurser forsvant i dynamiske funksjonsmoduler. Dette ble senere rettet i versjonene 8.9.2 and 8.10. Hvis du opplever at appen krasjer i en release-build, trenger det ikke være en arkitektonisk feil – det kan være en bug i en spesifikk versjon av forminskeren. (Android Developers)

Den reelle løsningen: keep-filer og eksplisitt ressursbevaring

Den riktige måten å beskytte ressurser som brukes indirekte, er å definere en keep-fil i prosjektets ressurser. Dokumentasjonen anbefaler å opprette en XML-fil i res/raw/ (f.eks. res/raw/my.package.keep.xml) i stedet for å stole på en generisk "global keep.xml" som en spesiell mekanisme. Bruk av et unikt filnavn for keep-filen forhindrer konflikter under ressursfletting (merging) i multimodulprosjekter. (Android Developers)

I vårt tilfelle så rettelsen slik ut:

<!-- Sti: app/src/main/res/raw/com.example.app.keep.xml -->
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@style/Theme.StartingScreen_*,@style/AppTheme_*,@raw/fts" />

Denne tilnærmingen gjør din intensjon eksplisitt: @raw/fts-ressursen må ikke fjernes, selv om den bare er tilgjengelig via dynamisk navneoppslag. Dette er kritisk for alle ressurser som leses via Resources.getIdentifier(). (Android Developers)

Best practices for AGP 9 og R8

For det første: For ressurser man har tilgang til dynamisk, er det best å føre dem opp eksplisitt i tools:keep i stedet for å stole på brede jokertegn som @raw/*. Jo mer spesifikke reglene dine er, desto mindre er risikoen for å beholde unødvendige ressurser, og desto mer forutsigbar blir byggeoppførselen. (Android Developers)

For det andre: Husk på shrinkMode. Som standard brukes safe mode, som forsøker å bevare ressurser som kan bli brukt dynamisk via getIdentifier(). Hvis du bytter til tools:shrinkMode="strict", beholdes bare ressurser det refereres eksplisitt til, noe som gjør nøyaktigheten til keep-reglene dine helt kritisk. (Android Developers)

For det tredje: Ikke forveksle årsaken med symptomet. Problemet er ikke at prosjektet ditt bruker DFM; det er at noen ressurser bare er tilgjengelige via runtime-oppslag og forblir usynlige for statisk analyse. DFM gjør rett og slett dette scenariet mer vanlig og mer tydelig under optimalisering. (Android Developers)

Oppsummering

Manglende ressurser i AGP 9 er ikke et resultat av magi eller tilfeldige feil. De er en logisk konsekvens av strengere statisk analyse i optimized resource shrinking. Dersom din arkitektur baserer seg på dynamisk ressurstilgang, må du eksplisitt beskytte disse ressursene ved hjelp av keep-filer og verifisere oppførselen mot din valgte forminskermodus. Hvis krasj oppstår spesifikt etter oppdatering til AGP 8.9–9.0, dobbeltsjekk plugin-versjonen din, ettersom kjente regresjoner på dette området allerede er adressert i påfølgende oppdateringer. (Android Developers)

Nyttige ressurser