本篇文章比较简单,如果熟悉apt的同学就不用看了,只是作为入门级功能。
经过前三篇的讲解,今天做一个简易版的View注入框架,功能类似黄油刀ButterKnife中的一小部分功能,Activity中view通过注解获取实例。
项目结构:
processor_lib是一个java lib 项目,实现processor。
annotationlib 注解。
injectlib 供用户调用的工具。
app 使用编译期注解,完成view自动注入。
import com.ldx.annotationlib.BindView;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.JavaFileObject;
@SupportedOptions({"CLASSNAME"})
@SupportedAnnotationTypes("com.ldx.annotationlib.BindView")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AptProcessor extends AbstractProcessor {
private Filer mFilerUtils;
private Types mTypesUtils;
private Elements mElementsUtils;
private Messager mMessager;
private Map<String,String> mOptionMap;
private ArrayList<VariableElement> mElementList;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFilerUtils = processingEnv.getFiler(); //生成java文件工具
mTypesUtils = processingEnv.getTypeUtils(); //type 工具
mElementsUtils = processingEnv.getElementUtils(); //element 工具,获取包名等
mMessager = processingEnv.getMessager(); //打印log,rebuild 项目可见
mOptionMap = processingEnv.getOptions(); //获取定义常量
mElementList = new ArrayList<>(); //element 集合,存放一个activity中所有的注解元素集合
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set != null && set.size() > 0){
//获取所有使用了BindView的元素,为了简单此处只适用于一个activity,如果要应用于多个activity,需要根据获取的elements,进行分类,此处为了简单只用于一个activity,也就不需要利用hashMap进行分类了。
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {
//由于作用于field ,直接强转为VariableElement
VariableElement variableElement = (VariableElement) element;
mElementList.add(variableElement);
}
//生成文件
createFile(mElementList);
return true;
}
return false;
}
private void createFile(ArrayList<VariableElement> mElementList) {
//getEnclosingElement 获取parent,此处也就是activity
TypeElement enclosingElement = (TypeElement) mElementList.get(0).getEnclosingElement();
//全包名
String pkName = mElementsUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
String packName = mElementsUtils.getPackageOf(enclosingElement).asType().toString();
//文件名
String className = enclosingElement.getSimpleName().toString();
try {
JavaFileObject jfo = mFilerUtils.createSourceFile(pkName + "."+className+ "$ViewBinding", new Element[]{});
Writer writer = jfo.openWriter();
writer.write(brewCode(className,pkName, mElementList));
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//拼接java文件代码
private String brewCode(String className, String pkName, ArrayList<VariableElement> mElementList) {
StringBuilder builder = new StringBuilder();
builder.append("package " + pkName + ";\n\n");
builder.append("public class " + className + "$ViewBinding implements com.ldx.injectlib.InjectIoc { \n\n");
builder.append("@Override\n\n");
builder.append("public void inject("+"Object"+" obj1"+"){ \n\n");
builder.append(" "+pkName+"."+className+" obj" +" = "+"("+pkName+"."+className+")"+"obj1;\n\n");
for (VariableElement element : mElementList){
//3.获取注解的成员变量名
String bindViewFiledName = element.getSimpleName().toString();
//变量类型
String bindViewFiledClassType = element.asType().toString();
BindView bindAnnotation = element.getAnnotation(BindView.class);
int id = bindAnnotation.value();
String info = String.format("%s %s = findViewById(%d)", bindViewFiledClassType, bindViewFiledName, id);
builder.append(" System.out.println(\"" + info + "\");\n\n");
builder.append(" obj."+bindViewFiledName+" = "+"obj.findViewById("+id+");"+"\n\n");
}
builder.append("}\n\n");
builder.append("}");
return builder.toString();
}
}
public interface InjectIoc {
void inject(Object obj);
}
反射调用:
import android.content.Context;
public class ViewInject {
public static void inject(Context activity){
try {
Class<?> clazz = activity.getClass();
String proxyClassFullName = clazz.getName()+"$ViewBinding";
Class<?> proxyClazz = Class.forName(proxyClassFullName);
InjectIoc injectIoc = (com.ldx.injectlib.InjectIoc)proxyClazz.newInstance();
injectIoc.inject(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import com.ldx.annotationlib.BindView;
import com.ldx.canvasdrawdemo.R;
import com.ldx.injectlib.ViewInject;
public class BindViewActivity extends AppCompatActivity {
@BindView(R.id.tv1)
public TextView textView1;
@BindView(R.id.tv2)
public TextView textView2;
@BindView(R.id.tv3)
public TextView textView3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bind_view);
ViewInject.inject(BindViewActivity.this);
if (textView1 != null){
System.out.println("=================="+textView1.toString());
textView1.setText("apt 设置的Text");
}else{
System.out.println("==================没有绑定成功");
}
}
}
//布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".activity.BindViewActivity">
<TextView
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text1"/>
<TextView
android:id="@+id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text2"/>
<TextView
android:id="@+id/tv3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text3"/>
</LinearLayout>
//生成的文件
package com.ldx.canvasdrawdemo.activity;
public class BindViewActivity$ViewBinding implements com.ldx.injectlib.InjectIoc {
@Override
public void inject(Object obj1){
com.ldx.canvasdrawdemo.activity.BindViewActivity obj = (com.ldx.canvasdrawdemo.activity.BindViewActivity)obj1;
System.out.println("android.widget.TextView textView1 = findViewById(2131231154)");
obj.textView1 = obj.findViewById(2131231154);
System.out.println("android.widget.TextView textView2 = findViewById(2131231155)");
obj.textView2 = obj.findViewById(2131231155);
System.out.println("android.widget.TextView textView3 = findViewById(2131231156)");
obj.textView3 = obj.findViewById(2131231156);
}
}
@SupportedOptions({"CLASSNAME"})
@SupportedAnnotationTypes("com.ldx.annotationlib.BindView")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AptProcessor3 extends AbstractProcessor {
private Filer mFilerUtils;
private Types mTypesUtils;
private Elements mElementsUtils;
private Messager mMessager;
private Map<String,String> mOptionMap;
private HashMap<TypeElement,ArrayList<VariableElement>> mEleMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFilerUtils = processingEnv.getFiler();
mTypesUtils = processingEnv.getTypeUtils();
mElementsUtils = processingEnv.getElementUtils();
mMessager = processingEnv.getMessager();
mOptionMap = processingEnv.getOptions();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set != null && set.size() > 0){
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {
VariableElement variableElement = (VariableElement) element;
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
if (mEleMap.containsKey(typeElement)){
mEleMap.get(typeElement).add(variableElement);
}else{
ArrayList<VariableElement> list = new ArrayList<VariableElement>();
list.add(variableElement);
mEleMap.put(typeElement, list);
}
}
createFile();
return true;
}
return false;
}
private void createFile() {
for (Map.Entry<TypeElement,ArrayList<VariableElement>> entry : mEleMap.entrySet()) {
TypeElement typeElement = entry.getKey();
ArrayList<VariableElement> list = entry.getValue();
TypeElement enclosingElement = typeElement;
String pkName = mElementsUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
String className = enclosingElement.getSimpleName().toString();
try {
JavaFileObject jfo = mFilerUtils.createSourceFile(pkName + "."+className+ "$ViewBinding", new Element[]{});
Writer writer = jfo.openWriter();
writer.write(brewCode(className,pkName, list));
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private String brewCode(String className, String pkName, ArrayList<VariableElement> mElementList) {
StringBuilder builder = new StringBuilder();
builder.append("package " + pkName + ";\n\n");
builder.append("public class " + className + "$ViewBinding implements com.ldx.injectlib.InjectIoc { \n\n");
builder.append("@Override\n\n");
builder.append("public void inject("+"Object"+" obj1"+"){ \n\n");
builder.append(" "+pkName+"."+className+" obj" +" = "+"("+pkName+"."+className+")"+"obj1;\n\n");
for (VariableElement element : mElementList){
//3.获取注解的成员变量名
String bindViewFiledName = element.getSimpleName().toString();
//变量类型
String bindViewFiledClassType = element.asType().toString();
BindView bindAnnotation = element.getAnnotation(BindView.class);
int id = bindAnnotation.value();
String info = String.format("%s %s = findViewById(%d)", bindViewFiledClassType, bindViewFiledName, id);
builder.append(" System.out.println(\"" + info + "\");\n\n");
builder.append(" obj."+bindViewFiledName+" = "+"obj.findViewById("+id+");"+"\n\n");
}
builder.append("}\n\n");
builder.append("}");
return builder.toString();
}
}
编译时注解学习一之 Element元素
编译时注解学习二之 注解处理器初探AbstractProcessor
编译时注解学习三之 注解处理器AbstractProcessor工具和Element属性简述
编译期注解学习四 简单的view注入框架
编译期注解学习五 - ElementKind,TypeKind,不同Element类型判断
编译期注解学习六- 生成java文件javapoet
编译期注解学习七-如何进行调试
编译时注解学习八 -模板文件读取