The annotations in Java are a magical thing that you don't know yet. You can see the custom annotations (Annotation) for the next hour. Now a lot of Android libraries are implemented using annotations, such as butterknife, we do not want to learn, we learn the note processor, we try to write a simple similar to the butterknife of things to bind the control.
What is an annotation processor?
The annotation processor is (Annotation Processor) is a tool for Javac to scan and compile and process annotations (Annotation) at compile time. You can define annotations and annotation handlers yourself to do something. An annotation processor It takes the Java code or (compiled bytecode) as input to generate the file (usually a Java file). These generated Java files cannot be modified and will be javac compiled in the same way as Java code that they have written manually. See here Add before understanding, should understand the general process, is to mark the annotated class, variables and so on as input, through the annotation processor processing, generate the Java code you want to generate.
Processor Abstractprocessor
The processor is written in a fixed routine, inheriting abstractprocessor. As follows:
public class Myprocessor extends Abstractprocessor { @Override public synchronized void Init ( Processingenvironment processingenv) { super.init (processingenv); } @Override public set<string> getsupportedannotationtypes () { return null; } @Override public sourceversion getsupportedsourceversion () { return sourceversion.latestsupported (); } @Override Public boolean process (set<? extends typeelement> annotations, roundenvironment roundenv) { return true; }}
- Init (processingenvironment processingenv) is called by the annotation processing tool, and the parameter processingenvironment provides tools such as Element,filer,messager
- Getsupportedannotationtypes () specifies that the annotation processor is registered to that annotation, which is a collection of strings, meaning that multiple types of annotations can be supported, and that the string is a legal full name.
- Getsupportedsourceversion specifying the Java version
- Process (SET< extends typeelement> annotations, roundenvironment roundenv) This is also the main, where you scan and process your annotations and generate Java code, The information is in the parameter roundenvironment, which will be introduced later.
You can also use the JAVA7 in the
@SupportedSourceVersion (sourceversion.latestsupported ()) @SupportedAnnotationTypes ( collection of {//Legal annotations full name})
Instead of Getsupportedsourceversion () and Getsupportedannotationtype (), there is no problem, and annotations can be used in the annotation processing device.
Registering a note processor
A special file is required to package the annotation processor Javax.annotation.processing.Processor under the meta-inf/services path
--myprcessor.jar----com------example--------myprocessor.class----meta-inf------services--------javax.annotation.processin G.processor
The content that is packaged into Javax.annotation.processing.Processor is the legal full name of the processor and wraps between multiple processors.
Com.example.myprocess.MyProcessorAcom.example.myprocess.MyProcessorB
Google provides a library of registered processors
Compile ' com.google.auto.service:auto-service:1.0-rc2 '
An annotation is done:
@AutoService (processor.class) public class Myprocessor extends Abstractprocessor { ...}
Read about the Butterknife we've got here.
1. Custom annotations
2. Interpreting annotations with the annotation processor
3. Generate Java files after parsing is complete
Bufferknife using:
public class Mainactivity extends Appcompatactivity { @Bind (r.id.rxjava_demo) Button Mrxjavademo; @Override protected void onCreate (Bundle savedinstancestate) { super.oncreate (savedinstancestate); Setcontentview (r.layout.activity_main); Butterknife.bind (this); Mrxjavademo.settext ("Text");} }
Then we compile, open the path:/app/build/intermediates/classes/release/com/ming/rxdemo/mainactivity$ $ViewBinder. Class
This is the Java file we generated and we can see that the button has been initialized in bind.
public class mainactivity$ $ViewBinder <t extends mainactivity> implements viewbinder<t> {public mainactivity$ $ViewBinder () { } public void bind (Finder finder, T target, Object source) { View view = (view) fi Nder.findrequiredview (source, 2131492944, "field \ ' mrxjavademo\ '"); Target.mrxjavademo = (Button) Finder.castview (view, 2131492944, "field \ ' mrxjavademo\ '"); } public void Unbind (T target) { Target.mrxjavademo = null; }}
Next we create a project to write a simple example of binding a control with annotations
Project structure
--apt-demo----bindview-annotation (Java Library)----bindview-api (Android Library)----bindview-compiler (Java Library)----app (Android app)
- Bindview-annotation Note Declaration
- Bindview-api calling the Android SDK API
- Bindview-compiler Annotation Processor Related
- App Test App
1. Create a @bindview annotation under Bindview-annotation, which returns a value, integer, named value, which is used to represent the control ID.
@Target (Elementtype.field) @Retention (retentionpolicy.class) public @interface BindView { /** * To install ID * * @return * /int value ();}
2. Create the annotation processor Bindviewprocessor and register in Bindview-compiler to do basic initialization work.
@AutoService (processor.class) public class Bindviewprocessor extends Abstractprocessor {/** * file related auxiliary class */PR Ivate Filer Mfiler; /** * element-related auxiliary class */private Elements melementutils; /** * Log-related auxiliary class */private Messager Mmessager; /** * parsed target annotation set */private map<string, annotatedclass> mannotatedclassmap = new hashmap<> (); @Override public synchronized void init (Processingenvironment processingenv) {super.init (processingenv); Melementutils = Processingenv.getelementutils (); Mmessager = Processingenv.getmessager (); Mfiler = Processingenv.getfiler (); } @Override Public set<string> getsupportedannotationtypes () {set<string> types = new Linkedhash Set<> (); Types.add (BindView.class.getCanonicalName ());//returns the set of annotations supported by the annotation processor return types; } @Override Public SourceVersion getsupportedsourceversion () {return sourceversion.latestsupported (); } @Override public Boolean process (SET<? extends typeelement> annotations, roundenvironment roundenv) { return true; }}
Did you notice that there is a map container in it, and the type is annotatedclass, what is this? This is a good understanding, we parse the XML, parsing JSON when the data parsing is not to be expressed in the form of objects, here is the same, @BindView to tag class members, a class can have more than one member, like an activity can have more than one control, There are multiple controls under a container, and so on. As follows:
Package Com.mingwei.myprocess.model;import Com.mingwei.myprocess.typeutil;import Com.squareup.javapoet.ClassName; Import Com.squareup.javapoet.javafile;import Com.squareup.javapoet.methodspec;import Com.squareup.javapoet.parameterizedtypename;import Com.squareup.javapoet.typename;import Com.squareup.javapoet.typespec;import Java.util.arraylist;import Java.util.list;import Javax.lang.model.element.modifier;import Javax.lang.model.element.typeelement;import javax.lang.model.util.elements;/** * Created by Mingwei on 12/10/16. * csdn:http://blog.csdn.net/u013045971 * Github:https://github.com/gumingwei */public class AnnotatedClass {/** * Class Name * * Public typeelement mclasselement; /** * Member Variable Collection */public list<bindviewfield> mfiled; /** * Element Auxiliary class */public Elements melementutils; Public Annotatedclass (Typeelement classelement, Elements elementutils) {this.mclasselement = classelement; This.melementutils = elementutils; this.mfiled = new arraylist<> (); /** * Gets the full name of the current class */public String Getfullclassname () {return mclasselement.getqualifiedname (). toSt Ring (); }/** * Add a member */public void AddField (Bindviewfield field) {Mfiled.add (field); }/** * Output Java */public javafile Generatefinder () {return null; }/** * Package name */public String getpackagename (typeelement type) {return melementutils.getpackageof (type ). Getqualifiedname (). toString (); }/** * Class name * * private static string GetClassName (typeelement type, String packagename) {int Packagel En = packagename.length () + 1; Return Type.getqualifiedname (). toString (). substring (packagelen). Replace ('. ', ' $ '); }}
Members are expressed in Bindviewfield, with no complicated logic, in the constructor to determine the type and initialization of the simple get function
Package Com.mingwei.myprocess.model;import Com.mingwe.myanno.bindview;import javax.lang.model.element.Element; Import Javax.lang.model.element.elementkind;import Javax.lang.model.element.name;import Javax.lang.model.element.variableelement;import javax.lang.model.type.typemirror;/** * Created by Mingwei on 12/10/ * csdn:http://blog.csdn.net/u013045971 * Github:https://github.com/gumingwei * Model class for fields marked by BindView annotation */public CLA SS Bindviewfield {private Variableelement mfieldelement; private int mresid; Public Bindviewfield (element element) throws IllegalArgumentException {if (Element.getkind ()! = Elementkind.field) {//Determine if class member throw new IllegalArgumentException (String.Format ("Only field can is annotated with @%s", BindView.class.getSimpleName ())); } mfieldelement = (variableelement) element; Get annotations and values BindView BindView = mfieldelement.getannotation (Bindview.class); Mresid = Bindview.value (); If (Mresid < 0) {throw new IllegalArgumentException (String.Format ("value () in%s for field% is not valid", BindView.class.getSimpleName (), Mfieldelement.getsimplename ())); }} public Name GetFieldName () {return mfieldelement.getsimplename (); } public int Getresid () {return mresid; } public Typemirror GetFieldType () {return mfieldelement.astype (); }}
There is a lot of element here, which is the concept of element in XML parsing. There is also an element concept in the Java source file:
Package com.example; Packageelementpublic class MyClass { //typeelement private int A; Variableelement private Foo other; Variableelement public Foo () {} //executeableelement public void SetA ( //Executeableelement int Newa //Typeelement ) { }}
The next step is to parse the annotations in the processor's process.
Clear each time before parsing, because the process method may not go more than once.
Traversal calls generate Java code after getting the annotation model
@Override Public Boolean process (set<? extends typeelement> annotations, roundenvironment roundenv) { Mannotatedclassmap.clear (); try { Processbindview (roundenv); } catch (IllegalArgumentException e) { error (E.getmessage ()); return true; } try { for (Annotatedclass annotatedClass:mAnnotatedClassMap.values ()) { info ("Generating file for%s", Annotatedclass.getfullclassname ()); Annotatedclass.generatefinder (). WriteTo (Mfiler); } } catch (Exception e) { e.printstacktrace (); Error ("Generate file failed,reason:%s", E.getmessage ()); } return true; }
Processbindview and Getannotatedclass
/** * Traversal Target roundenviroment * @param roundenv */private void Processbindview (Roundenvironment roundenv) {for (Element Element:roundEnv.getElementsAnnotatedWith (Bindview.class)) {Annotatedclass ANNOTATEDCL The Getannotatedclass (element); Bindviewfield field = new Bindviewfield (element); Annotatedclass.addfield (field); }}/** * if it exists directly in the map, does not exist on the new out of the map * @param element */private Annotatedclass Getannotatedclass ( Element Element) {Typeelement encloseelement = (typeelement) element.getenclosingelement (); String FullClassName = Encloseelement.getqualifiedname (). toString (); Annotatedclass Annotatedclass = Mannotatedclassmap.get (FullClassName); if (Annotatedclass = = null) {Annotatedclass = new Annotatedclass (encloseelement, melementutils); Mannotatedclassmap.put (FullClassName, Annotatedclass); } return Annotatedclass; }
3. Before generating Java, we will create some classes in Bindview-api to work with Bindview-compiler.
When you use Butterknife, you're not going to drop a bindview.bind (this) in OnCreate, what is this thing for? Imagine that a lot of work in front of you to build the Java code of the automatic bound control, if the generated Java code can not be associated with the place you want to use, it is useless, you can interpret Bindview.bind (this) to call the Java code you generated, And the generated code has done some initialization work for the controls, and naturally your control becomes available.
Interface: Finder defines Findview method
Implementation class: Used in Activityfinder activity, used in viewfinder view
Interface: The Injector inject method is to be created in the generated Java file, using the parameters passed in the method to initialize the control.
Helper classes: Viewinjector calling and passing parameters
This code I do not post, just a little bit of content, a look on the understanding.
4. Generating Java code in Annotatedclass
The generated code uses a handy library Javapoet. Classes, methods, can be built using the builder, very good to get started, no longer have to splice strings. haha haha ~
Public Javafile Generatefinder () {//build method Methodspec.builder Injectmethodbuilder = Methodspec.methodbuilder ( "Inject"). Addmodifiers (Modifier.public)//Add description. addannotation (Override.class)//Add annotations . Addparameter (Typename.get (Mclasselement.astype ()), "host", modifier.final)//Add parameter. Addparameter (Type Name.object, "source")//Add parameter. Addparameter (Typeutil.finder, "FINDER");//Add parameter for (Bindviewfield Fiel d:mfiled) {//Add a row injectmethodbuilder.addstatement ("host. $N = ($T) finder.findview (source, $L)", Fiel D.getfieldname (), Classname.get (Field.getfieldtype ()), Field.getresid ()); } String PackageName = Getpackagename (mclasselement); String className = GetClassName (mclasselement, PackageName); ClassName bindclassname = Classname.get (PackageName, ClassName); Build class TypeSpec Finderclass = Typespec.classbuilder (Bindclassname.simplenamE () + "$ $Injector")//class name. Addmodifiers (modifier.public)//Add description. Addsuperinterface (parameterize Dtypename.get (Typeutil.injector, Typename.get (Mclasselement.astype ()))//Add Interface (class/interface, Paradigm). Addmethod (Injectmet Hodbuilder.build ())//Add method. Build (); Return Javafile.builder (PackageName, Finderclass). Build (); The public String getpackagename (typeelement type) {return melementutils.getpackageof (type). Getqualifiedname (). String (); } private static string GetClassName (typeelement type, string packagename) {int packagelen = Packagename.length () + 1; Return Type.getqualifiedname (). toString (). substring (packagelen). Replace ('. ', ' $ '); }
You can system.out debug the code of the annotation processor in your code.
Also note that the cross references between projects.
Bindview-complier Reference Bindview-annotation
The app cites the remaining three module, the apt way of referencing bindview-complier
Apt Project (': Bindview-compiler ')
Just write it down here, the Demo's on GitHub.
One hour to figure it out. Annotation Processor (Annotation Processor Tool)