Uno de los principales desafíos del desarrollo de Android es la fragmentación. Casi todos los fabricantes cambian Android a sus necesidades. El desarrollador Andrey Makeev enumeró las diferencias entre las implementaciones de los proveedores y el proyecto original de código abierto de Android. Del informe puede aprender cómo beneficiarse de las características individuales del firmware en diferentes dispositivos.
- He estado programando desde la escuela, lo he estado desarrollando para Android durante tres años. De estos, pasé un año en Yandex, participé en proyectos como Launcher y Phone.

Quiero hablar sobre cómo se ve la fragmentación de la API de los dispositivos Android: desde el exterior, desde el lado de los desarrolladores de aplicaciones y desde el interior, desde el punto de vista de los desarrolladores de la plataforma y los teléfonos.
Mi informe consta de dos partes. Primero, hablemos sobre cómo la API se fragmenta externamente. Luego, revisaremos el código: descubriremos cómo se realiza la característica única del teléfono abstracto, cómo se construye el desarrollo.
La fragmentación de API es uno de los parámetros por los cuales puede fragmentar un dispositivo. Lo más obvio y más simple es la fragmentación según el SDK de Android, la encontramos todos los días, literalmente desde los primeros días de desarrollo para Android. Sabemos qué y en qué versión de la API apareció, que se eliminó, que se realizó una copia de seguridad, pero que todavía está disponible y que ya no está. Como regla, utilizamos varias bibliotecas de soporte de Google para simplificar nuestras vidas. Ya se ha hecho mucho por nosotros.


En nuestro código, se ve más o menos así: activamos algunas funciones, desactivamos algunas, dependiendo de la versión del SDK en la que nos encontremos actualmente. Si usa alguno, generalmente hacen lo mismo, pero por dentro.
No nos centraremos en este tipo de fragmentación. Los inconvenientes son conocidos por todos: nos vemos obligados a mantener una flota completa de dispositivos con diferentes versiones para al menos probar nuestra aplicación. Además, nos vemos obligados a escribir código adicional. Esto es especialmente inconveniente cuando recién comenzaste a desarrollar para Android y de repente resulta: debes aprender lo que había allí hace dos o tres años para admitir algunos dispositivos antiguos. Aspectos positivos: API se está desarrollando, la tecnología avanza, Android está ganando nuevos usuarios, se está volviendo más conveniente tanto para desarrolladores como para usuarios.
¿Cómo trabajamos con esto? También utilizamos bibliotecas y estamos muy contentos cuando nos negamos a admitir versiones anteriores. Creo que en la vida de todos los que han estado haciendo esto durante más de un año, hubo un momento así. Esto es solo felicidad. Esta es una opción de fragmentación obvia y simple. El siguiente paso es la fragmentación de tipo Android. Hay varios de ellos. Android TV habla por sí mismo, Android Auto está diseñado principalmente para radios de automóviles, Android Things, para IoT y dispositivos integrados similares. W es Wear OS, el antiguo Android Watch, reloj para Android. Intentaremos considerar cómo se ve esto desde el lado del desarrollador. Y, lo más interesante, intentemos considerar cómo se ve desde adentro.

Tome dos ejemplos de developer.android.com. El primero es Wear OS. ¿Qué necesitamos para hacer una solicitud? Agregamos una dependencia compileOnly a build.gradle y escribimos dos líneas adicionales en el manifiesto: uses-feature android.hardware.type.watch y uses-library, que corresponde al mismo nombre de paquete que la biblioteca que conectamos.

¿Cómo implementamos algo? Creamos una Actividad, solo en este caso no gastamos una actividad estándar con la que estamos acostumbrados a trabajar, y ni siquiera una compuesta, sino WearableActivity, y llamamos métodos específicos para ella, en este caso setAmbientEnabled (). Entonces, tenemos una dependencia compileOnly, es decir, no entra en el curso de nuestra aplicación. Uses-library, que, aparentemente, obliga al sistema operativo a conectar estas clases y este código a nosotros en tiempo de ejecución en el dispositivo, y las nuevas clases que usamos.

Android Things API prácticamente no es diferente. No prescribimos uses-feature, solo uses-library, compileOnly-dependency.

Creamos actividad, en este caso es exclusiva de la API de Android Things, la clase PeripheralManager. Estamos sondeando el GPIO y estamos tratando de comprometernos.

¿Cómo se comportará dicha aplicación en su teléfono? Hay dos opciones

Si indicamos que usa-library android: required = ”true”, entonces no cumplimos con los requisitos obligatorios del PackageManager para instalar la aplicación, y básicamente se niega a instalarla. Si especificamos android: required = "false", la aplicación se instalará, pero cuando intentemos acceder a la clase PeripheralManager, obtendremos NoClassDefFoundError, porque no existe tal clase en Android estándar.
¿Cuáles son las conclusiones? Conectamos la dependencia compileOnly solo para ponernos en contacto con ella durante el ensamblaje, y las clases que usamos nos esperan en el dispositivo, se conectan usando ciertas líneas en el manifiesto. A veces, prescribimos una función que se necesita con mayor frecuencia para distinguir en Google Play un dispositivo al que esta aplicación puede o no distribuirse.
No podría destacar los lados negativos de este tipo de fragmentación. Solo aquellos que se desarrollaron contarán muchas historias sobre cómo se encontraron con errores desconocidos completamente incomprensibles que no encontraron en el teléfono. El lado positivo es que estos son mercados adicionales, usuarios adicionales, experiencia adicional, siempre es bueno.
¿Cómo trabajar con eso? Escribe más aplicaciones. La recomendación general es escribir más de una versión de la aplicación para todos los tipos de Android, pero aún así hacer diferentes. Debería haber menos para un reloj, para Android Things prácticamente nada de lo que está escrito en el teléfono es adecuado, y así sucesivamente. Y use las bibliotecas que los desarrolladores de Android y, a veces, los desarrolladores de dispositivos nos proporcionan.
El siguiente tipo de fragmentación menos estudiado es la fragmentación del productor. Cada fabricante, después de recibir el código fuente, en raras ocasiones es AOSP, más a menudo es modificado de alguna manera por los desarrolladores de hardware, realiza cambios en él. Como regla general, aprendemos acerca de los efectos negativos de este tipo de fragmentación de los mejores canales, de las críticas negativas en Google Play, porque alguien ha roto algo. O aprendemos esto de la analítica de bloqueos, cuando de repente algo se bloquea de una manera incomprensible, solo en algunos dispositivos específicos. En el mejor de los casos, aprendemos esto de nuestro control de calidad, cuando algo se rompió durante sus pruebas en un dispositivo específico.

Sobre este tema, tengo una historia maravillosa de nuestro Lanzador de desarrollo. Recibimos un informe de error donde la actividad no se extendía a la pantalla completa, y nuestro fondo de pantalla predeterminado favorito no se mostraba en absoluto. No estaba decodificado, apareció una ventana vacía. Ni siquiera teníamos dispositivos para reproducirlo. Al estirarnos hacia la pantalla, aún pudimos encontrar un dispositivo de gama baja en el que no funcionaba, y solucionamos todo fácilmente usando Android: resizeableActivity = "true" en el manifiesto. Con el fondo de pantalla, todo resultó mucho más complicado. Aproximadamente dos días intentamos comunicarnos y obtener información más detallada. Al final, descubrieron que en varios dispositivos el códec para decodificar jpeg progresivo se implementaba con errores, cuando se usaban varios algoritmos de compresión en los resultados del otro. En este caso, simplemente escribimos un cheque de pelusa, que falló la compilación al compilar la aplicación, si colocamos el fondo de pantalla codificado de manera progresiva en el apk mismo. Recodificó todos los fondos de pantalla, repitió la situación en el backend, que distribuye el resto de los conjuntos de fondos de pantalla, y todo funciona muy bien. Pero nos costó unos dos días de procedimientos.

Se ve algo así en el código. Desagradable, pero desafortunadamente, así. Por lo general, estas líneas aparecen después de una larga depuración.

¿Qué garantías nos ofrece Google para garantizar que la API no se rompa tanto que las aplicaciones no funcionen en principio? En primer lugar, hay un CDD, que describe lo que es posible y lo que no se puede cambiar, lo que es compatible con versiones anteriores y lo que no. Este es un documento de varias docenas de páginas con recomendaciones generales, que, por supuesto, no cubrirá todos los casos. Para cubrir más casos, existe CTS, que debe completarse para que el teléfono reciba la certificación de Google y se pueden utilizar los servicios de Google. Este es un conjunto de aproximadamente 350,000 pruebas automatizadas. También hay un Verificador CTS, un APK normal que puedes poner en tu teléfono para realizar una serie de comprobaciones. Por cierto, si compra un teléfono con las manos, puede verificarlo así.
Con la llegada de Treble, apareció el proyecto VTS, es más probable que los desarrolladores de niveles más bajos. Comprueba las API del controlador, que, comenzando con Project Treble, están versionadas y también se someten a pruebas similares. Además, los propios desarrolladores de teléfonos son personas sanas que quieren que las aplicaciones de Android funcionen bien para ellos, pero esto es más o menos esperanza. El lado negativo es que encontramos errores imprevistos que no se pueden predecir hasta que la aplicación se inició en el dispositivo. Nuevamente, nos vemos obligados a comprar, además del hecho de que diferentes versiones de la API, también hay dispositivos adicionales de diferentes fabricantes para verificarlos.
Pero hay aspectos positivos. Como mínimo, las funciones implementadas con mayor frecuencia por los fabricantes se incluyen en Android. Alguien puede recordar que la API de huellas digitales estándar apareció más tarde que los dispositivos que podrían desbloquear la pantalla con una huella digital. Ahora, según XDA Developers, la API de Android también quiere desbloquear el uso de la cámara en la cara, pero esto aún no es exacto. Es probable que descubramos esto con usted.
Además, los propios desarrolladores de dispositivos, cuando crean API no estándar, pueden y muchas bibliotecas publican para trabajar con su API para desarrolladores comunes. Y si nunca antes ha hecho esto, le aconsejo que revise las estadísticas del uso de su aplicación, vea cuáles son los fabricantes más populares y consulte los portales de desarrolladores de sus sitios. Creo que se sorprenderá gratamente de que muchos tengan API con características de hardware interesantes, características de seguridad, servicios en la nube o algo más interesante. Y a primera vista parece una locura escribir características separadas para dispositivos individuales, pero además de los dispositivos también hay fabricantes de procesadores, que son incluso más pequeños, que también implementan sus API. Por ejemplo, Qualcomm tiene una maravillosa aceleración de hardware para reconocer imágenes de la cámara, que puedes usar, incluso tienen una buena descripción de ellas.
Por lo tanto, cualquiera de ustedes puede obtener algún beneficio incluso de este tipo de fragmentación. ¿Qué estamos haciendo con esto? En ningún caso, siéntase libre de informar errores y enviar informes de errores a desarrolladores de dispositivos e incluso desarrolladores de Android. Porque si alguna API que valiera la pena escribir la prueba CTS se rompió, entonces se escribirá, y hay tales precedentes, y después de eso, la API se volverá más confiable.
Aprende Android, conoce lo que ofrecen los fabricantes, no jures con ellos, trabaja con ellos.
¿Cómo se ve desde adentro? ¿Cómo puedo implementar alguna función que sea exclusiva de nuestro teléfono y usar esta API desde una aplicación de Android normal?

Un poco de teoría ¿Cómo describen los propios desarrolladores de Android el dispositivo interno AOSP? La capa superior es una aplicación escrita por usted o por los desarrolladores del teléfono, que no tiene derechos elevados, solo utiliza API estándar. Este es un marco, estas son clases que no forman parte de su aplicación, como Actividad, Parcelable, Paquete: son parte del sistema, se les conoce como marco. Las clases que están disponibles para usted en el dispositivo. Los siguientes son los servicios del sistema. Esto es lo que lo conecta con el sistema: ActivityManagerService, WindowManagerService, PackageManagerService, que implementa el lado interno de la interacción con el sistema.
Luego está la Capa de abstracción de hardware, esta es la capa superior de controladores, que contiene toda la lógica para mostrar en la pantalla, para interactuar con Bluetooth y similares. El núcleo es la capa inferior de controladores, gestión del sistema. Aquellos que saben cuál es el núcleo y lo que enfrentan, no necesitan decirlo, pero pueden hablar durante mucho tiempo.

¿Cómo se ve en el dispositivo? Nuestra aplicación interactúa no solo con el marco estándar, sino que también puede interactuar con el marco personalizado del fabricante. Además, a través de este marco, puede comunicarse con servicios personalizados. Si se trata de funciones cableadas o de bajo nivel, HAL está escrito para ellas, e incluso controladores a nivel del núcleo, si es necesario.

¿Cómo escribimos nuestra función? El plan es simple: necesita escribir un marco que no sea muy diferente de las bibliotecas que escribieron la mayoría de los desarrolladores de Android, creo que todos lo saben. Debe escribir un servicio del sistema, que es una aplicación ordinaria, solo con un conjunto de derechos no bastante ordinarios en el sistema. Y si es necesario, puede escribir HAL, pero omitimos esto. Puede escribir sus propios controladores a nivel del núcleo, pero tampoco lo consideraremos ahora. Y escriba una aplicación cliente que use todo esto.

Para que podamos interactuar con el sistema, necesitamos escribir algún tipo de contrato, y para esto ya existe un buen mecanismo para las interfaces AIDL. Es solo un tipo de interfaz sobre la base de la cual el sistema genera una clase adicional que podemos ampliar, a través de la cual se lleva a cabo la comunicación entre procesos entre su aplicación y los servicios del sistema.

A continuación, escribimos un marco, nuestra biblioteca, que contiene la implementación de esta interfaz, y representa todas las llamadas a ella. Si está interesado, ActivityManager, PackageManager, WindowManager funcionan de la misma manera. Hay un poco más de lógica que la que hemos implementado aquí, pero la esencia es solo eso.

Implementamos el marco, necesitamos escribir un servicio del sistema que reciba nuestros datos del lado del sistema, en este caso transmitimos y leemos enteros. Creamos una clase, también amplía la interfaz que se generó a partir de AIDL en la diapositiva anterior. Creamos un campo en el que escribimos valores, leemos, escribimos un setter, getter. Lo único es que no hay suficientes bloqueos, pero no encajan mucho en la diapositiva, deben hacerse.

Además, para que este servicio del sistema esté disponible, debemos registrarlo en el administrador de servicios del sistema, y en este caso es la misma clase que no está disponible para las aplicaciones ordinarias. Está disponible precisamente para aquellos que están en la plataforma en las particiones del sistema. Registramos el servicio simplemente en Application.onCreate (), lo ponemos a disposición bajo el nombre de la clase que creamos.
¿Qué necesitamos para que onCreate () se inicie básicamente y que nuestro servicio se cargue en la memoria? Escribimos en el manifiesto en la aplicación de Android: persistente = "verdadero". Esto significa que este es un proceso persistente, debe estar en la memoria constantemente, ya que realiza funciones del sistema.

También en el manifiesto en sí podemos especificar android: sharedUserId, en este caso el sistema, pero puede ser una amplia gama de ID diferentes, permiten que la aplicación obtenga derechos más amplios en el sistema, interactúe con varias API y servicios que no están disponibles para las aplicaciones comunes.
En este caso, por ejemplo, no usamos nada como esto.

Escribimos un marco, escribimos un servicio del sistema. Omitiremos los mecanismos internos, este es un tema algo complicado, merece un informe separado.
¿Cómo entregar el marco a los desarrolladores de aplicaciones? Dos formatos Podemos emitir clases completas y crear una biblioteca completa que compile en su aplicación, y toda la lógica se convertirá en parte de sus destrezas.
O puede distribuir el marco en forma de clases stub, en relación con el cual solo puede vincular durante la compilación y esperar que estas clases lo esperen de manera similar a los ejemplos anteriores de varias versiones de Android en el dispositivo. Puede distribuirlo a través de un repositorio Maven normal que todos conocen, o a través de sdkmanager de Android Studio, similar a cómo instala nuevas versiones del SDK. Es difícil decir qué método es más conveniente. Es más conveniente para mí personalmente conectar Maven.

Estamos escribiendo una aplicación simple. De una manera familiar, conectamos la dependencia compileOnly, solo que ahora es nuestra biblioteca. Prescribimos usos-biblioteca que escribimos y que pusimos en el dispositivo. Escribimos Actividad, accedemos a estas clases, interactuamos con el sistema. Por lo tanto, sería posible implementar absolutamente cualquier característica: transferencia de datos a algunos dispositivos adicionales, características de hardware adicionales, etc.
Por lo tanto, todos los desarrolladores ponen a disposición de los desarrolladores características únicas del dispositivo. A veces, estas son API privadas que solo se otorgan a socios. A veces son públicos y los que puedes encontrar en los portales de desarrolladores. Hay otras formas de implementar tales cosas, pero describí un método que se considera el principal en Google y entre los desarrolladores de Android.
No debe tratar a los desarrolladores de dispositivos como personas que rompen sus aplicaciones. Estos son los mismos desarrolladores, escriben el mismo código, aproximadamente al mismo nivel. Escribir informes de errores, realmente ayudan, a menudo los analizo. Escriba más aplicaciones y aproveche las oportunidades que ofrecen tanto Android como el propio dispositivo. Lo tengo todo