Annotation processing
in Android
Randy Hu @ 91 APP
Library use Annotation
Processor
• AndroidAnnotation
• http://androidannotations.org/
• ButterKnife
• https://github.com/JakeWharton/butterknife
• Dagger
• http://square.github.io/dagger/
Library (not) use Annotation
Processor
• Retrofit
• http://square.github.io/retrofit/
• galette
• https://github.com/uPhyca/GAlette
Annotation
@Target(ElementType.METHOD)

@Retention(RetentionPolicy.SOURCE)

public @interface Override {

}
• Target
• Where can annotate
• Retention
• Where to store
Target
• TYPE
• FIELD
• METHOD
• PARAMETER
• CONSTRUCTOR
• LOCAL_VARIABLE
• ANNOTATION_TYPE
• PACKAGE
Retention
• SOURCE
• CLASS
• RUNTIME
• used in Reflection
• java.lang.reflect.AnnotatedElement
Parameters
@Retention(RetentionPolicy.SOURCE)

@Target(ElementType.TYPE)

public @interface TypeAnnotation {

String[] uris() default {};

int count() default 0;

String value() default "";

AnnotationEnum enu() default AnnotationEnum.Enum1;

}
Usage:
@TypeAnnotation(uris={"MainActivity", "1"}, value="a", count=20)

public class MainActivity extends ActionBarActivity {
......
}
Write Annotation
Processor ( with your bare hand )
Modules
• annotations
• Annotations can be used in app & annotation-
processor
• app
• main
• processor
• process annotations annotated in app
Dependency - top level
buildscript {

repositories {

jcenter()

}

dependencies {

classpath 'com.android.tools.build:gradle:1.2.3'

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'

}

}

Dependency - processor
dependencies {

...

compile project(':annotation')
}
Dependency - app
apply plugin: 'com.android.application'

apply plugin: 'android-apt'
...
...
dependencies {
...

apt project(':annotationprocessor')

compile project(':annotation')

}
annotations
@Retention(RetentionPolicy.SOURCE)

@Target(ElementType.METHOD)

public @interface MethodAnnotation {

String parameterName();

String parameterValue();

}
app
@MethodAnnotation(parameterName = "input", parameterValue = "inputValue")

public void stringContact(String input) {



}
processor - basic setting
// Supported Annotation
@SupportedAnnotationTypes({"me.lunacat.annotations.MethodAnnotation"})

public class AnnotationProcessor extends AbstractProcessor {
private Filer filer;

private Messager messager;
@Override

public synchronized void init(ProcessingEnvironment env){

super.init(env);

filer = processingEnv.getFiler(); // File instance

messager = processingEnv.getMessager(); // Logger

}
@Override

public SourceVersion getSupportedSourceVersion() {

return SourceVersion.latestSupported();

}
// processing function
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
roundEnv) {}
}
processor- gather annotations
@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
ArrayList<MethodAnnotation> methodAnnotations = new ArrayList<>();

for (Element e : roundEnv.getElementsAnnotatedWith(MethodAnnotation.class))
{

MethodAnnotation annot = e.getAnnotation(MethodAnnotation.class);

methodAnnotations.add(annot);

}
generateJavaFile(methodAnnotations);
}
processor - generate code
private void generateJavaFile(ArrayList<MethodAnnotation> methodAnnotations) throws
IOException {
// create ExtraClass with annotation content
JavaFileObject source = filer.createSourceFile(“in.test.AnnotationGenClass”);

Writer writer = source.openWriter();

try {

PrintWriter pw = new PrintWriter(writer);

pw.println("package in.test;");

pw.println("public class AnnotationGenClass {");

for(MethodAnnotation annot: methodAnnotations) {

pw.println("public static final void " + annot.parameterName() + "() {}");

}

pw.println("}");

pw.flush();

pw.close();

}

finally {

writer.close();

}
}
processor - generate resources
private void generateJavaFile(ArrayList<MethodAnnotation> methodAnnotations) throws
IOException {
// create method.txt with annotation contents
FileObject file = filer.createResource(
StandardLocation.SOURCE_OUTPUT,

"",
"methods.txt",
null);


writer = file.openWriter();

try {

PrintWriter pw = new PrintWriter(writer);

for(MethodAnnotation annot: methodAnnotations) {

pw.println(annot.parameterName());

}

pw.flush();

pw.close();

}

finally {

writer.close();

}
}
processor - create meta-data
create file: javax.annotation.processing.Processor
file content: me.lunacat.AnnotationProcessor
Write Annotation
Processor ( with tools )
Generate Code - filer
private void generateJavaFile(ArrayList<MethodAnnotation> methodAnnotations) throws
IOException {
// create ExtraClass with annotation content
JavaFileObject source = filer.createSourceFile(“in.test.AnnotationGenClass”);

Writer writer = source.openWriter();

try {

PrintWriter pw = new PrintWriter(writer);

pw.println("package in.test;");

pw.println("public class AnnotationGenClass{");

for(MethodAnnotation annot: methodAnnotations) {

pw.println("public static final void " + annot.parameterName() + "() {}");

}

pw.println("}");

pw.flush();

pw.close();

}

finally {

writer.close();

}
}
Generate Code - JavaPoet
for(MethodAnnotation annot: methodAnnotations) {

MethodSpec dumpMethod = MethodSpec

.methodBuilder(annot.parameterName())

.addModifiers(Modifier.FINAL, Modifier.STATIC, Modifier.PUBLIC)

.returns(void.class)

.build();

dumpMethods.add(dumpMethod);

}

// create class

TypeSpec.Builder classBuilder = TypeSpec

.classBuilder("AnnotationGenClass")

.addModifiers(Modifier.PUBLIC);

if ( dumpMethods != null ) {

classBuilder.addMethods(dumpMethods);

}

JavaFile.builder("me.lunacat.annotationgent", classBuilder.build())

.build()

.writeTo(filer);
with dependencies
dependencies {

compile 'com.squareup:javapoet:1.1.0'
}
MetaData - bare hand
create file: java.annotation.processing.Processor
file content: me.lunacat.AnnotationProcessor
MetaData - Auto
• Auto
• https://github.com/google/auto
@AutoService(Processor.class)

public class AnnotationProcessor extends AbstractProcessor {
}
With dependency:
dependencies {
compile 'com.google.auto.service:auto-service:1.0-rc2'

}
Testing
JavaFileObject sampleActivity = JavaFileObjects

.forSourceString("SampleActivity", "package com.example;"

+ "import me.lunacat.annotations.TypeAnnotation; "

+ "@Type("airbnb://example.com/deepLink") public class SampleActivity {}”);
// sampleActivity with AnnotationProcessor with generate source like
assert_().about(javaSource())

.that(sampleActivity)

.processedWith(new AnnotationProcessor())

.compilesWithoutError()

.and()

.generatesSources(

JavaFileObjects.forResource("AnnotationGenClass.java"),

JavaFileObjects.forSourceString("AnnotationGenClass",

"package.."));
with dependencies
dependencies {
testCompile 'com.google.testing.compile:compile-testing:0.6'
}
Debug
Debug
messager.printMessage(Diagnostic.Kind.WARNING, "no annot found");
Pass Parameter to
Processor
Pass Parameter to Processor
apt {
arguments{
host "http://www.123456789.host/"
}
}
build.gradle (app)
Processor
public synchronized void init(ProcessingEnvironment env) {

String host = env.getOptions().get("host");
}
References
• Annotation 101
• http://brianattwell.com/android-annotation-processing-
pojo-string-generator/
• http://hannesdorfmann.com/annotation-processing/
annotationprocessing101/#processing-rounds
• Debug Annotation Processor
• https://turbomanage.wordpress.com/2014/06/09/
debug-an-annotation-processor-with-intellij-and-gradle/

Annotation processing in android

  • 1.
  • 3.
    Library use Annotation Processor •AndroidAnnotation • http://androidannotations.org/ • ButterKnife • https://github.com/JakeWharton/butterknife • Dagger • http://square.github.io/dagger/
  • 4.
    Library (not) useAnnotation Processor • Retrofit • http://square.github.io/retrofit/ • galette • https://github.com/uPhyca/GAlette
  • 5.
    Annotation @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.SOURCE)
 public @interface Override{
 } • Target • Where can annotate • Retention • Where to store
  • 6.
    Target • TYPE • FIELD •METHOD • PARAMETER • CONSTRUCTOR • LOCAL_VARIABLE • ANNOTATION_TYPE • PACKAGE
  • 7.
    Retention • SOURCE • CLASS •RUNTIME • used in Reflection • java.lang.reflect.AnnotatedElement
  • 8.
    Parameters @Retention(RetentionPolicy.SOURCE)
 @Target(ElementType.TYPE)
 public @interface TypeAnnotation{
 String[] uris() default {};
 int count() default 0;
 String value() default "";
 AnnotationEnum enu() default AnnotationEnum.Enum1;
 } Usage: @TypeAnnotation(uris={"MainActivity", "1"}, value="a", count=20)
 public class MainActivity extends ActionBarActivity { ...... }
  • 9.
    Write Annotation Processor (with your bare hand )
  • 10.
    Modules • annotations • Annotationscan be used in app & annotation- processor • app • main • processor • process annotations annotated in app
  • 11.
    Dependency - toplevel buildscript {
 repositories {
 jcenter()
 }
 dependencies {
 classpath 'com.android.tools.build:gradle:1.2.3'
 classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
 }
 }

  • 12.
    Dependency - processor dependencies{
 ...
 compile project(':annotation') }
  • 13.
    Dependency - app applyplugin: 'com.android.application'
 apply plugin: 'android-apt' ... ... dependencies { ...
 apt project(':annotationprocessor')
 compile project(':annotation')
 }
  • 14.
  • 15.
    app @MethodAnnotation(parameterName = "input",parameterValue = "inputValue")
 public void stringContact(String input) {
 
 }
  • 16.
    processor - basicsetting // Supported Annotation @SupportedAnnotationTypes({"me.lunacat.annotations.MethodAnnotation"})
 public class AnnotationProcessor extends AbstractProcessor { private Filer filer;
 private Messager messager; @Override
 public synchronized void init(ProcessingEnvironment env){
 super.init(env);
 filer = processingEnv.getFiler(); // File instance
 messager = processingEnv.getMessager(); // Logger
 } @Override
 public SourceVersion getSupportedSourceVersion() {
 return SourceVersion.latestSupported();
 } // processing function public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {} }
  • 17.
    processor- gather annotations @Override
 publicboolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { ArrayList<MethodAnnotation> methodAnnotations = new ArrayList<>();
 for (Element e : roundEnv.getElementsAnnotatedWith(MethodAnnotation.class)) {
 MethodAnnotation annot = e.getAnnotation(MethodAnnotation.class);
 methodAnnotations.add(annot);
 } generateJavaFile(methodAnnotations); }
  • 18.
    processor - generatecode private void generateJavaFile(ArrayList<MethodAnnotation> methodAnnotations) throws IOException { // create ExtraClass with annotation content JavaFileObject source = filer.createSourceFile(“in.test.AnnotationGenClass”);
 Writer writer = source.openWriter();
 try {
 PrintWriter pw = new PrintWriter(writer);
 pw.println("package in.test;");
 pw.println("public class AnnotationGenClass {");
 for(MethodAnnotation annot: methodAnnotations) {
 pw.println("public static final void " + annot.parameterName() + "() {}");
 }
 pw.println("}");
 pw.flush();
 pw.close();
 }
 finally {
 writer.close();
 } }
  • 19.
    processor - generateresources private void generateJavaFile(ArrayList<MethodAnnotation> methodAnnotations) throws IOException { // create method.txt with annotation contents FileObject file = filer.createResource( StandardLocation.SOURCE_OUTPUT,
 "", "methods.txt", null); 
 writer = file.openWriter();
 try {
 PrintWriter pw = new PrintWriter(writer);
 for(MethodAnnotation annot: methodAnnotations) {
 pw.println(annot.parameterName());
 }
 pw.flush();
 pw.close();
 }
 finally {
 writer.close();
 } }
  • 20.
    processor - createmeta-data create file: javax.annotation.processing.Processor file content: me.lunacat.AnnotationProcessor
  • 21.
  • 22.
    Generate Code -filer private void generateJavaFile(ArrayList<MethodAnnotation> methodAnnotations) throws IOException { // create ExtraClass with annotation content JavaFileObject source = filer.createSourceFile(“in.test.AnnotationGenClass”);
 Writer writer = source.openWriter();
 try {
 PrintWriter pw = new PrintWriter(writer);
 pw.println("package in.test;");
 pw.println("public class AnnotationGenClass{");
 for(MethodAnnotation annot: methodAnnotations) {
 pw.println("public static final void " + annot.parameterName() + "() {}");
 }
 pw.println("}");
 pw.flush();
 pw.close();
 }
 finally {
 writer.close();
 } }
  • 23.
    Generate Code -JavaPoet for(MethodAnnotation annot: methodAnnotations) {
 MethodSpec dumpMethod = MethodSpec
 .methodBuilder(annot.parameterName())
 .addModifiers(Modifier.FINAL, Modifier.STATIC, Modifier.PUBLIC)
 .returns(void.class)
 .build();
 dumpMethods.add(dumpMethod);
 }
 // create class
 TypeSpec.Builder classBuilder = TypeSpec
 .classBuilder("AnnotationGenClass")
 .addModifiers(Modifier.PUBLIC);
 if ( dumpMethods != null ) {
 classBuilder.addMethods(dumpMethods);
 }
 JavaFile.builder("me.lunacat.annotationgent", classBuilder.build())
 .build()
 .writeTo(filer); with dependencies dependencies {
 compile 'com.squareup:javapoet:1.1.0' }
  • 24.
    MetaData - barehand create file: java.annotation.processing.Processor file content: me.lunacat.AnnotationProcessor
  • 25.
    MetaData - Auto •Auto • https://github.com/google/auto @AutoService(Processor.class)
 public class AnnotationProcessor extends AbstractProcessor { } With dependency: dependencies { compile 'com.google.auto.service:auto-service:1.0-rc2'
 }
  • 26.
    Testing JavaFileObject sampleActivity =JavaFileObjects
 .forSourceString("SampleActivity", "package com.example;"
 + "import me.lunacat.annotations.TypeAnnotation; "
 + "@Type("airbnb://example.com/deepLink") public class SampleActivity {}”); // sampleActivity with AnnotationProcessor with generate source like assert_().about(javaSource())
 .that(sampleActivity)
 .processedWith(new AnnotationProcessor())
 .compilesWithoutError()
 .and()
 .generatesSources(
 JavaFileObjects.forResource("AnnotationGenClass.java"),
 JavaFileObjects.forSourceString("AnnotationGenClass",
 "package..")); with dependencies dependencies { testCompile 'com.google.testing.compile:compile-testing:0.6' }
  • 27.
  • 28.
  • 29.
  • 30.
    Pass Parameter toProcessor apt { arguments{ host "http://www.123456789.host/" } } build.gradle (app) Processor public synchronized void init(ProcessingEnvironment env) {
 String host = env.getOptions().get("host"); }
  • 31.
    References • Annotation 101 •http://brianattwell.com/android-annotation-processing- pojo-string-generator/ • http://hannesdorfmann.com/annotation-processing/ annotationprocessing101/#processing-rounds • Debug Annotation Processor • https://turbomanage.wordpress.com/2014/06/09/ debug-an-annotation-processor-with-intellij-and-gradle/