Annotation Processing no Android

Felipe Theodoro
Android Dev BR
Published in
3 min readJul 12, 2016

--

Um grande problema que encontramos quando desenvolvemos em Java é a repetição de código gerado em diferentes em partes do projeto. Alguns casos clássicos como lista paginada, bind de views, banco de dados, entre outros.

Esse tipo de problema é recorrente e faz com que nós desenvolvedores sempre caímos na tentação de repetir código, tornando a manutenção custosa.

Para solucionar esse problema o Java possuí uma API nativa e poderosa chamada Annotation Processing (Processamento por Anotação).

Mas afinal, o que isso faz?

Para entendermos sobre Annotation Processing, precisamos saber como o código Java é compilado.

Todo código Java é compilado para byte code, possibilitando que a nossa JVM consiga interpretá-lo.

Agora, imagine que consigamos fazer com que alguns códigos sejam criados em tempo de compilação e que possamos utilizá-los sem a necessidade de duplicação (ou até triplicação), e isso torna nossa vida muito mais fácil, concordam?

Ai que o Annotation Processing entra.

Antes de vermos a sua implementação, vale a pena listar algumas bibliotecas que utilizam o Annotation Processing, como por exemplo o Dagger 2, ButterKnife, squidb, PermissionsDispatcher, AutoValue entre outros.

Para iniciar, precisaremos de algumas dependências em nosso build.gradle e mais dois módulos diferentes, sendo um para o processor e o outro para annotation, totalizando três módulos.

No build.gradle do projeto (raiz) é necessário adicionar um plugin que faça com que seja habilitado o Annotation Processing no Android Studio:

classpath ‘com.neenbedankt.gradle.plugins:android-apt:1.8’

O build.gradle tanto no module annotation e module processor deverá ficar assim:

apply plugin: ‘java’targetCompatibility = JavaVersion.VERSION_1_7
sourceCompatibility = JavaVersion.VERSION_1_7

Um ponto interessante é que o Annotation Processor será executado na máquina onde estamos desenvolvendo o código e não no aparelho, por isso que plugin adicionado deverá ser 'java'!

Ainda, dentro do build.gradle do module processor, vamos adicionar as dependências:

//Referência para o module annotation
compile project (‘:annotation’)
// Viabiliza o processamento em tempo de compilação
compile ‘com.google.auto.service:auto-service:1.0-rc2’
// Anotações padrões
compile ‘org.glassfish:javax.annotation:10.0-b28’

Pronto! Agora, dentro do module annotation, nós podemos criar uma interface annotation para o nosso projeto, como no exemplo abaixo:

@Target(value = TYPE)
public @interface FooAnnotation{}

Na interface FooAnnotation somente será permitido sua utilização em classes. Nós podemos criar anotações para várias propriedades. Você pode ver aqui.

Após essas configurações, iremos direto para nosso processor.

Vamos criar uma classe MyProcessor.class:

@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor

Perceba que anotamos a nossa classe com @AutoService(Processor.class), que por sua vez habilita nosso processador para ser executado somente em tempo de compilação.

E herdamos da classe AbstractProcessor a qual será provida todo o ciclo e comportamento necessário para o nosso processador.

Tudo o que desenvolvemos dentro dessa classe será executado em tempo de compilação e não em runtime no Android, ou seja, será executado em nossa máquina de desenvolvimento e não no Android.

A classe AbstractProcessor requer implementação de 4 métodos abstratos:

public synchronized void init(ProcessingEnvironment processingEnv){}

Para todo Annotation Processing é preciso um construtor vazio, porém a classe AbstractProcessor disponibiliza o método init junto com o parâmetro ProcessingEnvironment, onde será provido Elements, Types e Filter.

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv){}

Esse método é como um “main()” para o Annotation Processing. Cada processor será executado e aqui poderão ser criados os códigos compilados. Com o parâmetro roundEnv, você poderá descobrir todas as classes anotadas que serão executadas por esse processador.

public SourceVersion getSupportedSourceVersion(){}

Você deverá especificar qual versão do Java será utilizada.

Normalmente será retornado SourceVersion.latestSupported() ou uma versão específica SourceVersion.RELEASE_6

public Set<String> getSupportedAnnotationTypes(){

Aqui, você retornará à annotation que esse processador deverá procurar. Como por exemplo:

singleton(FooAnnotation.class.getCanonicalName());

O método estático singleton da classe Collections somente retorna um objeto Set do tipo informado no caso FooAnnotation.

Conclusão

A utilização do Annotation Processing no Android nos trás grandes vantagens, na minha opinião evitar boilerPlates é uma grande sacada!

Outra grande vantagem é a possibilidade de adicionar arquiteturas avançadas, como por exemplo a Injeção de dependência sem degradar performance com Reflection.

E a possibilidade de criarmos bibliotecas para serem utilizadas pela comunidade. Features pontuais que auxiliam bastante no desenvolvimento.

Pronto!

Agora temos tudo para criar Annotation Processing no Android. Segue o link do meu Github com um exemplo pratico:

Segue também minha apresentação no SlideShare:

How it Works — Annotation Processing

Obrigado!

--

--