KEMBAR78
Annotation Processing in Android | PDF
ANNOTATION PROCESSING
IN ANDROIDEmanuele Zattin - Realm
@emanuelez
19-2-2016 - DroidKaigi
DroidKaigi
WHAT IS A JAVA
ANNOTATION?
LET'S START WITH THE OFFICIAL
DEFINITION
Annotations, a form of metadata, provide data about a
program that is not part of the program itself.
Annotations have no direct effect on the operation of the
code they annotate.
— Oracle
WHAT CAN ANNOTATIONS DO?
1. Provide information for the compiler
2. Allow runtime processing
3. Allow compile-time processing
HOW TO DEFINE AN ANNOTATION
The simplest annotation can be defined as:
public @interface MyAnnotation {}
and it can be used like this:
@MyAnnotation
public class MyClass {}
ANNOTATIONS CAN ACCEPT
ARGUMENTS
public @interface MyAnnotation {
String arg1();
int arg2 default 1;
String[] arg3;
}
Which can be used like this:
@MyAnnotation (
arg1 = "value1", // It's a comma, not a semicolon
arg3 = { "value2", "value3" } // Arrays use curly brackets
)
public class MyClass {}
ANNOTATIONS CAN BE ANNOTATED
The most important is @Retention which value can
be:
▸ RetentionPolicy.SOURCE
▸ RetentionPolicy.CLASS
▸ RetentionPolicy.RUNTIME
ANNOTATIONS CAN BE ANNOTATED
PART 2
The other important annotation is @Target which
value:
ElementType.ANNOTATION_TYPE
ElementType.CONSTRUCTOR
ElementType.FIELD
ElementType.LOCAL_VARIABLE
ElementType.METHOD
ANNOTATIONS CAN BE ANNOTATED
PART 3
Other useful annotations:
▸ @Documented
▸ @Inherited
▸ @Repeatable
AN EXAMPLE ANNOTATION
@Retention(RetentionPolicy.CLASS) // Available at compile-time
@Target(ElementType.TYPE) // Can only be applied to classes
@interface MyAnnotation {
String arg1();
int arg2 default 1;
String[] arg3;
}
WHAT IS AN
ANNOTATION
PROCESSOR?
Annotation Processing is a technique that provides
a hook into the Java compile process.
It allows to produce compiler errors and warnings
and to generate source code and byte code.
JSR 269
HOW DOES IT WORK?
Here's a high-level example:
1. Normal compilation
2. First round of annotation processing
3. Second round of annotation processing
4. ...
IMPLEMENTING A PROCESSOR
public abstract class AbstractProcessor implements Processor {
// more methods here!
void init(
ProcessingEnvironment processingEnv
);
abstract boolean process(
Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv
);
}
THE PROCESSING ENVIRONMENT
It provides the tools to write new files and access utility
classes.
Here are the most useful methods:
// Use Filer to write new files
Filer getFiler();
// Use Elements to manage fields, methods and classes
Elements getElementUtils();
// Use Types to deal with classes, converting Type to Element, ...
Types getTypeUtils();
THE ROUND ENVIRONMENT
It provides tools to deal with the specific round.
The most useful methods are:
// Get the elements annotated with a given annotation
Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a);
Set<? extends Element> getElementsAnnotatedWith(TypeElement a);
THE MOST USELESS ANNOTATION
public AnnoyingProcessor extends AbstractProcessor {
boolean process( Set<> annotations, RoundEnvironment env) {
Messager m = processingEnv.getMessager();
for (TypeElement te : annotations) {
for (Element e : env.getElementsAnnotatedWith(te)) {
m.printMessage(Diagnostic.Kind.NOTE, "Processing " + e.toString());
}
}
return true;
}
}
REGISTERING YOUR PROCESSOR
1. Package your processor in a Jar file
2. The Jar file must contain a file called
javax.annotation.processing.Proce
ssor
located in META-INF/services
3. This file must contain the fully
qualified name of your processor
GENERATING JAVA CODE IN YOUR AP
Enter JavaPoet
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(processingEnv.getFiler());
TESTING
Testing annotations processors is hard because the
execution happens at compile time
SOME MUCH NEEDED HELP!
Google's compile-testing library
EXAMPLE 1
assert_() // it uses the Truth testing library
.about(javaSource())
.that(
JavaFileObjects.forSourceString(
"HelloWorld", "final class HelloWorld {}"
)
).compilesWithoutError();
EXAMPLE 2
assert_().about(javaSource())
.that(JavaFileObjects.forResource("HelloWorld.java"))
.processedWith(new MyAnnotationProcessor())
.compilesWithoutError()
.and()
.generatesSources(
JavaFileObjects.forResource("GeneratedHelloWorld.java"));
HOW ABOUT
ANNOTATION
PROCESSING IN
ANDROID?
PROBLEM 1
The Android framework does not include
javax.annotation.processing
SOLUTION
Setup your AP to be a Java module
and to inform your users to use
the Gradle android-apt plugin by Hugo Visser
PROBLEM 2
Both your library and your AP might depend on the
annotations
SOLUTION
Setup your annotations to be another Java module
on which both the library and the AP depend on
PROBLEM 3
Now the Javadoc of your library does not include the
annotations
SOLUTION
android.libraryVariants.all { variant ->
task("javadoc${variant.name.capitalize()}", type: Javadoc) {
description "Generates Javadoc for $variant.name."
group 'Docs'
source = variant.javaCompile.source
source "../annotations/src/main/java" // <-- THIS!
ext.androidJar = files(project.android.getBootClasspath())
classpath = files(variant.javaCompile.classpath.files)
+ ext.androidJar
exclude '**/BuildConfig.java'
exclude '**/R.java'
}
}
ANDROID LIBRARIES THAT USE AP
▸ ButterKnife
▸ Dagger
▸ Parceler
▸ Realm
Thank you!
Questions?

Annotation Processing in Android

  • 1.
    ANNOTATION PROCESSING IN ANDROIDEmanueleZattin - Realm @emanuelez 19-2-2016 - DroidKaigi
  • 2.
  • 3.
    WHAT IS AJAVA ANNOTATION?
  • 4.
    LET'S START WITHTHE OFFICIAL DEFINITION Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate. — Oracle
  • 5.
    WHAT CAN ANNOTATIONSDO? 1. Provide information for the compiler 2. Allow runtime processing 3. Allow compile-time processing
  • 6.
    HOW TO DEFINEAN ANNOTATION The simplest annotation can be defined as: public @interface MyAnnotation {} and it can be used like this: @MyAnnotation public class MyClass {}
  • 7.
    ANNOTATIONS CAN ACCEPT ARGUMENTS public@interface MyAnnotation { String arg1(); int arg2 default 1; String[] arg3; } Which can be used like this: @MyAnnotation ( arg1 = "value1", // It's a comma, not a semicolon arg3 = { "value2", "value3" } // Arrays use curly brackets ) public class MyClass {}
  • 8.
    ANNOTATIONS CAN BEANNOTATED The most important is @Retention which value can be: ▸ RetentionPolicy.SOURCE ▸ RetentionPolicy.CLASS ▸ RetentionPolicy.RUNTIME
  • 9.
    ANNOTATIONS CAN BEANNOTATED PART 2 The other important annotation is @Target which value: ElementType.ANNOTATION_TYPE ElementType.CONSTRUCTOR ElementType.FIELD ElementType.LOCAL_VARIABLE ElementType.METHOD
  • 10.
    ANNOTATIONS CAN BEANNOTATED PART 3 Other useful annotations: ▸ @Documented ▸ @Inherited ▸ @Repeatable
  • 11.
    AN EXAMPLE ANNOTATION @Retention(RetentionPolicy.CLASS)// Available at compile-time @Target(ElementType.TYPE) // Can only be applied to classes @interface MyAnnotation { String arg1(); int arg2 default 1; String[] arg3; }
  • 12.
  • 13.
    Annotation Processing isa technique that provides a hook into the Java compile process. It allows to produce compiler errors and warnings and to generate source code and byte code.
  • 14.
  • 15.
    HOW DOES ITWORK? Here's a high-level example: 1. Normal compilation 2. First round of annotation processing 3. Second round of annotation processing 4. ...
  • 16.
    IMPLEMENTING A PROCESSOR publicabstract class AbstractProcessor implements Processor { // more methods here! void init( ProcessingEnvironment processingEnv ); abstract boolean process( Set<? extends TypeElement> annotations, RoundEnvironment roundEnv ); }
  • 17.
    THE PROCESSING ENVIRONMENT Itprovides the tools to write new files and access utility classes. Here are the most useful methods: // Use Filer to write new files Filer getFiler(); // Use Elements to manage fields, methods and classes Elements getElementUtils(); // Use Types to deal with classes, converting Type to Element, ... Types getTypeUtils();
  • 18.
    THE ROUND ENVIRONMENT Itprovides tools to deal with the specific round. The most useful methods are: // Get the elements annotated with a given annotation Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a); Set<? extends Element> getElementsAnnotatedWith(TypeElement a);
  • 19.
    THE MOST USELESSANNOTATION public AnnoyingProcessor extends AbstractProcessor { boolean process( Set<> annotations, RoundEnvironment env) { Messager m = processingEnv.getMessager(); for (TypeElement te : annotations) { for (Element e : env.getElementsAnnotatedWith(te)) { m.printMessage(Diagnostic.Kind.NOTE, "Processing " + e.toString()); } } return true; } }
  • 20.
    REGISTERING YOUR PROCESSOR 1.Package your processor in a Jar file 2. The Jar file must contain a file called javax.annotation.processing.Proce ssor located in META-INF/services 3. This file must contain the fully qualified name of your processor
  • 21.
    GENERATING JAVA CODEIN YOUR AP Enter JavaPoet
  • 22.
    MethodSpec main =MethodSpec.methodBuilder("main") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class) .addParameter(String[].class, "args") .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") .build(); TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(main) .build(); JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld) .build(); javaFile.writeTo(processingEnv.getFiler());
  • 23.
    TESTING Testing annotations processorsis hard because the execution happens at compile time
  • 24.
    SOME MUCH NEEDEDHELP! Google's compile-testing library
  • 25.
    EXAMPLE 1 assert_() //it uses the Truth testing library .about(javaSource()) .that( JavaFileObjects.forSourceString( "HelloWorld", "final class HelloWorld {}" ) ).compilesWithoutError();
  • 26.
  • 27.
  • 28.
    PROBLEM 1 The Androidframework does not include javax.annotation.processing
  • 29.
    SOLUTION Setup your APto be a Java module and to inform your users to use the Gradle android-apt plugin by Hugo Visser
  • 30.
    PROBLEM 2 Both yourlibrary and your AP might depend on the annotations
  • 31.
    SOLUTION Setup your annotationsto be another Java module on which both the library and the AP depend on
  • 32.
    PROBLEM 3 Now theJavadoc of your library does not include the annotations
  • 33.
    SOLUTION android.libraryVariants.all { variant-> task("javadoc${variant.name.capitalize()}", type: Javadoc) { description "Generates Javadoc for $variant.name." group 'Docs' source = variant.javaCompile.source source "../annotations/src/main/java" // <-- THIS! ext.androidJar = files(project.android.getBootClasspath()) classpath = files(variant.javaCompile.classpath.files) + ext.androidJar exclude '**/BuildConfig.java' exclude '**/R.java' } }
  • 34.
    ANDROID LIBRARIES THATUSE AP ▸ ButterKnife ▸ Dagger ▸ Parceler ▸ Realm
  • 35.
  • 36.