Um dos principais desafios do desenvolvimento do Android é a fragmentação. Quase todos os fabricantes alteram o Android para suas necessidades. O desenvolvedor Andrey Makeev listou as diferenças entre implementações de fornecedores e o Android Open Source Project original. No relatório, você pode aprender como se beneficiar dos recursos individuais do firmware em diferentes dispositivos.
- Programei desde a escola, desenvolvo-o para Android há três anos. Destes, passei um ano no Yandex, participei de projetos como Launcher e Phone.

Quero falar sobre como é a fragmentação da API dos dispositivos Android - do lado de fora, do lado dos desenvolvedores de aplicativos e do lado de dentro, do ponto de vista dos desenvolvedores da plataforma e dos telefones.
Meu relatório consiste em duas partes. Primeiro, vamos falar sobre como a API é fragmentada externamente. Em seguida, examinaremos o código - descobriremos como a característica exclusiva do telefone abstrato é realizada, como o desenvolvimento é construído.
A fragmentação da API é um dos parâmetros pelos quais você pode fragmentar um dispositivo. O mais óbvio e mais simples é a fragmentação de acordo com o SDK do Android, nós a encontramos todos os dias, literalmente desde os primeiros dias de desenvolvimento para o Android. Sabemos o que e em qual versão da API ela apareceu, que foi removida, que foi copiada, mas ainda está disponível e que já foi desativada. Como regra, usamos várias bibliotecas de suporte do Google para simplificar nossas vidas. Muito já foi feito por nós.


Em nosso código, parece algo assim: ativamos algumas funcionalidades, desativamos algumas - dependendo da versão do SDK em que estamos atualmente. Se você usar algum, eles geralmente fazem o mesmo, mas por dentro.
Não vamos nos concentrar nesse tipo de fragmentação. Os contras são conhecidos por todos - somos forçados a manter toda uma frota de dispositivos com versões diferentes para, pelo menos, testar nosso aplicativo. Além disso, somos forçados a escrever código extra. Isso é especialmente inconveniente quando você começou a desenvolver para o Android e, de repente, acontece: você precisa aprender o que havia dois ou três anos atrás para oferecer suporte a alguns dispositivos antigos. Aspectos positivos: a API está se desenvolvendo, a tecnologia está avançando, o Android está conquistando novos usuários, está se tornando mais conveniente para desenvolvedores e usuários.
Como trabalhamos com isso? Também usamos bibliotecas e ficamos muito felizes quando nos recusamos a suportar versões mais antigas. Eu acho que na vida de todos que fazem isso há mais de um ano, houve um momento assim. Isso é apenas felicidade. Esta é uma opção de fragmentação óbvia e simples. O próximo passo é a fragmentação do tipo Android. Existem vários deles. A Android TV fala por si mesma, o Android Auto se destina principalmente a rádios de carros, Android Things - para IoT e dispositivos embarcados similares. W é o Wear OS, o antigo Android Watch, relógio para Android. Vamos tentar considerar como isso fica do lado do desenvolvedor. E, o mais interessante, vamos tentar considerar como fica por dentro.

Veja dois exemplos de developer.android.com. O primeiro é o Wear OS. O que precisamos para fazer um aplicativo? Adicionamos uma dependência compileOnly para build.gradle e gravamos duas linhas adicionais no manifesto: uses-feature android.hardware.type.watch e uses-library, que corresponde ao mesmo nome de pacote que a biblioteca que conectamos.

Como implementamos algo? Criamos uma atividade, apenas nesse caso não gastamos uma atividade padrão com a qual estamos acostumados a trabalhar, e nem mesmo uma composta, mas WearableActivity, e chamamos métodos específicos a ela, neste caso setAmbientEnabled (). Portanto, temos uma dependência compileOnly, ou seja, ela não entra no curso de nossa aplicação. Biblioteca de usos, que aparentemente força o sistema operacional a conectar essas classes e esse código a nós em tempo de execução no dispositivo e as novas classes que usamos.

A API do Android Things praticamente não é diferente. Não prescrevemos usos-recurso, apenas usos-biblioteca, compileOnly-dependência.

Criamos atividade, nesse caso, é exclusiva da API do Android Things, a classe PeripheralManager. Estamos pesquisando o GPIO e tentando prometer.

Como esse aplicativo se comportará no seu telefone? Existem duas opções.

Se indicamos que android da biblioteca de usos: required = ”true”, não cumprimos os requisitos obrigatórios do PackageManager para instalar o aplicativo, e ele basicamente se recusa a instalá-lo. Se especificarmos android: required = ”false”, o aplicativo será instalado, mas quando tentarmos acessar a classe PeripheralManager, obteremos NoClassDefFoundError, porque não existe essa classe no Android padrão.
Quais são as conclusões? Conectamos a dependência compileOnly apenas para entrar em contato com ela durante a montagem, e as classes que usamos nos aguardam no dispositivo, elas são conectadas usando determinadas linhas no manifesto. Às vezes, prescrevemos um recurso que é mais frequentemente necessário para distinguir no Google Play um dispositivo para o qual esse aplicativo pode ou não pode ser distribuído.
Não pude destacar os lados negativos desse tipo de fragmentação. Somente aqueles que se desenvolveram contarão muitas histórias sobre como encontraram bugs desconhecidos completamente incompreensíveis que não encontraram no telefone. O lado positivo é que estes são mercados adicionais, usuários adicionais, experiência adicional, é sempre bom.
Como trabalhar com isso? Escreva mais aplicativos. A recomendação geral é escrever mais de uma versão do aplicativo para todos os tipos de Android, mas ainda assim diferentes. Deve haver menos para um relógio, para o Android Things praticamente nada do que está escrito no telefone é adequado, e assim por diante. E use as bibliotecas que os desenvolvedores do Android e, às vezes, os desenvolvedores de dispositivos nos fornecem.
O próximo tipo de fragmentação menos estudado é a fragmentação do produtor. Cada fabricante, que recebeu o código-fonte - em casos raros, é o AOSP, mais frequentemente é modificado pelos desenvolvedores de hardware - faz alterações nele. Como regra geral, aprendemos sobre os efeitos negativos desse tipo de fragmentação não dos melhores canais - de críticas negativas no Google Play, porque alguém quebrou alguma coisa. Ou aprendemos isso com a análise de falhas, quando de repente algo falha de uma maneira incompreensível, apenas em alguns dispositivos específicos. Na melhor das hipóteses, aprendemos isso com nosso controle de qualidade, quando algo ocorreu durante o teste em um dispositivo específico.

Neste tópico, tenho uma história maravilhosa do nosso Launcher de desenvolvimento. Recebemos um relatório de erro em que a atividade não se estendia à tela inteira e nosso papel de parede padrão favorito não era exibido. Não foi decodificado, uma janela vazia apareceu. Nem sequer tínhamos dispositivos para reproduzi-lo. Ao esticar para a tela, ainda conseguimos encontrar um dispositivo low-end no qual não funcionava, e consertamos tudo com facilidade usando o android: resizeableActivity = "true" no manifesto. Com o papel de parede, tudo ficou muito mais complicado. Cerca de dois dias, tentamos entrar em contato e obter informações mais detalhadas. No final, eles descobriram que em vários dispositivos o codec para decodificar o jpeg progressivo foi implementado com bugs, quando vários algoritmos de compactação foram usados nos resultados um do outro. Nesse caso, acabamos de escrever uma verificação de cotão, que falhou na compilação ao criar o aplicativo, se colocarmos o papel de parede codificado de maneira progressiva no próprio apk. Recodificou todos os papéis de parede, repetiu a situação no back-end, que distribui o restante dos conjuntos de papéis de parede e tudo funciona muito bem. Mas nos custou cerca de dois dias de processo.

Parece algo assim no código. Desagradável, mas infelizmente assim. Geralmente essas linhas aparecem após uma longa depuração.

Que garantias o Google nos dá para garantir que a API não seja quebrada tanto que os aplicativos não funcionem em princípio? Primeiro de tudo, existe um CDD, que descreve o que é possível e o que não pode ser alterado, o que é compatível com versões anteriores e o que não é. Este é um documento de várias dezenas de páginas com recomendações gerais, as quais, é claro, não abrangerão todos os casos. Para cobrir mais casos, existe o CTS, que deve ser preenchido para que o telefone receba a certificação do Google e os serviços do Google possam ser usados a partir dele. Este é um conjunto de aproximadamente 350.000 testes automatizados. Há também um Verificador CTS, um APK comum que você pode colocar no telefone para realizar uma série de verificações. A propósito, se você comprar um telefone com as mãos, poderá verificar assim.
Com o advento do Treble, o projeto VTS apareceu, é mais provável para desenvolvedores de níveis mais baixos. Ele verifica as APIs do driver, que, começando com o Project Treble, são versionadas e também passam por testes semelhantes. Além disso, os desenvolvedores de telefones são pessoas saudáveis que desejam que os aplicativos Android funcionem bem para eles, mas isso é uma esperança. O lado negativo é que encontramos bugs imprevistos que não podem ser previstos até que o aplicativo seja iniciado no dispositivo. Novamente, somos obrigados a comprar, além do fato de que diferentes versões da API, também existem dispositivos adicionais de diferentes fabricantes, a fim de verificá-los.
Mas há aspectos positivos. No mínimo, os recursos mais frequentemente implementados pelos fabricantes se enquadram no próprio Android. Alguém pode se lembrar que a API de impressão digital padrão apareceu mais tarde do que os dispositivos que poderiam desbloquear a tela com uma impressão digital. Agora, de acordo com XDA Developers, a API do Android também deseja desbloquear usando a câmera na cara, mas isso ainda não é preciso. É provável que você descubra isso com você.
Além disso, os próprios desenvolvedores de dispositivos, quando criam APIs não padrão, podem e muitas publicam bibliotecas para trabalhar com sua API para desenvolvedores comuns. E se você nunca fez isso antes, recomendo que você revise as estatísticas do uso de seu aplicativo, veja quais são os fabricantes mais populares e consulte os portais de desenvolvedores de seus sites. Acho que você ficará surpreso ao saber que muitos têm APIs com recursos interessantes de hardware, recursos de segurança, serviços em nuvem ou qualquer outra coisa interessante. E, à primeira vista, parece selvagem escrever recursos separados para dispositivos individuais, mas, além dos dispositivos, também existem fabricantes de processadores, ainda menores, que também implementam suas APIs. Por exemplo, a Qualcomm possui uma aceleração de hardware maravilhosa para reconhecer imagens da câmera, que você pode usar bastante, elas ainda têm uma boa descrição delas.
Assim, qualquer um de vocês pode obter algum benefício, mesmo com esse tipo de fragmentação. O que estamos fazendo com isso? Em nenhum caso, fique à vontade para relatar erros e enviar relatórios de erros para desenvolvedores de dispositivos e até para desenvolvedores do Android. Como se alguma API que valesse a pena escrever no teste CTS fosse quebrada, ela seria gravada - e havia esses precedentes - e depois a API se tornaria mais confiável.
Aprenda o Android, saiba o que os fabricantes oferecem, não jure com eles - trabalhe com eles.
Como é o interior? Como posso implementar um recurso exclusivo do nosso telefone e usar essa API em um aplicativo Android comum?

Um pouco de teoria. Como os próprios desenvolvedores do Android descrevem o dispositivo AOSP interno? A camada superior é um aplicativo que foi escrito por você ou pelos desenvolvedores do telefone, que não possui direitos altos, apenas usa APIs padrão. Essa é uma estrutura, essas são as classes que não fazem parte do seu aplicativo, como Atividade, Parcelável, Pacote configurável - elas fazem parte do sistema, são chamadas de estrutura. As classes que estão disponíveis para você no dispositivo. A seguir estão os serviços do sistema. É isso que o conecta ao sistema: ActivityManagerService, WindowManagerService, PackageManagerService, que implementam o lado interno da interação com o sistema.
A seguir, estão a camada de abstração de hardware, essa é a camada superior dos drivers, que contém toda a lógica para exibição na tela, para interação com Bluetooth e similares. O kernel é a camada inferior dos drivers, gerenciamento do sistema. Quem sabe o que é o núcleo e o que ele enfrenta, não precisa contar, mas você pode conversar por um longo tempo.

Como fica no dispositivo? Nosso aplicativo interage não apenas com a estrutura padrão, mas também pode interagir com a estrutura personalizada do fabricante. Além disso, por meio dessa estrutura, ele pode se comunicar com serviços personalizados. Se esses são recursos conectados ou de baixo nível, o HAL é escrito para eles e até mesmo drivers no nível do kernel, se necessário.

Como escrevemos nosso recurso? O plano é simples: você precisa escrever uma estrutura que não seja muito diferente das bibliotecas que a maioria dos desenvolvedores do Android escreveu, acho que todos sabem disso. Você precisa escrever um serviço do sistema, que é um aplicativo comum, apenas com um conjunto de direitos não muito comuns no sistema. E, se necessário, você pode escrever HAL, mas omitimos isso. Você pode escrever seus próprios drivers no nível do kernel, mas também não o consideraremos agora. E escreva um aplicativo cliente que usará tudo isso.

Para podermos interagir com o sistema, precisamos escrever algum tipo de contrato e, para isso, já existe um bom mecanismo para as interfaces AIDL. É apenas um tipo de interface com base na qual o sistema gera uma classe adicional que podemos estender, por meio da qual é realizada a comunicação entre processos entre o aplicativo e os serviços do sistema.

Em seguida, escrevemos uma estrutura, nossa biblioteca, que mantém a implementação dessa interface, e proxies todas as chamadas para ela. Se você estiver interessado, o ActivityManager, o PackageManager e o WindowManager funcionam da mesma maneira. Há um pouco mais de lógica do que implementamos aqui, mas a essência é exatamente isso.

Implementamos a estrutura, precisamos escrever um serviço do sistema que receba nossos dados do lado do sistema; nesse caso, transmitimos e lemos números inteiros. Criamos uma classe, ela também estende a interface gerada pelo AIDL no slide anterior. Criamos um campo no qual escrevemos valores, lemos, escrevemos um setter, getter. A única coisa é que não há bloqueios suficientes, mas eles não se encaixam muito no slide, eles devem ser feitos.

Além disso, para que esse serviço do sistema esteja disponível, precisamos registrá-lo no gerenciador de serviços do sistema e, nesse caso, é da mesma classe que não está disponível para aplicativos comuns. Está disponível precisamente para aqueles que estão na plataforma nas partições do sistema. Registramos o serviço simplesmente em Application.onCreate (), disponibilizamos-o sob o nome da classe que criamos.
O que precisamos para o onCreate () iniciar basicamente e nosso serviço ser carregado na memória? Escrevemos no manifesto no aplicativo android: persistent = ”true”. Isso significa que este é um processo persistente, que deve estar na memória constantemente, porque executa as funções do sistema.

Também no manifesto, podemos especificar android: sharedUserId, neste caso, sistema, mas pode ser uma ampla variedade de IDs diferentes, eles permitem que o aplicativo obtenha direitos mais amplos no sistema, interaja com várias APIs e serviços que não estão disponíveis para aplicativos comuns.
Nesse caso, por exemplo, não usamos nada parecido com isso.

Escrevemos uma estrutura, escrevemos um serviço do sistema. Omitiremos os mecanismos internos, este é um tópico um pouco complicado, merece um relatório separado.
Como entregar a estrutura para desenvolvedores de aplicativos? Dois formatos. Podemos emitir classes completas e criar uma biblioteca completa compilada em seu aplicativo, e toda a lógica se tornará parte de seus dexes.
Ou você pode distribuir a estrutura na forma de classes stub, em relação às quais você só pode vincular durante a compilação e esperar que essas classes esperem por você da mesma forma que os exemplos anteriores de várias versões do Android no próprio dispositivo. Você pode distribuí-lo por meio de um repositório Maven comum que todos conhecem ou por meio do Android Studio sdkmanager, semelhante à instalação de novas versões do SDK. É difícil dizer qual método é mais conveniente. É mais conveniente para mim conectar pessoalmente o Maven.

Estamos escrevendo um aplicativo simples. De uma maneira familiar, conectamos a dependência compileOnly, só que agora é a nossa biblioteca. Nós prescrevemos a biblioteca de usos que escrevemos e colocamos no dispositivo. Nós escrevemos Activity, temos acesso a essas classes, interagimos com o sistema. Portanto, seria possível implementar absolutamente todos os recursos: transferência de dados para alguns dispositivos adicionais, recursos adicionais de hardware, etc.
Assim, todos os desenvolvedores disponibilizam recursos exclusivos do dispositivo para os desenvolvedores. Às vezes, são APIs privadas que oferecem apenas para parceiros. Às vezes, eles são públicos e aqueles que você pode encontrar nos portais de desenvolvedores. Existem outras maneiras de implementar essas coisas, mas descrevi um método que é considerado o principal no Google e entre os desenvolvedores do Android.
Você não deve tratar os desenvolvedores de dispositivos como pessoas que quebram seus aplicativos. Estes são os mesmos desenvolvedores, eles escrevem o mesmo código, aproximadamente no mesmo nível. Escreva relatórios de bugs, eles realmente ajudam, eu frequentemente os analiso. Escreva mais aplicativos e aproveite as oportunidades que o Android e o próprio dispositivo oferecem. Eu tenho tudo.