L'un des principaux défis du développement Android est la fragmentation. Presque tous les fabricants adaptent Android à leurs besoins. Le développeur Andrey Makeev a répertorié les différences entre les implémentations des fournisseurs et le projet Open Source Android d'origine. À partir du rapport, vous pouvez apprendre comment bénéficier des fonctionnalités individuelles du firmware sur différents appareils.
- Je programme depuis l'école, je le développe pour Android depuis trois ans. Parmi ceux-ci, j'ai passé un an à Yandex, participé à des projets tels que Launcher et Phone.

Je veux parler de la fragmentation de l'API des appareils Android - de l'extérieur, du côté des développeurs d'applications et de l'intérieur, du point de vue des développeurs de la plate-forme et des téléphones.
Mon rapport se compose de deux parties. Tout d'abord, parlons de la façon dont l'API est fragmentée en externe. Ensuite, nous passerons en revue le code - nous découvrirons comment la caractéristique unique du téléphone abstrait est réalisée, comment le développement est construit.
La fragmentation de l'API est l'un des paramètres par lesquels vous pouvez fragmenter un périphérique. Le plus évident et le plus simple est la fragmentation selon le SDK Android, on la rencontre tous les jours, littéralement dès les premiers jours de développement pour Android. Nous savons quoi et dans quelle version de l'API elle est apparue, qu'elle a été supprimée, qu'elle a été sauvegardée, mais qu'elle est toujours disponible et qui a déjà disparu. En règle générale, nous utilisons diverses bibliothèques d'assistance de Google pour simplifier nos vies. Beaucoup a déjà été fait pour nous.


Dans notre code, cela ressemble à ceci: nous activons certaines fonctionnalités, désactivons certaines - selon la version du SDK dans laquelle nous sommes actuellement. Si vous en utilisez, ils font généralement la même chose, mais à l'intérieur.
Nous ne nous concentrerons pas sur ce type de fragmentation. Les inconvénients sont connus de tous - nous sommes obligés de conserver toute une flotte d'appareils avec différentes versions afin de tester au moins notre application. De plus, nous sommes obligés d'écrire du code supplémentaire. Cela est particulièrement gênant lorsque vous venez de commencer à développer pour Android et cela se révèle soudainement: vous devez savoir ce qui était là il y a deux ou trois ans afin de prendre en charge certains anciens appareils. Aspects positifs: l'API se développe, la technologie progresse, Android gagne de nouveaux utilisateurs, elle devient plus pratique pour les développeurs et les utilisateurs.
Comment travaillons-nous avec cela? Nous utilisons également des bibliothèques et sommes très heureux lorsque nous refusons de prendre en charge les anciennes versions. Je pense que dans la vie de tous ceux qui font cela depuis plus d'un an, il y a eu un tel moment. C'est juste du bonheur. Il s'agit d'une option de fragmentation évidente et simple. Ensuite, la fragmentation de type Android. Il y en a plusieurs. Android TV parle de lui-même, Android Auto est principalement destiné aux autoradios, Android Things - pour l'IoT et les appareils embarqués similaires. W est Wear OS, l'ancienne montre Android, regardez pour Android. Nous allons essayer de voir à quoi cela ressemble du côté du développeur. Et, plus intéressant, essayons de voir à quoi cela ressemble de l'intérieur.

Prenez deux exemples de developer.android.com. Le premier est Wear OS. De quoi avons-nous besoin pour faire une demande? Nous ajoutons une dépendance compileOnly à build.gradle et écrivons deux lignes supplémentaires dans le manifeste: uses-feature android.hardware.type.watch et uses-library, qui correspond au même nom de package que la bibliothèque que nous avons connectée.

Comment mettons-nous en œuvre quelque chose? Nous créons une activité, seulement dans ce cas, nous ne dépensons pas une activité standard avec laquelle nous sommes habitués à travailler, et pas même une activité composite, mais WearableActivity, et appelons des méthodes qui lui sont spécifiques, dans ce cas, setAmbientEnabled (). Donc, nous avons une dépendance compileOnly, c'est-à-dire qu'elle n'entre pas dans le cours de notre application. Uses-library, qui, apparemment, oblige le système d'exploitation à nous connecter ces classes et ce code lors de l'exécution sur le périphérique, et les nouvelles classes que nous utilisons.

L'API Android Things n'est pratiquement pas différente. Nous ne prescrivons pas la fonction uses, seulement la bibliothèque uses, la dépendance compileOnly.

Nous créons une activité, dans ce cas, elle est unique à l'API Android Things, la classe PeripheralManager. Nous sondons le GPIO et essayons de nous engager.

Comment une telle application se comportera-t-elle sur votre téléphone? Il y a deux options.

Si nous avons indiqué que uses-library android: required = "true", nous n'avons pas rempli les exigences obligatoires du PackageManager pour installer l'application, et il refuse essentiellement de l'installer. Si nous avons spécifié android: required = ”false”, l'application sera installée, mais lorsque nous essayons d'accéder à la classe PeripheralManager, nous obtenons NoClassDefFoundError, car il n'y a pas une telle classe dans Android standard.
Quelles en sont les conclusions? Nous connectons la dépendance compileOnly uniquement afin de la contacter lors de l'assemblage, et les classes que nous utilisons nous attendent sur le périphérique, elles sont connectées à l'aide de certaines lignes du manifeste. Parfois, nous prescrivons une fonctionnalité qui est plus souvent nécessaire pour distinguer sur Google Play un appareil auquel cette application peut ou ne peut pas être distribuée.
Je ne pouvais pas distinguer les côtés négatifs de ce type de fragmentation. Seuls ceux qui ont développé raconteront de nombreuses histoires sur la façon dont ils ont rencontré des bugs inconnus complètement incompréhensibles qu'ils n'ont pas rencontrés au téléphone. Le côté positif est que ce sont des marchés supplémentaires, des utilisateurs supplémentaires, une expérience supplémentaire, c'est toujours bon.
Comment travailler avec? Écrivez plus d'applications. La recommandation générale est d'écrire plus d'une version de l'application pour tous les types d'Android, mais toujours d'en faire différentes. Il devrait y en avoir moins pour une montre, pour Android Things, pratiquement rien de ce qui est écrit sur le téléphone ne convient, etc. Et utilisez les bibliothèques que les développeurs Android et, parfois, les développeurs d'appareils nous fournissent.
Le type de fragmentation suivant le moins étudié est la fragmentation des producteurs. Chaque fabricant, après avoir reçu le code source - dans de rares cas c'est AOSP, le plus souvent il est en quelque sorte modifié par les développeurs de matériel - y apporte des modifications. En règle générale, nous ne découvrons les effets négatifs de ce type de fragmentation que sur les meilleurs canaux - à partir de critiques négatives sur Google Play, car quelqu'un a cassé quelque chose. Ou nous apprenons cela de l'analyse des plantages, lorsque soudainement quelque chose plante de façon incompréhensible, uniquement sur certains appareils spécifiques. Dans le meilleur des cas, nous apprenons cela de notre QA, quand quelque chose s'est cassé pendant leurs tests sur un appareil spécifique.

Sur ce sujet, j'ai une merveilleuse histoire de notre lanceur de développement. Nous avons reçu un rapport de bogue où l'activité ne s'étendait pas en plein écran et notre fond d'écran par défaut préféré n'était pas du tout affiché. Il n'a pas été décodé, a montré une fenêtre vide. Nous n'avions même pas d'appareils pour le reproduire. En nous étirant à l'écran, nous avons toujours pu trouver un appareil bas de gamme sur lequel il ne fonctionnait pas, et tout a été facilement résolu à l'aide d'Android: resizeableActivity = "true" dans le manifeste. Avec le papier peint, tout s'est avéré beaucoup plus compliqué. Environ deux jours, nous avons essayé de tendre la main et d'obtenir des informations plus détaillées. À la fin, ils ont découvert que sur un certain nombre d'appareils, le codec de décodage jpeg progressif était implémenté avec des bogues, lorsque plusieurs algorithmes de compression étaient utilisés sur les résultats des autres. Dans ce cas, nous venons d'écrire un lint-check, qui échoue à la construction lors de la construction de l'application, si nous mettons le papier peint encodé de manière progressive dans l'apk lui-même. Recodé tous les fonds d'écran, répété la situation sur le backend, qui distribue le reste des ensembles de fonds d'écran, et tout fonctionne très bien. Mais cela nous a coûté environ deux jours de procédure.

Cela ressemble à ceci dans le code. Désagréable, mais malheureusement, comme ça. Habituellement, ces lignes apparaissent après un long débogage.

Quelles garanties Google nous donne-t-il pour garantir que l'API ne soit pas tellement cassée que les applications ne fonctionnent pas en principe? Tout d'abord, il existe un CDD qui décrit ce qui est possible et ce qui ne peut pas être changé, ce qui est rétrocompatible et ce qui ne l'est pas. Il s'agit d'un document de plusieurs dizaines de pages contenant des recommandations générales qui, bien entendu, ne couvriront pas tous les cas. Pour couvrir plus de cas, il existe CTS, qui doit être rempli pour que le téléphone reçoive la certification de Google et les services Google peuvent être utilisés à partir de celui-ci. Il s'agit d'un ensemble d'environ 350 000 tests automatisés. Il existe également un vérificateur CTS, un fichier APK normal que vous pouvez mettre sur votre téléphone pour effectuer une série de vérifications. Soit dit en passant, si vous achetez un téléphone avec vos mains, vous pouvez le vérifier comme ça.
Avec l'avènement de Treble, le projet VTS est apparu, il est plus probable pour les développeurs de niveaux inférieurs. Il vérifie les API du pilote qui, à partir de Project Treble, sont versionnées et subissent également des tests similaires. En outre, les développeurs de téléphones eux-mêmes sont des personnes en bonne santé qui souhaitent que les applications Android fonctionnent correctement pour eux, mais c'est un espoir. Le côté négatif est que nous rencontrons des bugs imprévus qui ne peuvent être prédits avant le lancement de l'application sur l'appareil. Encore une fois, nous sommes obligés d'acheter, en plus du fait que différentes versions de l'API, il existe également des appareils supplémentaires de différents fabricants afin de les vérifier.
Mais il y a des aspects positifs. Au minimum, les fonctionnalités les plus fréquemment implémentées par les fabricants relèvent d'Android lui-même. Quelqu'un peut se souvenir que l'API Fingerprint standard est apparue plus tard que les appareils qui pouvaient déverrouiller l'écran avec une empreinte digitale. Maintenant, selon les développeurs XDA, l'API Android veut également déverrouiller en utilisant la caméra en face, mais ce n'est pas encore exact. Nous allons probablement en savoir plus avec vous.
De plus, les développeurs d'appareils eux-mêmes, lorsqu'ils créent des API non standard, ils peuvent, et de nombreuses bibliothèques de publication, travailler avec leur API pour les développeurs ordinaires. Et si vous ne l'avez jamais fait auparavant, je vous conseille de parcourir les statistiques d'utilisation de votre application, de voir quels sont les fabricants les plus populaires, et de regarder les portails développeurs de leurs sites. Je pense que vous serez agréablement surpris que beaucoup aient des API avec des fonctionnalités matérielles intéressantes, des fonctionnalités de sécurité, des services cloud ou autre chose d'intéressant. Et à première vue, il semble insensé d'écrire des fonctionnalités distinctes pour des appareils individuels, mais en plus des appareils, il existe également des fabricants de processeurs, encore plus petits, qui implémentent également leurs API. Par exemple, Qualcomm a une merveilleuse accélération matérielle pour reconnaître les images de la caméra, que vous pouvez utiliser, ils en ont même une bonne description.
Ainsi, n'importe lequel d'entre vous peut bénéficier de ce type de fragmentation. Que faisons-nous avec ça? En aucun cas, n'hésitez pas à signaler des bogues et à envoyer des rapports de bogues aux développeurs d'appareils et même aux développeurs Android. Parce que si des API qui valaient la peine d'écrire le test CTS ont été cassées, alors elles seront écrites - et il y avait de tels précédents - et après cela, l'API est devenue plus fiable.
Apprenez Android, découvrez ce que les fabricants proposent, ne jurez pas avec eux - travaillez avec eux.
À quoi ça ressemble de l'intérieur? Comment puis-je implémenter une fonctionnalité qui sera unique à notre téléphone et utiliser cette API à partir d'une application Android normale?

Un peu de théorie. Comment les développeurs Android décrivent-ils eux-mêmes le périphérique AOSP interne? La couche supérieure est une application qui a été écrite par vous ou par les développeurs du téléphone lui-même, qui n'a pas de droits élevés, utilise simplement des API standard. Ceci est un framework, ce sont des classes qui ne font pas partie de votre application, telles que Activity, Parcelable, Bundle - elles font partie du système, elles sont appelées framework. Les classes disponibles sur l'appareil. Viennent ensuite les services système. C'est ce qui vous connecte au système: ActivityManagerService, WindowManagerService, PackageManagerService, qui implémentent le côté interne de l'interaction avec le système.
Viennent ensuite la couche d'abstraction matérielle, c'est la couche supérieure des pilotes, qui contient toute la logique d'affichage à l'écran, d'interaction avec Bluetooth et autres. Le noyau est la couche inférieure des pilotes, la gestion du système. Ceux qui savent ce qu'est le noyau et auquel il est confronté n'ont pas besoin de le dire, mais vous pouvez parler longtemps.

À quoi cela ressemble-t-il sur l'appareil? Notre application interagit non seulement avec le cadre standard, mais peut également interagir avec le cadre personnalisé du fabricant. De plus, grâce à ce cadre, il peut communiquer avec des services personnalisés. S'il s'agit de fonctionnalités câblées ou de bas niveau, alors HAL est écrit pour elles, et même des pilotes au niveau du noyau, si nécessaire.

Comment écrivons-nous notre fonctionnalité? Le plan est simple: vous devez écrire un framework qui n'est pas très différent des bibliothèques que la plupart des développeurs Android ont écrites, je pense que vous le savez tous. Vous devez écrire un service système, qui est une application ordinaire, uniquement avec un ensemble de droits pas tout à fait ordinaire dans le système. Et si nécessaire, vous pouvez écrire HAL, mais nous omettons cela. Vous pouvez écrire vos propres pilotes au niveau du noyau, mais nous ne le considérerons pas non plus. Et écrivez une application client qui utilisera tout cela.

Pour pouvoir interagir avec le système, nous devons rédiger une sorte de contrat, et pour cela il existe déjà un bon mécanisme pour les interfaces AIDL. C’est juste une sorte d’interface sur la base de laquelle le système génère une classe supplémentaire que nous pouvons étendre, à travers laquelle la communication interprocessus entre votre application et les services système est effectuée.

Ensuite, nous écrivons un framework, notre bibliothèque, qui contient l'implémentation de cette interface, et procède par procuration à tous les appels. Si vous êtes intéressé, alors ActivityManager, PackageManager, WindowManager fonctionnent de la même manière. Il y a un peu plus de logique que ce que nous avons mis en œuvre ici, mais l'essentiel est juste cela.

Nous avons implémenté le framework, nous devons écrire un service système qui recevra nos données du côté système, dans ce cas, nous transmettons et lisons un entier. Nous créons une classe, elle étend également l'interface qui a été générée à partir d'AIDL sur la diapositive précédente. Nous créons un champ dans lequel nous écrivons des valeurs, lisons, écrivons un setter, getter. La seule chose est qu'il n'y a pas assez de verrous, mais ils ne tenaient pas beaucoup sur la glissière, ils devraient être faits.

De plus, pour que ce service système soit disponible, nous devons l'enregistrer dans le gestionnaire de services système, et dans ce cas, c'est la même classe qui n'est pas disponible pour les applications ordinaires. Il est disponible précisément pour ceux qui sont dans la plate-forme dans les partitions du système. Nous enregistrons le service simplement dans Application.onCreate (), le rendons disponible sous le nom de la classe que nous avons créée.
De quoi avons-nous besoin pour que onCreate () démarre et que notre service soit chargé en mémoire? Nous écrivons dans le manifeste dans l'application android: persistent = ”true”. Cela signifie qu'il s'agit d'un processus persistant, il doit être constamment en mémoire, car il exécute des fonctions système.

Dans le manifeste lui-même, nous pouvons également spécifier android: sharedUserId, dans ce cas, le système, mais il peut s'agir d'une large gamme d'ID différents, ils permettent à l'application d'obtenir des droits plus étendus sur le système, d'interagir avec diverses API et services qui ne sont pas disponibles pour les applications ordinaires.
Dans ce cas, par exemple, nous n'avons rien utilisé de tel.

Nous avons écrit un cadre, écrit un service système. Nous allons omettre les mécanismes à l'intérieur, c'est un sujet quelque peu compliqué, il mérite un rapport séparé.
Comment fournir le framework aux développeurs d'applications? Deux formats. Nous pouvons émettre des classes à part entière et créer une bibliothèque à part entière que vous compilez dans votre application, et toute la logique fera partie de vos dexes.
Ou vous pouvez distribuer le cadre sous la forme de classes de stub, par rapport auxquelles vous ne pouvez lier qu'au moment de la compilation et vous attendre à ce que ces classes vous attendent de la même manière que les exemples précédents de différentes versions d'Android sur l'appareil lui-même. Vous pouvez le distribuer via un référentiel Maven ordinaire que tout le monde connaît ou via Android Studio sdkmanager, de la même manière que vous installez de nouvelles versions du SDK. Il est difficile de dire quelle méthode est la plus pratique. Il est plus pratique pour moi de connecter Maven personnellement.

Nous écrivons une application simple. De manière familière, nous connectons la dépendance compileOnly, seulement maintenant c'est notre bibliothèque. Nous prescrivons une bibliothèque d'utilisations que nous avons écrite et que nous avons mise sur l'appareil. Nous écrivons Activity, obtenons l'accès à ces classes, interagissons avec le système. Il serait donc possible de mettre en œuvre absolument toutes les fonctionnalités: transfert de données vers certains appareils supplémentaires, fonctionnalités matérielles supplémentaires, etc.
Ainsi, tous les développeurs mettent des fonctionnalités uniques de l'appareil à la disposition des développeurs. Parfois, ce sont des API privées qui ne donnent qu'aux partenaires. Parfois, ils sont publics et ceux que vous pouvez trouver sur les portails de développeurs. Il existe d'autres façons de mettre en œuvre de telles choses, mais j'ai décrit une méthode qui est considérée comme la principale de Google et des développeurs Android.
Vous ne devez pas traiter les développeurs d'appareils comme des personnes qui cassent vos applications. Ce sont les mêmes développeurs, ils écrivent le même code, à peu près au même niveau. Rédiger des rapports de bugs, ils aident vraiment, je les analyse souvent. Écrivez plus d'applications et profitez des opportunités qu'offre Android et l'appareil lui-même. J'ai tout.