1 a brief introduction
APT (Annotation Processing Tool) is a Tool for Processing annotations. It detects the annotations in source code files and uses the Annotation processor to process them. When processing annotations, the Annotation processor can generate additional source files and other files according to annotations in source files. APT will also compile the generated source files and original source files and generate class files together. Simply put: Automatically generate code without affecting performance! Common ButterKnife, Dagger and other tools use APT technology.
Example 2
We realized the charm of APT technology by implementing the function like ButterKnife to automatically generate findviewById. APT workflow
- Custom annotation
- Defining an Annotation processor (AbstractProcess)
- Java code generation in the processor (javaPoet is possible)
- Registration Handler (AutoService)
Create the apt-Annotation module to define the BindView annotation
@Retention(RetentionPolicy.CLASS) // Compile time annotations
@Target(ElementType.FIELD) // The annotation scope is a class member
public @interface BindView {
int value(a);
}
Copy the code
Create the apt-Processor module to define the annotation processor, generate the required code, and register the processor
// BindViewProcessor.java
@AutoService(Processor.class) // Register the handler
public class BindViewProcessor extends AbstractProcessor {
private Elements mElementUtils;
private Map<String, ClassCreatorProxy> mProxyMap = new HashMap<>();
ProcessingEnvironment provides a number of useful utility classes Elements, Types, and Filer
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mElementUtils = processingEnv.getElementUtils();
}
/** * specifies which annotation handler is registered for, in this case BindView */
@Override
public Set<String> getSupportedAnnotationTypes(a) {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
}
/ * * * specified using Java version, usually returns SourceVersion. Here latestSupported () * /
@Override
public SourceVersion getSupportedSourceVersion(a) {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// Generate Java files from annotations
mProxyMap.clear();
// Get all the annotations
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
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);
}
BindView bindAnnotation = variableElement.getAnnotation(BindView.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.generateJavaCode()).build();
try {
// Generate the file
javaFile.writeTo(processingEnv.getFiler());
} catch(IOException e) { e.printStackTrace(); }}return true; }}// ClassCreatorProxy.java
// JavaPoet generates Java code
public class ClassCreatorProxy {
private String mBindingClassName;
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);
}
/** * Create Java code */
public TypeSpec generateJavaCode(a) {
return TypeSpec.classBuilder(mBindingClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethods())
.build();
}
/** * add Method */
private MethodSpec generateMethods(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
Create library (apt-library) reflection execute bind
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 | IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); }}Copy the code
Import apt related three packages in app main module, using BindView annotation
public class TestActivity extends AppCompatActivity {
@BindView(R.id.tv)
TextView mTextView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
BindViewTools.bind(this);
mTextView.setText("hello world"); }}Copy the code
Compile the results
Can be found in/build/generated/ap_generated_sources
Below to find the generated Java file.
Write in the last
Through the above simple example of using APT to generate code, presumably we have an intuitive understanding of APT. For example, the popular ButterKnife frame in the market uses APT technology, which can be studied. This technique, coupled with the Javapoet framework, makes it possible to write many “elegant” frameworks.