Menu
R8 Optimized Resource Shrinking dans AGP 9 : quand l'analyse statique rencontre l'accès dynamique

R8 Optimized Resource Shrinking dans AGP 9 : quand l'analyse statique rencontre l'accès dynamique

par Nikolay Vlasov

Le passage à Android Gradle Plugin (AGP) 9.0 a introduit un changement significatif dans le fonctionnement de la réduction des ressources (resource shrinking). Lorsque isShrinkResources = true est activé dans votre type de build, AGP utilise désormais l'Optimized Resource Shrinking. Ce mécanisme couple étroitement l'analyse des ressources avec l'analyse du code, supprimant toutes les ressources référencées uniquement par du code inutilisé. Si cela permet de réduire la taille des APK, cela peut aussi causer des problèmes dans les projets s'appuyant sur un accès dynamique aux ressources — des problèmes qui auraient pu passer inaperçus dans les versions précédentes. (Android Developers)

Pourquoi les ressources disparaissent-elles dans les Dynamic Feature Modules ?

Les réducteurs de ressources modernes s'appuient fortement sur l'analyse statique. Si une ressource est référencée directement dans votre code (par exemple, R.raw.my_resource), le réducteur peut facilement suivre son utilisation. Cependant, lorsque vous accédez aux ressources de manière dynamique via des noms de chaînes et des recherches à l'exécution (runtime lookups), l'analyse statique ne peut plus prouver de manière fiable que la ressource est nécessaire. Avec AGP 9, cela devient encore plus prononcé car l'Optimized Resource Shrinking fait désormais partie du pipeline unifié d'optimisation du code et des ressources. (Android Developers)

Dans les projets utilisant des Dynamic Feature Modules (DFM), le module de base manque souvent de dépendances à la compilation sur les classes R de la fonctionnalité, en particulier lorsque les modules sont livrés à la demande via Play Feature Delivery. Bien que le nouveau réducteur de ressources supporte les fonctionnalités dynamiques depuis un certain temps, c'est précisément dans cette architecture que les problèmes de « ressources manquantes » surviennent le plus fréquemment en raison des recherches dynamiques. (Android Developers)

Prenons par exemple un extrait du projet Smart Directory Agent Center. Le chargement d'un index de recherche plein texte est implémenté ainsi :

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

Pour le réducteur de ressources, cet appel est une « boîte noire » : le nom de la ressource est passé sous forme de chaîne plutôt que par une référence statique. Par conséquent, les outils de build pourraient supposer à tort que la ressource est inutilisée et la supprimer lors du build de l'application, ce qui provoquerait une exception Resources$NotFoundException à l'exécution. (Android Developers)

R8 Optimized Resource Shrinking
La réduction optimisée des ressources couple étroitement l'analyse du code avec les ressources disponibles.

L'évolution du Resource Shrinking dans AGP

Il est important de noter qu'AGP 9.0 n'a pas réinventé la réduction des ressources de zéro ; il a plutôt rendu son comportement plus rigoureux et prévisible au sein du pipeline optimisé. Le Precise Resource Shrinking était déjà activé par défaut dans AGP 8.3. Dans AGP 9.0, l'Optimized Resource Shrinking est automatiquement déclenché lorsque isShrinkResources = true. Pour les versions comprises entre 8.6 et 9.0, ce comportement nécessitait généralement l'activation explicite du flag android.r8.optimizedResourceShrinking=true. (Android Developers)

Méfiez-vous de la régression apparue dans AGP 8.9 : les notes de version ont documenté un bug de réduction des ressources provoquant la disparition de ressources dans les modules de fonctionnalités dynamiques. Ce bug a été corrigé ultérieurement dans les versions 8.9.2 et 8.10. Si vous rencontrez un crash dans votre build final, il ne s'agit peut-être pas d'un défaut architectural — cela pourrait être un bug dans une version spécifique du réducteur. (Android Developers)

La vraie solution : fichiers keep et rétention explicite des ressources

La bonne façon de protéger les ressources utilisées indirectement est de définir un fichier keep dans les ressources de votre projet. La documentation recommande de créer un fichier XML dans res/raw/ (par exemple, res/raw/my.package.keep.xml) plutôt que de compter sur un « keep.xml global » comme mécanisme spécial. L'utilisation d'un nom de fichier unique pour le fichier keep évite les conflits lors de la fusion des ressources dans les projets multi-modules. (Android Developers)

Dans notre cas, la correction s'est présentée ainsi :

<!-- Chemin : 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" />

Cette approche rend votre intention explicite : la ressource @raw/fts ne doit pas être supprimée, même si elle n'est accessible que via la résolution dynamique de nom. C'est crucial pour toutes les ressources lues via Resources.getIdentifier(). (Android Developers)

Bonnes pratiques pour AGP 9 et R8

Premièrement, pour les ressources consultées dynamiquement, il est préférable de les lister explicitement dans tools:keep plutôt que de se fier à des jokers larges comme @raw/*. Plus vos règles sont spécifiques, moins vous risquez de conserver des ressources inutiles, et plus le comportement de votre build sera prévisible. (Android Developers)

Deuxièmement, gardez à l'esprit le shrinkMode. Par défaut, le safe mode est utilisé, lequel tente de préserver les ressources susceptibles d'être utilisées dynamiquement via getIdentifier(). Si vous passez en tools:shrinkMode="strict", seules les ressources explicitement référencées sont conservées, ce qui rend la précision de vos règles de conservation absolument critique. (Android Developers)

Troisièmement, ne confondez pas la cause et le symptôme. Le problème n'est pas que votre projet utilise des DFM ; c'est que certaines ressources ne sont accessibles que via des recherches à l'exécution et restent invisibles pour l'analyse statique. Les DFM rendent simplement ce scénario plus courant et plus apparent lors de l'optimisation. (Android Developers)

Résumé

Les ressources manquantes dans AGP 9 ne sont pas le fruit de la magie ou d'une erreur aléatoire. Elles sont la conséquence logique d'une analyse statique plus stricte dans l'Optimized Resource Shrinking. Si votre architecture repose sur un accès dynamique aux ressources, vous devez explicitement protéger ces ressources à l'aide de fichiers keep et vérifier le comportement par rapport au mode de réduction choisi. Si des plantages apparaissent spécifiquement après la mise à jour vers AGP 8.9-9.0, vérifiez la version de votre plugin, car des régressions connues dans ce domaine ont déjà été corrigées dans des correctifs ultérieurs. (Android Developers)

Ressources utiles