Une API en constante évolution
-
Évolution rapide de l'API Android avec modifications réalisées entre différentes versions de l'API
- Pour essayer de faciliter l'usage de fonctionnalités existantes
- Pour enrichir des aspects de l'API en introduisant de nouvelles possibilités
- Pour supporter de nouveaux matériels (NFC depuis API 10, BLE depuis API 18...)
Ajout de nouvelles fonctionnalités :
- par de nouvelles méthodes sur des classes existantes
- par de nouvelles classes
Modification d'usage de fonctionnalités existantes :
- par le renommage de méthodes ou classes (homogénisation)
- par la création de nouvelles classes
Les anciennes méthodes et/ou classes sont notées deprecated dans l'API.
Compatibilité du code avec des versions plus récentes de l'API
Normalement du code écrit pour une version N de l'API doit fonctionner sur un appareil doté d'une API de version > N sans modification (compatibilité ascendante).
Les méthodes et classes dépréciées restent utilisables.
Le comportement de certaines fonctionnalités peut évoluer au cours des versions. Par exemple certains thèmes par défaut peuvent être introduits par de nouvelles versions de l'API : l'application de ces thèmes sur des applications conçues pour des versions plus anciennes de l'API pourrait poser certains problèmes. A l'exécution, la propriété targetSdkVersion indiquée dans le manifeste est consultée : si celle-ci est inférieure à la version actuelle de l'API, certains comportements de rétro-compatibilité peuvent être adaptés (comme la non-adoption de nouveaux thèmes graphiques).
⚠ Il est important d'indiquer comme targetSdkVersion la version la plus élevée sur laquelle l'application a été testée (et pas la version minimale supportée).
Il est possible d'interdire l'installation de l'application sur un appareil utilisant une version de l'API trop élevée avec la propriété maxSdkVersion pour indiquer la version maximale de l'API supportée. L'utilisation de cette propriété est plutôt à éviter dans le cas général car il est souhaitable d'assurer une compatibilité avec les futures versions de l'API. Cela pourrait néanmoins être utile si l'on compile différentes versions d'une application, chacune destinée à des versions spécifiques (utilisation de saveurs de version).
Compatibilité du code avec des versions plus anciennes de l'API
Du code écrit pour une version N de l'API ne fonctionnera sur un appareil doté d'une version < N de l'API si on utilise des classes ou méthodes nouvellement introduites.
Comment utiliser de nouvelles fonctionnalités de l'API tout en garantissant une compatibilité descendante ?
- En utilisant si elle existe une bibliothèque de rétro-portage implantant la fonctionnalité
- En testant le niveau d'API de l'appareil exécutant l'application ou l'introspection pour utiliser la fonctionnalité uniquement si elle existe dans l'API
Comment gérer l'absence d'une fonctionnalité sur une version inférieure ?
- En utilisant une autre fonctionnalité de l'API réalisant le travail souhaité (ou en le faisant manuellement : peut nécessiter beaucoup de code).
- En fonctionnant en mode degradé sans utiliser la fonctionnalité si celle-ci n'est pas indispensable à l'exécution correcte de l'application
On indique la version minimale de l'API supportée avec la propriété minSdkVersion dans le fichier build.gradle pour la génération du manifeste final. Un IDE ou le programme de vérification lint signale les éventuels appels à des fonctionnalités non supportées par la minSdkVersion ; les laisser expose à des exceptions lors de l'exécution sur des appareils de niveau d'API trop faible.
Une application ne peut être installée sur un appareil utilisant une API inférieure au minSdkVersion déclaré.
Rétro-portage de pans récents de l'API
Il existe des bibliothèques rétroportant des fonctionnalités nouvelles de l'API sur des appareils plus anciens.
-
Avantage :
- possibilité d'utiliser un minSdkVersion plus faible et être compatible avec plus de matériels
-
Inconvénients :
-
les classes peuvent être nommées différemment et/ou présentes dans d'autres paquetages que les versions de l'API récente
- nécessaire pour distinguer les classes systèmes des classes rétroportées sur les appareils récents
-
nécessite l'incorporation dans l'APK des classes de rétroportage ; alourdit l'APK
- possibilité d'utiliser ProGuard pour n'incorporer que les classes et méthodes réellement utilisées par l'application
-
les classes peuvent être nommées différemment et/ou présentes dans d'autres paquetages que les versions de l'API récente
L'utilisation des bibliothèques de rétro-portage est plus ou moins transparente avec un IDE Android (en fonction du niveau minimum d'API supporté indiqué). Les bibliothèques sont automatiquement embarquées.
Les bibliothèques officielles rétroportées sont regroupées dans le projet AndroidX (paquetage androidx).
Test du niveau d'API
Le niveau d'API actuel de l'appareil peut être obtenu avec le champ statique Build.VERSION.SDK_INT. On peut alors choisir de n'exécuter certaines portions de code que si le niveau actuel d'API dépasse une certaine valeur (et implanter éventuellement une solution de repli si ce n'est pas le cas).
Si l'on teste correctement le niveau d'API avant de réaliser les appels nécessitant une API supérieure au minSdkVersion, les avertissements de lint deviennent inutiles : on peut alors les supprimer avec l'annotation @TargetAPI(apiLevel) (en remplaçant apiLevel par le niveau maximum d'API géré par la méthode).
Introspection
L'introspection permet de vérifier dynamiquement à l'exécution si certaines classes ou méthodes sont disponibles. On peut charger dynamiquement une classe avec la méthode statique Class.forName(String className) qui lèvera une exception si la classe n'est pas disponible. On peut ensuite instantier la classe ou récupérer une instance singleton, appeler Method getMethod(String name) ou Field getField(String name) pour obtenir une méthode ou un champ... Il faut toujours prévoir la possibilité de l'échec de l'introspection sur des niveaux inférieurs de l'API en capturant correctement les exceptions levées.
L'introspection est assez flexible mais présente l'inconvénient de devoir écrire un code assez verbeux et peu élégant. Son usage doit donc être parcimonieux.
Design du code pour la gestion de différents niveaux d'API
Si l'on est amené à appeler des fonctionnalités sur des versions d'API supérieures au minSdkVersion par test du niveau d'API ou introspection, il est recommandé de créer des classes spécifiques dédiées à ce travail afin de bien séparer cette problématique du reste du code. Idéalement on pourra écrire une bibliothèque distincte du module principal du projet.