“This article has participated in the call for good writing activities, click to view: the back end, the big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!”
Today we first on the demo code, and then in APT technology to introduce.
What is the APT
APT(Annotation Processing Tool) is literally Annotation processor, should be related to annotations, Annotation Processing Tool. By scanning and processing annotations at compile time. The annotation processor then generates Java code from the annotations. This is simply a.java file generated by annotations at compile time. Do you want to think about metaprogramming?
How to use APT in a project
The goal of the Demo
The goal is to see how APT helps us automatically generate code at compile time through its own view binding feature. Collect some information online and do it yourself. It sounds like a Butter Knife pair that implements something like Butter Knife.
View binding has recently been implemented using Binding. I’ve moved a bit away from ButterKnife, but I still have fond memories of the library and its clean and concise API.
Create a project
Create an empty Android project, and then add modules to the project. Each module implements different functions in APT, so that is to distinguish modules by functions.
Create a module
- Note that this is a (Java module) in which you can create the ZiViewBind annotation
- Create the ZI-API (Android module) to provide the user can call the API, which implements the view binding.
- Zi-compiler (Java module) is used to write annotation processing, and zi-API, which is an Android model, is a compilation module type that you need to specify, and then a Java module called Zi-Annotation, Used in this module to create annotations
No. | Module name | Module type | instructions |
---|---|---|---|
1 | zi-annotation | Java module | In this module, you can create annotations |
2 | zi-api | Android module | Provides an API that users can call |
3 | zi-compiler | Java module | Write annotation processing |
Android Studio automatically introduces the created modules in the Setting file when we create them.
rootProject.name='zi application'
include ':app'
include ':zi-annotation'
include ':zi-api'
include ':zi-compiler'
Copy the code
Dealing with dependencies
These modules are also dependent on each other. First, configure them in build.gradle(ZI-Compiler) file as follows
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs'.include: ['*.jar'])
implementation 'com. Google. Auto. Services: auto - service: 1.0 rc2'
implementation 'com. Squareup: javapoet: 1.10.0'
implementation project(':zi-annotation')
}
sourceCompatibility = "Seven"
targetCompatibility = "Seven"
Copy the code
- Pay attention to
zi-compiler
Depends on thezi-annotation
The module
Implementation 'com. Google. Auto. Services: auto - service: 1.0 rc2' implementation 'com. Squareup: javapoet: 1.10.0'Copy the code
Tip: Add the newly created modules to the main project, and note that the ZI-Compiler is annotationProcesso because the main job of the ZI-Compiler is to process annotations to generate Java code at compile time
dependencies {
...
implementation project(':zi-annotation')
implementation project(':zi-api')
annotationProcessor project(':zi-compiler')}Copy the code
Now that the modules are created, added, and dependent on each other, all that remains is to add code to those modules. Here we are talking about the Android project construction is still quite tedious, fortunately Android Studio such a tool to help us complete a lot of work, and give us a certain guide to build Android.
Zi – the annotation module
Create the ZiBindView annotation
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface ZiBindView {
}
Copy the code
@Target specifies the range of objects that the Annotation is modifying
value | instructions |
---|---|
CONSTRUCTOR | Used to describe a constructor |
FIELD | Used to describe domains |
LOCAL_VARIABLE | Used to describe local variables |
METHOD | Used to describe methods |
PACKAGE | Describe packages |
PARAMETER | Describe parameters |
TYPE | Used to describe a class, interface (including annotation type), or enum declaration |
Zi – compiler module
Annotations that create ZiBindViewProcessor and ClassCreatorProxy are not covered in the code are not explained here
@Retention defines how long the Annotation is retained: some annotations only appear in source code and are discarded by the compiler; Others are compiled in class files; Annotations compiled in a class file may be ignored by the virtual machine, while others will be read when the class is loaded (note that class execution is not affected, since annotations and classes are used separately). Using this meta-annotation, you can limit the “life cycle” of an Annotation.
Annotation processors need to inherit from AbstractProcessor, where some code writing is basically fixed.
public class ZiBindViewProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false; }}Copy the code
GetSupportedSourceVersion getSupportedAnnotationTypes () returns the annotation of support types () returns the source version of support
@Override
public Set<String> getSupportedAnnotationTypes(a) {
HashSet<String> supportTypes = new LinkedHashSet<>();
// Get the class name
supportTypes.add(ZiBindView.class.getCanonicalName());
return supportTypes;
}
Copy the code
Here, getName() returns a representation of the class in the virtual machine, while getCanonicalName() returns a more understandable representation. There is no difference between the two methods for most classes. But there is a difference for array or inner classes. In addition, class loading (virtual machine loading) requires the class name to be getName.
@Override
public SourceVersion getSupportedSourceVersion(a) {
return SourceVersion.latestSupported();
}
Copy the code
To collect information
“Information collection” means that according to our declaration, we get the corresponding Element, and then annotate to collect the required information, which is used for the later production of objects
Production proxy class (A production proxy class that produces text into classes at compile time, ClassCreatorProxy
An annotation class is produced for each
Initialize the
public interface ProcessingEnvironment {
Elements getElementUtils(a);
Types getTypeUtils(a);
Filer getFiler(a);
Locale getLocale(a);
Messager getMessager(a);
Map<String, String> getOptions(a);
SourceVersion getSourceVersion(a);
}
Copy the code
ZiBindViewProcessor complete code
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({"zidea.example.com.zi_compiler.ZiBindViewProcessor"})
@AutoService(Processor.class)
public class ZiBindViewProcessor extends AbstractProcessor {
// Log related helper classes
private Messager mMessager;
// An element helper class to help us get information about the element.
private Elements mElementUtils;
private Map<String,ClassCreatorProxy> mProxyMap = new HashMap<>();
// Get Elements using the ProcessingEnvironment object
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mMessager = processingEnvironment.getMessager();
mElementUtils = processingEnvironment.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes(a) {
HashSet<String> supportTypes = new LinkedHashSet<>();
// Get the class name
supportTypes.add(ZiBindView.class.getCanonicalName());
return supportTypes;
}
@Override
public SourceVersion getSupportedSourceVersion(a) {
return SourceVersion.latestSupported();
}
// Collect information
// The so-called information collection means that we get the corresponding Element according to our declaration, and then annotate it to collect the information needed for later production of objects
// Production proxy class (At compile time, the production proxy class ClassCreatorProxy, which produces text into classes, produces an annotation class for each
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// Output information
mMessager.printMessage(Diagnostic.Kind.NOTE,"processing...");
// Follow the annotations to get the required information
GetElementsAnnotatedWith is used to get the Element object of the annotation ZiBindView
Set<? extends Element> elements =roundEnvironment.getElementsAnnotatedWith(ZiBindView.class);
ZiBindView = ZiBindView = ZiBindView
for (Element element : elements){
VariableElement variableElement = (VariableElement) element;
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
String fullClassName = classElement.getQualifiedName().toString();
ClassCreatorProxy proxy = mProxyMap.get(fullClassName);
if(proxy == null){
proxy = new ClassCreatorProxy(mElementUtils,classElement);
mProxyMap.put(fullClassName,proxy);
}
ZiBindView bindAnnotation = variableElement.getAnnotation(ZiBindView.class);
int id = bindAnnotation.value();
proxy.putElement(id,variableElement);
}
for(String key:mProxyMap.keySet()){
ClassCreatorProxy proxyInfo = mProxyMap.get(key);
JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(),proxyInfo.generateJavaCode2()).build();
try {
javaFile.writeTo(processingEnv.getFiler());
}catch (IOException e){
e.printStackTrace();
}
}
mMessager.printMessage(Diagnostic.Kind.NOTE,"process finish ...");
return true; }}Copy the code
for(String key:mProxyMap.keySet()){
ClassCreatorProxy proxyInfo = mProxyMap.get(key);
JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(),proxyInfo.generateJavaCode2()).build();
try {
javaFile.writeTo(processingEnv.getFiler());
}catch(IOException e){ e.printStackTrace(); }}Copy the code
- Returns the filer used to create the new class
ClassCreatorProxy complete code
public class ClassCreatorProxy {
// The name of the bound class
private String mBindingClassName;
// The name of the package to bind
private String mPackageName;
private TypeElement mTypeElement;
private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();
public ClassCreatorProxy(Elements elementUtils, TypeElement classElement){
this.mTypeElement = classElement;
PackageElement packageElement = elementUtils.getPackageOf(mTypeElement);
String packageName = packageElement.getQualifiedName().toString();
String className = mTypeElement.getSimpleName().toString();
this.mPackageName = packageName;
this.mBindingClassName = className + "_ViewBinding";
}
public void putElement(int id, VariableElement element){
mVariableElementMap.put(id,element);
}
public String generateJavaCode(a){
StringBuilder builder = new StringBuilder();
builder.append("package ").append(mPackageName).append("; \n\n");
builder.append("import com.example.gavin.apt_library.*; \n");
builder.append('\n');
builder.append("public class ").append(mBindingClassName);
builder.append(" {\n");
generateMethods(builder);
builder.append('\n');
builder.append("}\n");
return builder.toString();
}
private void generateMethods(StringBuilder builder) {
builder.append("public void bind(" + mTypeElement.getQualifiedName() + " host ) {\n");
for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id);
String name = element.getSimpleName().toString();
String type = element.asType().toString();
builder.append("host." + name).append("=");
builder.append("(" + type + ")(((android.app.Activity)host).findViewById( " + id + ")); \n");
}
builder.append(" }\n");
}
public String getProxyClassFullName(a) {
return mPackageName + "." + mBindingClassName;
}
public TypeElement getTypeElement(a) {
return mTypeElement;
}
//JavaPoet is a build utility class framework for automatically generating Java files that makes it easy to build code at compile time based on what we annotate.
public TypeSpec generateJavaCode2(a) {
TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethods2())
.build();
return bindingClass;
}
private MethodSpec generateMethods2(a) {
ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(host, "host");
for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id);
String name = element.getSimpleName().toString();
String type = element.asType().toString();
methodBuilder.addCode("host." + name + "=" + "(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));");
}
return methodBuilder.build();
}
public String getPackageName(a){
returnmPackageName; }}Copy the code
Zi – API module
public class ZiBindViewTools {
public static void bind(Activity activity) {
Class clazz = activity.getClass();
try {
Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
Method method = bindViewClass.getMethod("bind", activity.getClass());
method.invoke(bindViewClass.newInstance(), activity);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch(InvocationTargetException e) { e.printStackTrace(); }}}Copy the code
Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
Method method = bindViewClass.getMethod("bind", activity.getClass());
method.invoke(bindViewClass.newInstance(), activity);
Copy the code
We’re through
We have finished the APT part of the code, so how to use we create ZiBindView? Use the butterKnife in the same way. ```java public class MainActivity extends AppCompatActivity { @ZiBindView(value = R.id.main_activity_textView) TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ZiBindViewTools.bind(this); textView.setText("Zidea"); }}Copy the code
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
Copy the code
However, when we run it, we may not be able to live the code because the gradle version is too high and we need to create it manually.
- Start by switching the directory from Android to the Project view
- in
zi-compiler
Create the following structure under the directoryresources/META-INF/services
File directory - Then add a question under the directory
file
Add the following content to file
So we just created ZiBindViewProcessor package name plus file name.