Android

1 set, 2017

Descompactando o Android Bundle com extensões AutoValue na Uber Engineering

Publicidade

O Android Bundle é um objeto conveniente e, por vezes, necessário para que os dados empacotados sejam passados através dos process boundaries e enviados com intents, bem como para armazenar o estado entre as mudanças de configuração. Infelizmente, o envio de dados empacotados em um Bundle geralmente requer código repetitivo e propenso a erros. O que torna isso ainda pior é que um Bundle anexado a uma intention do GCM (Google Cloud Messaging) perde informações de tipo porque o GCM serializa os dados como pares de chave-valor String-String.

Ao investigar uma solução para eliminar a unbundling dos objetos Bundled GCM para o aplicativo de motorista da Uber para Android, percebemos que não só os dados fornecidos geralmente são apenas de leitura, mas também utilizados para preencher os dados mostrados em uma notificação e determinar a lógica do negócio correspondente para intenções pendentes. Esses objetos são exemplos principais de classes de valor; classes que são finais, imutáveis e possuem implementações de equals, hashCode e toString com base apenas no estado da instância.

Gerar classes de valor é simples usando o AutoValue, que leva uma classe abstrata que define seus campos pelo seu tipo de retorno do método getter e names e, em seguida, gera uma classe de valor concreta que é uma subclasse da interface. O AutoValue também fornece um framework para extensões encadeando-as. Abaixo, fornecemos um exemplo do framework AutoValue para encadear as extensões:

 

final class AutoValue_BundledObject extends $AutoValue_BundledObject { 
   // Unbundle implementation
}

final class $AutoValue_BundledObject extends BundledObject { 
   // AutoValue value type implementation
}

@AutoValue
public abstract class BundledObject { 
   static BundledObject create(…) {
       return new AutoValue_BundledObject(…);
   }
   // Abstract properties
}

 

Apesar de existir uma extensão AutoValue para lidar com o código Parcelable repetitivo, não havia uma para Bundles. Para corrigir isso na Uber, criamos e tornamos open source o AutoValue: Bundle Extension. Neste artigo, discutimos como nossa nova ferramenta separa os dados e lida com interferências de tipo, permitindo que os engenheiros do Android separem rapidamente os dados em uma classe de valor. Essa ferramenta diminui a probabilidade de encontrar erros e código iterativo, bem como a perda de informações de tipo, melhorando, por sua vez, a experiência global do aplicativo para os usuários do Android.

 

Separando os dados

Nosso AutoValue: Bundle Extension será executado se a classe base abstrata for anotada com @AutoValue e existir um método estático público que retorna o tipo de classe com dois parâmetros: o bundle e uma instância do Gson. Quando ele é executado, a extensão gera uma classe de valor típica, mas adiciona um método de separação estática que leva o bundle e a instância do Gson como parâmetros e retorna uma instância da classe anotada, como mostrado no exemplo abaixo:

 

@AutoValue
public abstract class Foo {

   public static Foo create(Bundle bundle, Gson gson) {
       return AutoValue_Foo.unbundle(bundle, gson);
   }

   public abstract String bar();
}

 

Como mostrado acima, o AutoValue geralmente chama o construtor de sua subclasse para criar uma nova instância passando valores para todos os campos. No entanto, usamos um método estático porque queremos apenas aceitar dois parâmetros, o bundle e o desserializador, o Gson. O método de separação irá lidar com isso adequadamente, chamando os métodos de obtenção adequados no bundle.

No exemplo acima, ele chamará bundle.getString (“bar”). Semelhante às classes padrão do AutoValue, o tipo de retorno e o nome do parâmetro são usados; no entanto, aqui eles determinam que método chamamos no bundle e que String passamos como um parâmetro. Os nomes dos métodos devem ser escritos em lower camel case (fooBar) e serão convertidos para nomes de parâmetros em snake case (foo_bar). Embora essa seja uma limitação rígida, é importante se lembrar disso ao nomear seus parâmetros, especialmente se os dados forem fornecidos a partir de uma fonte externa.

 

GCM e type inference

A versão simples acima lida com o caso em que os dados são armazenados no bundle como seu tipo apropriado; no entanto, como mencionado anteriormente, o GCM serializa todos os dados em pares String-String. Para lidar com isso, a extensão inclui uma anotação de nível de classe @GCMBundle que lê todos os dados do bundle como uma String e, em seguida, usa primitive type parsing ou a instância de Gson para converter para o tipo de retorno apropriado. Ele pode até mesmo lidar com objetos parametrizados, como um ArrayList <Foo>.

A instância Gson será usada para separar outros tipos de objetos, obtendo o objeto do bundle como uma String e usando um TypeToken para desserializá-lo. Além disso, a classe gerada contém vários métodos helpers sobrescritos, “toPrimitive” para converter arrays Byte, Short, Char e Float em seus corolários de arrays primitivos. Isso ocorre porque o Gson não pode ler arrays de tipos primitivos de uma Json String sem um desserializador personalizado. Por exemplo, se um array de bytes for armazenado em uma string GCM Json, a extensão geraria:

 

public static Test unbundle(Bundle bundle, Gson gson) {
  return new AutoValue_Test(toPrimitive(gson.fromJson(bundle.getString(“byte_array”), Byte[].class))
}

public static byte[] toPrimitive(Byte[] byteParam) {
   byte[] bytes = new byte[byteParam.length];
   for (int i = 0; i < byteParam.length; i++) {
       bytes[i] = byteParam[i];
   }
   return bytes;
}

 

Próximos passos

À medida que continuamos aumentando as capacidades do AutoValue: Bundle Extension, as possíveis adições incluem:

  • Novos desserializadores: atualmente, a extensão suporta a desserialização usando apenas o Gson; no entanto, ela foi construída para também suportar várias classes de desserializadores. Para fazer isso, empacotamos a desserialização de objetos por trás de uma interface Deserializer que gera o código específico do desserializador. Planejamos adicionar suporte para novos desserializadores, como o Moshi, atualizando o método aplicável na classe de extensão e criando uma nova classe Deserializer que escreve o código específico;
  • Especificação do caso de parâmetro: adicionar suporte para especificação de caso de parâmetro permitiria uma maior flexibilidade para o desenvolvedor. Um método possível para realizar isso seria usar o CaseFormat de Guava como um parâmetro para a anotação.

AutoValue: Bundle Extension é uma solução que construímos para gerar código que, de outra forma, seria demorado e propenso a erros. Adicione-o ao seu projeto, simplesmente incluindo AutoValue e a extensão como processadores de anotação em suas dependências, e se você quiser usar o @GCMBundle, também inclua a extensão conforme fornecido.

Para aprender mais sobre os esforços open source da Uber Engineering, vá à nossa página no GitHub.

 

***

 

Este artigo é do Uber Engineering. Ele foi escrito por Zachary Sweigart. A tradução foi feita pela Redação iMasters com autorização. Você pode conferir o original em: https://eng.uber.com/autovalue-bundle-extension/