
/ PxHere / PD
L'optimisation du poids de l'APK est une tâche non triviale, mais très pertinente à l'époque de l'application instantanée. L'activation de proguard vous épargnera du code inutile si vos dépendances peuvent être déterminées au stade de la compilation, mais il existe plusieurs autres types de fichiers dans l'APK qui peuvent être exclus de l'assembly.
Sous le chat sur la façon de créer des dépendances - défini au stade de la compilation, quels fichiers peuvent être exclus de l'assembly et comment le faire, ainsi que, nous analyserons comment exclure les composants inutilisés de l'assembly si vous avez plusieurs applications avec une base de code commune.
Avant de lire
- Avant d'appliquer les conseils de l'article, optimisez le guide APK pour Google . Cet article est destiné à ceux qui n'ont pas suffisamment d'optimisations standard.
- Par "proguard", je veux dire un compilateur d'optimisation avec minification.
- Par composant, j'entends une certaine caractéristique du produit d'un point de vue commercial. Dans notre cas, il s'agit simplement d'une collection de fichiers dans un certain package. Nous avons un module gradle pour toute l'application.
Le poids de notre APK optimisé par Google était de 4.4
Fichiers supplémentaires
Commençons par un simple. Si vous n'utilisez pas kotlin-reflect , vous pouvez exclure les méta-informations sur les classes kotlin de l'assembly. Vous pouvez le faire comme suit:
Dans build.gradle (Module: app)
android { packagingOptions { exclude("META-INF/*.kotlin_module") exclude("**.kotlin_builtins") exclude("**.kotlin_metadata") } }
La réflexion Java n'a pas besoin des *.kotlin_module
, *.kotlin_builtins
et *.kotlin_metadata
. Déterminer quelle réflexion vous utilisez est très simple. Si vous écrivez obj::class.<method>
, alors vous utilisez la réflexion kotlin, si obj::class.java.<method>
, puis la réflexion java.
Le résultat de l'optimisation pour nous: -602,1 ko
Dépendances
Les bibliothèques ajoutent parfois des dépendances pour les cas qui ne se produisent jamais dans votre application. Par exemple, ktor-client tire avec kotlin-reflect (0,5 Mo!).
J'ai lutté avec de tels cas comme suit: j'ai collecté l'APK avec minifyEnabled = true
, je l' minifyEnabled = true
jeté dans l'analyseur Android Studio, téléchargé mapping.txt
et recherché des packages qui, en théorie, ne devraient pas être présents dans l'assemblage. Par exemple, kotlin.reflect
. Après avoir ./gradlew app:dependencies
dans le dossier du projet pour rechercher les dépendances (n'oubliez pas d'augmenter la longueur de l'historique dans le terminal. L'arbre des dépendances peut être volumineux!). À partir de cet arbre, il est facile de comprendre ce qui fait référence aux dépendances inutiles et de les exclure. Dans build.gradle
votre module:
dependencies { implementation("io.ktor:ktor-client-core:$ktorVersion") { exclude(group: "org.jetbrains.kotlin", module: "kotlin-reflect") } implementation("io.ktor:ktor-client-okhttp:$ktorVersion") { exclude(group: "org.jetbrains.kotlin", module: "kotlin-reflect") } }
Ce code supprime la dépendance de la bibliothèque ktor-client sur kotlin-reflect . Si vous souhaitez exclure autre chose, remplacez vos valeurs.
!!! Utilisez ces conseils très attentivement! Avant d'éliminer les dépendances, assurez-vous que vous n'en avez pas besoin. Si vous ne le faites pas, alors l'application peut commencer à tomber en production !!!
Le résultat de l'optimisation pour nous: -500,3 ko
Validez votre XML
Malheureusement, proguard ne supprime pas les fichiers de balisage XML supplémentaires du dossier de présentation. Le XML non utilisé peut utiliser des widgets "lourds" et proguard ne pourra pas non plus les exclure de l'assemblage! Pour éviter cela, supprimez les ressources inutilisées avec Refactor -> Remove unused resources...
Vérifiez votre di
Si vous, comme nous, utilisez DI d'exécution, vérifiez si vous avez des fournisseurs pour les dépendances que vous n'utilisez pas. Proguard ne peut pas les exclure de l'assembly car ils ne sont pas inutilisés du point de vue du compilateur. Vous les utilisez lors de la création d'un graphe de dépendances.
Exclure les dépendances de débogage des versions de version
Les outils de débogage peuvent prendre beaucoup de place de façon inattendue. Par exemple, le stetho
pèse environ 0.2
après compression! Dans tous les cas, il est préférable d'exclure l'intégralité de l'infrastructure de débogage de la version afin que personne ne puisse en savoir trop sur votre application en la téléchargeant simplement sur Google Play.
Vous pouvez créer différentes versions des mêmes fichiers pour le débogage et la publication. Pour ce faire, dans le dossier src
, à côté de main
, créez les dossiers de debug
et de release
. Vous pouvez maintenant écrire la fonction initStetho
qui initialise Stetho dans le fichier src/debug/java/your/pkg/Stetho.kt
et la fonction initStetho
qui ne fait rien dans le src/debug/java/your/pkg/Stetho.kt
src/release/java/your/pkg/Stetho.kt
.
Au cas où, assurez-vous que cette dépendance est incluse uniquement dans les versions de débogage. Vous pouvez le faire en remplaçant l' implementation
par debugImplementation
dans build.gradle
. Plus souvent qu'autrement, proguard élimine les fichiers inutiles même sans cette étape, mais pas toujours. La réponse à la question "pourquoi?" ci-dessous dans le texte de l'article .
Parfois, sur la même base de code, plusieurs versions différentes d'une application sont émises. Il peut s'agir de versions différentes pour différents pays ou régions, ou, comme dans notre cas, pour différents clients. Vous trouverez ci-dessous des conseils sur la façon de décharger la plate-forme.

/ PxHere / PD
Notre expérience
Nous développons un concepteur d'applications mobiles E-SHOP . Nous avons plusieurs dizaines de clients et chacun a son propre ensemble de composants. Certains composants sont utilisés par tous les clients, certains ne sont qu'une partie. Notre tâche consiste à inclure dans l'assemblage client uniquement les composants dont il a besoin.
Exception de drapeau
Pour chaque client, nous créons une saveur de produit distincte. C'est pratique car il est facile de créer différentes ressources pour différents clients, l'IDE fournit une interface graphique pour basculer entre les saveurs et les caches fonctionnent bien. Et vous pouvez également générer votre propre BuildConfig.java
pour chaque client. Les valeurs de champ de cette classe sont connues au stade de la compilation. Voilà ce dont nous avons besoin! Créez un champ de type boolean
pour chaque composant.
android { productFlavors { client1 { buildConfigField("boolean", "IS_CATALOG_ENABLED", "true") } client2 { buildConfigField("boolean", "IS_CATALOG_ENABLED", "false") } } }
Il s'agit d'une version simplifiée de la configuration. Le présent est complexe en raison de l'intégration avec notre CI.
On sait maintenant si le composant est actif au stade de la compilation, et proguard peut l'exclure de l'assemblage!
XML Ă nouveau
Maintenant, le problème avec les dispositions XML inutilisées prend une nouvelle dimension! Vous ne pouvez pas simplement prendre et supprimer le balisage d'un composant simplement parce que certains clients n'en ont pas besoin.
Dans notre application XML de l'un des composants rarement utilisés, nous avons utilisé un widget qui faisait référence à la bibliothèque de reconnaissance d'images firebase.ml.vision
. Il pèse environ 0,2 Mo, ce qui est beaucoup. Il a été décidé d'ajouter ce widget avec du code au lieu de le déclarer dans le balisage. Après cela, proguard a pu exclure la vision
de l'assemblage pour les clients qui n'en avaient pas besoin.
Le résultat de l'optimisation pour nous: -222,3 ko pour l'APK moyen
@Keep
Il y a 2 façons de dire à proguard que votre classe ne peut pas être minifiée: écrivez une règle dans le fichier proguard-rules.pro
ou mettez l'annotation @Keep
. Dans la bibliothèque play-services-vision
, cette annotation se trouve sur la classe racine. Par conséquent, 0,2 mb se bloque, même dans les applications clientes qui n'ont pas besoin de reconnaissance d'image.
Je n'ai pas trouvé de moyen simple et sûr de supprimer cette annotation. Si vous savez comment - veuillez écrire dans les commentaires.
Heureusement, la bibliothèque firebase.ml.vision
, qui est une version plus récente de play-services-vision
, n'utilise pas cette annotation, et nous avons résolu le problème en y accédant.
Et encore DI
Dernier point mais non le moindre. DI pour les composants déconnectés. Tout est simple ici: pour chaque composant, nous utilisons notre propre conteneur, et nous connectons les dépendances générales via un module séparé.
Le résultat d'optimisation pour nous: -20,1 ko pour l'APK moyen
Conclusions
- Le poids de l'APK moyen est passé de
4.4
Ă 3.1
, et le minimum - Ă 2.5
! - Le code d'application n'a pas été endommagé, mais amélioré. DI est désormais plus facile à utiliser
Toutes les optimisations présentées dans l'article sont des «fruits à faible pendaison». Ils sont assez faciles à mettre en œuvre et obtiennent rapidement le résultat. Jusqu'à -43% pour un APK déjà optimisé dans notre cas. J'espère avoir économisé votre temps en répertoriant tout au même endroit.
Merci Ă tous!