More and more third-party libraries use APT technology, such as DBflow, Dagger2, ButterKnife, ActivityRouter, and AptPreferences. The relevant code is generated according to annotations during compilation, which is a very elegant but simple technology that can bring great convenience to development.

Annotation

If you want to learn APT, then it must first understand the basis of the Annotation, here attached my another article address: www.taoweiji.cn/2016/07/18/…

APT

APT(Annotation Processing Tool) is a Tool for Processing annotations. It detects the annotations in source code files and uses annotations to perform additional Processing. When processing annotations, the Annotation processor can generate additional source files and other files according to the annotations in source files (the specific content of files is determined by the Annotation processor writer). APT will also compile the generated source files and original source files. Put them together to generate a class file.

To create an Annotation Module

First, we need to create a New Java Library called Annotations, which will place annotations and associated code that will be used in the project. Here is a simple custom annotation:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS) 
public @interface Test {   } 
Copy the code
Build. Gradle is configured to specify the JDK version
Apply plugin: 'Java' sourceCompatibility = 1.7 targetCompatibility = 1.7 dependencies {compile fileTree(dir: 'libs', include: ['*.jar']) }Copy the code

Create a Compiler Module

Create a Java Library called Compiler that will write the associated code generated by the code. The core is right here.

To configure the build. Gradle
Apply plugin: 'Java' sourceCompatibility = 1.7 targetCompatibility = 1.7 dependencies {compile fileTree(dir: 'libs', include: [' *. Jar ']) compile 'com. Google. Auto. Services: auto - service: 1.0 rc2' compile 'com. Squareup: javapoet: 1.7.0 compile project(':annotation') }Copy the code
  1. It is important to define the compiled JDK version as 1.7, otherwise an error will be reported.
  2. The main function of AutoService is to annotate the Processor class and generate meta-INF configuration information for it.
  3. The main purpose of the JavaPoet library is to help generate code in the form of class calls.
  4. Rely on the Annotation Module created above. #### the logic that defines the code generated by the Processor class is here.
@AutoService(Processor.class) public class TestProcessor extends AbstractProcessor { @Override public Set getSupportedAnnotationTypes() { return Collections.singleton(Test.class.getCanonicalName()); } @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { return false; }}Copy the code
Generate the first class

We are going to generate the following HelloWorld code:

package com.example.helloworld;

public final class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, JavaPoet!");
  }
}
Copy the code

Modify the TestProcessor process method above

@Override public boolean process(Set annotations, RoundEnvironment roundEnv) { 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(); try { javaFile.writeTo(processingEnv.getFiler()); } catch (IOException e) { e.printStackTrace(); } return false; }Copy the code

Use it in the app

Configure build.gradle in the project root directory
Dependencies {classpath 'com. Neenbedankt. Gradle. Plugins: android - apt: 1.8'}Copy the code
Configure build.gradle for your app
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
//...
dependencies {
    //..
    compile project(':annotation')
    apt project(':compiler')
}
Copy the code
Compile using the

Add the @test annotation to a random class

@Test
public class MainActivity extends AppCompatActivity {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
   }
}

Copy the code

Click the Android Studio ReBuild Project, can build in the app/generated/source/apt directory, you can see the generated code.

Annotation-based View injection: DIActivity

So far we haven’t used annotations and @test above is not actually used, so let’s do some more practical code generation. Implement annotation-based View instead of findByView in the project. This is just to learn how to use APT. If you really want to use DI framework, ButterKnife is recommended. 1. First, create @diActivity and @DiView annotations in the Annotation Module.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface DIActivity {

}
Copy the code
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DIView {
    int value() default 0;
}
Copy the code
@AutoService(Processor.class) public class DIProcessor extends AbstractProcessor { private Elements elementUtils; @ Override public Set getSupportedAnnotationTypes () {/ / annotations need to deal with the return Collections.singleton(DIActivity.class.getCanonicalName()); } @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { System.out.println("DIProcessor"); Set elements = roundEnv.getElementsAnnotatedWith(DIActivity.class); For (Element: elements) {Class TypeElement TypeElement = (TypeElement) Element; List members = elementUtils.getAllMembers(typeElement); MethodSpec.Builder bindViewMethodSpecBuilder = MethodSpec.methodBuilder("bindView") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(TypeName.VOID) .addParameter(ClassName.get(typeElement.asType()), "activity"); for (Element item : members) { DIView diView = item.getAnnotation(DIView.class); if (diView == null){ continue; } bindViewMethodSpecBuilder.addStatement(String.format("activity.%s = (%s) activity.findViewById(R.id.text)",item.getSimpleName(),ClassName.get(item.asType()).toString())); } TypeSpec typeSpec = TypeSpec.classBuilder("DI" + element.getSimpleName()) .superclass(TypeName.get(typeElement.asType())) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(bindViewMethodSpecBuilder.build()) .build(); JavaFile javaFile = JavaFile.builder(getPackageName(typeElement), typeSpec).build(); try { javaFile.writeTo(processingEnv.getFiler()); } catch (IOException e) { e.printStackTrace(); } } return true; } private String getPackageName(TypeElement type) { return elementUtils.getPackageOf(type).getQualifiedName().toString(); } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); elementUtils = processingEnv.getElementUtils(); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.RELEASE_7; }}Copy the code
@DIActivity
public class MainActivity extends Activity {
    @DIView(R.id.text)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DIMainActivity.bindView(this);
        textView.setText("Hello World!");
    }
}
Copy the code

In effect, apt generates the following code

public final class DIMainActivity extends MainActivity { public static void bindView(MainActivity activity) { activity.textView = (android.widget.TextView) activity.findViewById(R.id.text); }}Copy the code

Commonly used method

Common Element subclasses
  1. TypeElement: class
  2. ExecutableElement: Member method
  3. VariableElement: Member variable
Get the TypeName by package name and class name

TypeName targetClassName = ClassName.get(“PackageName”, “ClassName”);

Get the TypeName from Element

TypeName type = TypeName.get(element.asType());

Gets the package name of the TypeElement

String packageName = processingEnv.getElementUtils().getPackageOf(type).getQualifiedName().toString();

Gets all of TypeElement’s member variables and member methods

List members = processingEnv.getElementUtils().getAllMembers(typeElement);

conclusion

Dagger2, DBFlow, ButterKnife and other apt-based open source projects are recommended. JavaPoet also has many examples to learn from.

The Example code

Github.com/taoweiji/De…

Our open source projects recommend:

Android Rapid Persistence framework: AptPreferences

AptPreferences is a fast persistence framework designed to simplify the use of SharePreferences and reduce the writing of code. You can save basic types and objects very quickly. AptPreferences is based on APT technology, which implements code generation during compilation and distinguishes persistent information according to different users.

Github.com/joyrun/AptP…

ActivityRouter Routing framework: Annotates the URL to open an Activity

Based on APT technology, the function of opening Activity through URL can be realized through annotations, and it can be used in WebView and external browser. It supports multi-level Activity jump, Bundle and Uri parameter injection and parameter type conversion.

Github.com/joyrun/Acti…