A combinação de plataforma cruzada e abordagem nativa no desenvolvimento de aplicativos móveis

O lançamento de aplicativos para apenas uma plataforma móvel não é relevante e você precisa cuidar de desenvolver duas versões ao mesmo tempo, para iOS e Android. E aqui você pode escolher duas maneiras: trabalhar nas linguagens de programação "nativas" para cada sistema operacional ou usar estruturas de plataforma cruzada.

Ao desenvolver um dos projetos da DD Planet, contei com a última opção. E neste artigo, falarei sobre a experiência de desenvolver um aplicativo de plataforma cruzada, os problemas que encontramos e as soluções encontradas.

Três abordagens para o desenvolvimento de aplicativos móveis de plataforma cruzada


Para começar, considere as abordagens usadas quando você precisar obter dois aplicativos ao mesmo tempo: para iOS e Android.

O primeiro é o mais caro, em tempo e em recursos: desenvolver um aplicativo separado para cada plataforma. A complexidade dessa abordagem está no fato de que cada um dos sistemas operacionais exige uma abordagem própria: isso é expresso tanto na linguagem em que o desenvolvimento está em andamento (para Android - Java ou Kotlin, para iOS - Objective-C ou Swift) e nos métodos para descrever a parte da interface do usuário aplicativos (arquivos axml e xib ou storyboard, respectivamente).

Somente esse fato nos leva ao fato de que, para essa abordagem, é necessário formar duas equipes de desenvolvimento. Além disso, você precisará duplicar a lógica de cada uma das plataformas: interação com API e lógica de negócios.

imagem

Mas e se o número de APIs usadas aumentar?

imagem

Isso levanta a questão: como reduzir a quantidade necessária de recursos humanos? Livre-se da necessidade de duplicar o código para cada plataforma. Há um número suficiente de estruturas e tecnologias que resolvem esse problema.

O uso de uma estrutura de plataforma cruzada (Xamarin.Forms, por exemplo) torna possível escrever código em uma linguagem de programação e descrever a lógica de dados e a lógica da interface do usuário uma vez, em um só lugar. Portanto, a necessidade de usar duas equipes de desenvolvimento desaparece. E, como resultado da compilação do projeto, obtemos dois aplicativos nativos na saída. E esta é a segunda abordagem.

imagem

Acho que muitos sabem o que é Xamarin, ou pelo menos ouviram falar dele, mas como funciona? O Xamarin é baseado na implementação de código aberto da plataforma .NET - Mono. O Mono inclui seu próprio compilador C #, tempo de execução e várias bibliotecas, incluindo a implementação do WinForms e ASP.Net.

O objetivo do projeto é permitir que programas escritos em C # sejam executados em sistemas operacionais diferentes dos sistemas Windows - Unix, Mac OS e outros. A estrutura do Xamarin, em essência, é uma biblioteca de classes que fornece aos desenvolvedores acesso ao SDK da plataforma e aos compiladores para eles. O Xamarin.Forms, por sua vez, permite que você não apenas escreva para as duas plataformas no mesmo idioma, mas também projete telas usando a marcação XAML, familiar para aqueles que já tinham experiência com aplicativos WPF. Como resultado da montagem do projeto, temos uma aparência quase idêntica em todas as plataformas, pois no estágio de compilação todos os controles XF são convertidos em nativos para cada plataforma.

imagem

O desenvolvedor é forçado a escrever código para cada plataforma apenas se for necessário acessar qualquer recurso da plataforma (por exemplo, um scanner de impressão digital ou o nível da bateria) ou se for necessário ajustar o comportamento do controle. Em alguns casos, ao desenvolver um aplicativo, pode ser necessário escrever código dependente da plataforma, mas mesmo nesse caso, ninguém proíbe levar funções da plataforma para a interface e interagir com ela a partir de um projeto comum no futuro.

Uma linguagem de programação, pouco código e assim por diante. Tudo parece bonito, mas Xamarin.Forms não é uma bala de prata e toda a sua beleza se transforma em realidade. Assim que surge uma situação em que os controles XF internos não atendem mais aos requisitos para eles, a estrutura de telas e controles se torna cada vez mais complicada. Para garantir um trabalho confortável com telas de um projeto comum, é necessário escrever cada vez mais renderizações personalizadas.

Isso passará para a terceira abordagem, que usamos no desenvolvimento de aplicativos.

Já descobrimos que o uso do Xamarin Forms pode complicar o trabalho, em vez de simplificá-lo. Portanto, para implementar telas arquiteturalmente complexas, elementos de design e controles fundamentalmente diferentes dos nativos, foi encontrado um compromisso e a possibilidade de combinar a primeira e a segunda abordagens.

Temos os mesmos três projetos: um projeto PCL comum, mas sem o Xamarin Forms, e dois projetos Xamarin Android e Xamarin iOS. Ainda há a oportunidade de escrever tudo em um idioma, lógica comum entre dois projetos, mas não há limitações de uma única marcação XAML. O componente da interface do usuário é controlado por cada plataforma e usa ferramentas nativas, no Android - AXML nativo, em arquivos iOS - XIB. Cada plataforma tem a capacidade de cumprir suas diretrizes, uma vez que a conexão entre os projetos Core e a plataforma é organizada apenas no nível de dados.

Para organizar esse relacionamento, você pode usar o padrão de design do MVVM e sua implementação bastante popular para o Xamarin - MVVMCross. Seu uso permite manter um ViewModel comum para cada tela, que descreve toda a "lógica de negócios" da obra, e confiar sua renderização à plataforma. Ele também permite que dois desenvolvedores trabalhem com a mesma tela (uma com lógica - a outra com interface do usuário) e não interfiram entre si. Além da implementação do padrão, obtemos um número suficiente de ferramentas para o trabalho: a implementação de DI e IoC. Para elevar a interação com a plataforma ao nível do código comum, um desenvolvedor precisa apenas declarar uma interface e implementá-la na plataforma. Para coisas típicas, o MvvmCross já fornece um conjunto de seus próprios plugins. Na equipe, usamos o plug-in messenger para trocar mensagens entre a plataforma e o código comum e o plug-in para trabalhar com arquivos (selecionar imagens da galeria etc.).

Resolvemos os problemas de design complexo e navegação em vários níveis


Como mencionado anteriormente, ao usar representações complexas na tela, a estrutura pode complicar a vida mais do que facilitar. Mas o que é chamado de elemento complexo? Como eu estou envolvido principalmente no desenvolvimento do iOS, um exemplo dessa plataforma será considerado. Por exemplo, algo trivial como um campo de entrada pode ter vários estados e lógica suficiente para alternar e visualizar.

imagem

No decorrer do trabalho com a entrada do usuário, esse controle de entrada foi desenvolvido aqui. Ele pode elevar seu nome acima do campo de entrada, trabalhar com máscaras, definir prefixos, pós-correções, notificar quando o CapsLock é pressionado, validar informações de dois modos: proibição de entrada e saída de informações de erro. A lógica dentro do controle leva aproximadamente ~ 1000 linhas. E, parece: o que pode ser complicado no design do campo de entrada?

Um exemplo simples de um controle complexo que vimos. E as telas?

imagem

Para começar, vou esclarecer que, na maioria dos casos, uma tela de aplicativo é uma classe - UIViewController, descrevendo seu comportamento. Durante o desenvolvimento, foi necessária a criação de navegação em vários níveis. O conceito do aplicativo desenvolvido se resume ao gerenciamento do seu imóvel e à interação com vizinhos e organizações municipais. Portanto, foram construídos três níveis de navegação: propriedade, nível de apresentação (casa, cidade, região) e tipo de conteúdo. Toda troca é realizada em uma tela.

Isso foi feito para que o usuário, onde quer que esteja, entenda que tipo de conteúdo ele vê. Para organizar essa navegação, a tela principal do aplicativo não consiste em apenas um controlador. Visualmente, ele pode ser dividido em 3 partes, mas alguém pode tentar adivinhar quantos controladores são usados ​​aqui?

imagem

Quinze controladores principais. E isso é apenas para conteúdo.

Aqui, esse monstro vive na tela principal e se sente muito bem. Quinze controladores para uma tela são, é claro, muito. Isso afeta a velocidade de todo o aplicativo e você precisa otimizá-lo de alguma forma.

Recusamos a inicialização síncrona: todos os modelos de visualização são inicializados em segundo plano e somente quando necessário. Para reduzir o tempo de renderização, também abandonamos os arquivos xib para essas telas: posicionamento absoluto e matemática são sempre mais rápidos que calcular as dependências entre os elementos.

Para acompanhar tantos controladores, você precisa entender:

  • Em que condição está cada um deles;
  • Onde está o usuário?
  • O que ele espera ver ao mudar para outro controlador.

Para fazer isso, escrevi um processador de navegação separado que armazena informações sobre a localização do usuário, o tipo de conteúdo que ele está visualizando, histórico de navegação etc. Ele controla a ordem e a necessidade de inicialização.

Como cada guia é um controle deslizante do controlador (para criar uma transição de furto nelas), é necessário entender: cada uma delas pode estar em seu próprio estado (por exemplo, “Notícias” está aberta em uma e “Votação” na outra). Isto é seguido pelo mesmo processador de navegação. Mesmo alterando o nível de apresentação de casa para a região, permaneceremos no mesmo tipo de conteúdo.

Controlamos o fluxo de dados em tempo real


Trabalhando com tantos dados no aplicativo, você precisa organizar a entrega de informações relevantes em todas as seções em tempo real. Para resolver esse problema, três métodos podem ser distinguidos:

  1. Acesse a API por temporizadores ou gatilhos e solicite novamente o conteúdo relevante nas telas;
  2. Ter uma conexão permanente com o servidor e receber alterações em tempo real;
  3. Receba push com alterações de conteúdo.

Cada abordagem tem seus prós e contras, portanto, é melhor usar os três, escolhendo apenas os pontos fortes de cada um. Dividimos condicionalmente o conteúdo dentro do aplicativo em vários tipos: quente, regular e serviço. Isso é feito para determinar o tempo aceitável entre o evento e a notificação do usuário. Por exemplo, queremos ver uma mensagem de bate-papo imediatamente depois que eles nos enviaram - este é um conteúdo quente. Outra opção: pesquisa com vizinhos. Não faz diferença quando o vemos, agora ou em um minuto, porque esse é um conteúdo comum. Pequenas notificações dentro do aplicativo (mensagens não lidas, comandos etc.) são conteúdo de serviço que precisa de entrega urgente, mas não consome muitos dados.

Acontece:

  • Conteúdo quente - conexão permanente com a API;
  • Conteúdo normal - solicitações de http para a API;
  • Conteúdo do sistema - notificações push.

O mais interessante é manter uma conexão constante. Escrever o seu próprio cliente para trabalhar com soquetes da Web é um passo na toca do coelho, então você precisa procurar outras soluções. Como resultado, paramos na biblioteca SignalR. Vamos ver o que é.

O ASP.Net SignalR é uma biblioteca da Microsoft que simplifica a interação cliente-servidor em tempo real, fornecendo comunicação bidirecional entre o cliente e o servidor. O servidor inclui uma API completa para gerenciar a conexão, eventos de desconexão de conexão, um mecanismo para combinar clientes conectados em grupos e autorização.

O SignalR pode usar solicitações de websockets, LongPolling e http como transporte. É possível especificar o tipo de transporte à força ou confiar na biblioteca: se o websocket puder ser usado, ele funcionará no websocket; se isso não for possível, será desativado até encontrar um transporte aceitável. Esse fato acabou sendo muito prático, pois está planejado usá-lo em dispositivos móveis.

Total, que benefício obtemos:

  • Capacidade de trocar mensagens de qualquer tipo entre o cliente e o servidor;
  • O mecanismo para alternar automaticamente entre soquetes da web, solicitações de pool longo e HTTP;
  • Informações sobre o status atual da conexão;
  • Uma oportunidade para unir clientes em grupos;
  • Métodos práticos para manipular a lógica do envio de mensagens em um grupo;
  • A capacidade de dimensionar o servidor.

Obviamente, isso não satisfaz todas as necessidades, mas notavelmente facilita a vida.

Dentro do projeto, um wrapper é usado na biblioteca SignalR, o que simplifica ainda mais o trabalho com ele, a saber:

  • Monitora o status da conexão, reconecta de acordo com as condições especificadas e em caso de interrupção;
  • Capaz de substituir ou reabrir rapidamente a conexão, matando de forma assíncrona a antiga e dando-a ao coletor de lixo para rasgar - como se viu, o método de conexão funciona dez vezes mais rápido que o método de fechamento (Dispose ou Stop), e esta é a única maneira de fechá-la;
  • Organiza uma fila para enviar mensagens para que a reconexão ou reabertura da conexão não interrompa o envio;
  • Transfere o controle para os delegados apropriados em caso de erros imprevistos.

Cada um desses invólucros (os chamamos de clientes) trabalha em conjunto com o sistema de armazenamento em cache e, no caso de uma desconexão, pode solicitar apenas os dados que podem ter sido perdidos durante esse período. "Cada" porque vários compostos ativos são mantidos simultaneamente. Dentro do aplicativo, existe um messenger completo e um cliente separado é usado para atendê-lo.

O segundo cliente é responsável por receber notificações. Como eu já disse, o tipo usual de conteúdo é obtido por meio de solicitações http, no futuro a atualização recai sobre esse cliente, que relata todas as mudanças importantes nele (por exemplo, a votação passou de um status para outro, publicação de novas notícias).

Visualize os dados no aplicativo


imagem

Obter dados é uma coisa, mostrar é outra. A atualização de dados em tempo real tem suas próprias dificuldades. No mínimo, você precisa decidir como apresentar essas atualizações ao usuário. No aplicativo, usamos três tipos de notificações:

  1. Notificação de conteúdo não lido;
  2. Atualização automática de dados na tela;
  3. Oferta de conteúdo.

A maneira mais familiar e comum de mostrar que em algum lugar existe um novo conteúdo é destacar o ícone da seção. Assim, quase todos os ícones têm a capacidade de mostrar o notificador de conteúdo não lido como um ponto vermelho. Coisas mais interessantes são as atualizações automáticas.

A atualização automática de dados é possível somente quando o novo conteúdo não reorganiza a tela e não altera o tamanho dos controles. Por exemplo, na tela da pesquisa: as informações sobre os votos mudarão apenas o valor da barra de progresso e as porcentagens. Tais alterações não implicam nenhum redimensionamento; podem ser aplicadas instantaneamente sem problemas.

As dificuldades surgem quando você precisa adicionar novo conteúdo às listas. Todas as listas no aplicativo são de fato ScrollView e possuem várias características: tamanho da janela, tamanho do conteúdo e posição da rolagem. Todos eles têm um começo estático (parte superior da tela com coordenadas 0; 0) e podem se expandir para baixo. Adicione novo conteúdo na lista, no final, não apresenta nenhum problema, a lista vai durar. Mas o novo conteúdo deve aparecer na parte superior, e esta é a imagem:

imagem

Sendo em 3 elementos, estaremos em 2 - o pergaminho saltará para cima. E como o novo conteúdo pode chegar constantemente, o usuário não poderá rolar normalmente. Você pode dizer: por que não calcular o tamanho do novo conteúdo e mudar a rolagem para baixo para esse valor? Sim, isso pode ser feito. Mas você precisa controlar manualmente a posição de rolagem e, nesse momento, o usuário rola em qualquer direção, sua ação será interrompida. É por isso que essas telas não podem ser atualizadas em tempo real sem o consentimento do usuário.

A melhor solução nessa situação seria informar o usuário que, enquanto ele rolava o feed, alguém postou novo conteúdo. Em nosso design, parece um círculo vermelho no canto da tela. Ao clicar nele, o usuário concede seu consentimento condicional para devolvê-lo à parte superior da tela e exibir novos conteúdos.

Com essa abordagem, é claro que evitamos os problemas de "manipular" o conteúdo, mas eles ainda precisavam ser resolvidos. Ou seja, na tela de bate-papo, pois durante a comunicação e interação com a tela, novos conteúdos devem ser exibidos em locais diferentes.

A diferença entre o bate-papo e as listas regulares é que o conteúdo novo está na parte inferior da tela. Como essa é uma "cauda", você pode adicionar conteúdo sem muita dificuldade. O usuário passa 90% do tempo aqui, o que significa que você precisa manter constantemente a posição de rolagem e reduzi-la ao receber e enviar mensagens. Em uma conversa ao vivo, essas ações devem ser realizadas com bastante frequência.

O segundo ponto: carregando o histórico enquanto rola para cima. Apenas ao carregar a história, nos encontramos em uma situação em que é necessário colocar as mensagens acima do nível da revisão (o que implica viés), para que a rolagem seja suave e contínua. E como já sabemos, para não incomodar o usuário, é impossível controlar manualmente a posição de rolagem.

E nós encontramos uma solução: nós a entregamos. O flip da tela resolveu dois problemas ao mesmo tempo:

  1. O final da lista está no topo, para que possamos adicionar uma história sem problemas, sem interferir na rolagem do usuário;
  2. A última mensagem está sempre no topo da lista e não precisamos rolar a tela antes dela.

imagem

Essa solução também ajudou a acelerar a renderização, eliminando operações desnecessárias com o controle de rolagem.

Falando em desempenho. Nas primeiras versões da tela, foram detectados rebaixamentos visíveis ao rolar as mensagens. «» – , , , — , . . , , .

iOS . , , , , , , , .

UI .

:


  • ;
  • , , ;
  • , ;
  • ;
  • SignalR – - ;
  • ;
  • , , ;
  • , SignalR-, , , , .

Source: https://habr.com/ru/post/pt430892/


All Articles