Wechat official account: Android Research Institute focuses on learning more about Android, focusing on the mobile field. Questions or suggestions, please leave a message on the public account; If you find the article helpful, you are welcome to give compliments [^1]
[TOC]
APT,
Why is APT so important? Nowadays, more and more third-party libraries use APT technology, such as Dagger2, ButterKnife, ARouter, etc. During compilation, relevant code logic is generated according to annotation, and dynamic generation of Java class files brings great convenience to development.
The first step is to understand the basics of annotation.
APT full name :Annotation Processing Tool can be interpreted as Annotation processor, which detects the source code file to find out the annotations therein, and uses the specified Annotation to perform additional Processing. When an Annotation handler processes an Annotation, it can generate additional source files and other files based on the annotations in the source file (the content of the file is determined by the Annotation handler’s writer). APT also compiles the generated source file and the original source file. Together, you generate a class file.
Here is an example of how APT works. This example implements Gradle version 3.0.1
- Create a New Java Library called AnnotationLib, and note that it’s Java Library, not Android library
@target (elementType.type) @retention (retentionPolicy.class) public @interface Test {String path(); }Copy the code
Build. Gradle configuration
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])}sourceCompatibility = "1.7"
targetCompatibility = "1.7"
Copy the code
- Create a Java library named TestCompiler
Note gradle configuration
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':annotationlib')
implementation 'com. Google. Auto. Services: auto - servic: 1.0 rc4'
implementation 'com. Squareup: javapoet: 1.11.1'
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
Copy the code
Com. Google. Auto. Services: auto – service: 1.0 rc4 is registered Processor
Com. Squareup: javapoet: 1.11.1 is a third-party libraries generate class files
Define Processor class
@supportedSourCeversion (SourceVersion.release_7) // Specify annotations to be processed @SupportedAnnotationTypes({"demo.prim.com.annotationlib.Test"})
public class MyClass extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment RoundEnvironment) {// Create MethodSpec main = MethodSpec. MethodBuilder ("main")
.addModifiers(Modifier.PUBLIC,Modifier.STATIC)
.addParameter(String[].class,"args")
.addStatement("$T.out.println($S)",System.class,"Hello JavaPoet") .build(); // Create TypeSpec classtypeSpec = TypeSpec.classBuilder("HelloWord") .addModifiers(Modifier.PUBLIC,Modifier.FINAL) .addMethod(main) .build(); JavaFile file = Javafile.builder ()"com.ecample.test".typeSpec)
.build();
try {
file.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
return true; }}Copy the code
The above code generates the following class
public final class HelloWord {
public static void main(String[] args) {
System.out.println("Hello JavaPoet"); }}Copy the code
- To use this in your app, note that the following configuration is required if gradle version is earlier than 3.0.0
Configure build.gradle dependencies {classpath in the project root directory'com. Neenbedankt. Gradle. Plugins: android - apt: 1.8'
}
Copy the code
Configure app's build.gradle 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 @test (path =) to any class"main")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); }}Copy the code
Clicking Rebuild Project in Android Studio will generate the following files:
This is when the HelloWord class is called.
APT of actual combat
TextView = findViewById(R.I.D.extView); .
In general, it can be written like this:
public class ManagerFindByMainActivity { public void findById(MainActivity activity) { activity.textView = activity.findViewById(R.id.textView); }}Copy the code
In the above code, it is the same as before, instead of creating a new class to find the ID for each Activity. So can we dynamically generate this class with apt?
The answer is yes, of course.
- Create two annotations in the Annotation lib
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface DIActivity {
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BYView {
int value() default 0;
}
Copy the code
- Create the processor in compiler lib
Gradle configuration:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com. Google. Auto. Services: auto - service: 1.0 rc4'
implementation 'com. Squareup: javapoet: 1.11.1'
implementation project(':lib-annotation')}sourceCompatibility = "1.7"
targetCompatibility = "1.7"
Copy the code
Core code implementation:
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({Constance.DIACTIVITY})
public class ByProcessor extends AbstractProcessor {
private Elements elementUtils;
private Types typeUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementUtils = processingEnvironment.getElementUtils();
typeUtils = processingEnvironment.getTypeUtils();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set! = null) { Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(DIActivity.class);if(elementsAnnotatedWith ! = null) {// Get the node on which the DIActivity annotation is set // Determine whether the annotated node is an Activity TypeElementtypeElement = elementUtils.getTypeElement(Constance.Activity);
for (Element element : elementsAnnotatedWith) {
TypeMirror typeMirror = element.asType(); DIActivity annotation = Element.getannotation (diActivity.class); // Get the annotation informationif (typeUtils.isSubtype(typeMirror, typeElemement.astype ())) {// annotate the Activity class TypeElement classElement = (TypeElement) Element; // Create parameters Map<String,Class<? extends IRouteGroup>>> routes ParameterSpec altlas = ParameterSpec .builder(ClassName.get(typeMirror), "activity")// The parameter name.build(); MethodSpec.Builder method = MethodSpec. MethodBuilder ()"findById") // .addAnnotation(Override.class) .addModifiers(PUBLIC, STATIC) .returns(TypeName.VOID) .addParameter(altlas); // Create the body of the function // get all the member variables of the TypeElement and the member methods List<? extends Element> allMembers = elementUtils.getAllMembers(classElement); / /?? // Iterate over the member variablesfor(Element member: allMembers) {// Find the member variable annotated by BYView BYView = member.getannotation (byview.class);if (byView == null) {
continue; } method. AddStatement (String. Format (String."activity.%s = (%s) activity.findViewById(%s)"Classname.get (member.astype ()).toString(),// the type of the node variable byView.value()); } // Create class TypeSpectypeSpec = TypeSpec.classBuilder("ManagerFindBy"+ element.getsimplename ()).addmodifiers (PUBLIC, FINAL)// scope.addmethod (method.build())// addMethod.build(); // Create Javaclass file JavaFile JavaFile = Javafile.builder ()"com.prim.find.by".typeSpec).build(); try { javaFile.writeTo(processingEnv.getFiler()); } catch (IOException e) { e.printStackTrace(); }}else {
throw new IllegalArgumentException("@DIActivity must of Activity"); }}}return true;
}
return false; }}Copy the code
- Use annotations in an Activity
implementation project(':lib_annotation')
annotationProcessor project(':lib_compiler')
Copy the code
@DIActivity
public class MainActivity extends AppCompatActivity {
@BYView(R.id.textView)
public TextView textView;
@BYView(R.id.textView1)
public TextView textView1;
@BYView(R.id.textView2)
public TextView textView2;
@BYView(R.id.button)
public Button button;
}
@DIActivity
public class SencodActivity extends AppCompatActivity {
@BYView(R.id.sencodText)
public TextView sencodText;
}
Copy the code
And then RebuildProject, you get:
- Use the generated classes
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ManagerFindByMainActivity.findById(this);
textView.setText("APT found it.");
textView1.setText("I found it in APT --> 1");
textView2.setText("I found it in APT --> 2");
button.setText("I was found by APT and I need to jump.");
}
public void click(View view) {
Intent intent = new Intent(this, SencodActivity.class);
startActivity(intent);
}
Copy the code
The project address
Commonly used method
Common Element subclasses
-
TypeElement: class
-
ExecutableElement: member methods
-
VariableElement: member variable
-
TypeName TypeName targetClassName = className. get(” PackageName “, “ClassName”);
-
TypeName TypeName type = typenname.get (element.astype ());
-
Obtaining TypeElement package name String packageName = processingEnv. GetElementUtils () getPackageOf (type). GetQualifiedName (). The toString ();
-
Get all member variables and member methods of a TypeElement List<? extends Element> members = processingEnv.getElementUtils().getAllMembers(typeElement);
Refer to the article
Android componentization topic: Componentization Configuration
APT of actual combat
Principles of Routing Framework
Service communication between modules
The following is my public qr code, welcome to follow.
If you think that Android Research is helpful to you, welcome to praise, with your support, Android Research will be better and better!