运行时注解
以 @BindView 为例。下面是实现步骤
- 新建一个 apt-annotation 的 java-library ,然后在库中新建一个注解,传入一个 int 参数用来传入viewId
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200326220149309.png
// RetentionPolicy.CLASS 代表编译时注解
@Retention(RetentionPolicy.CLASS)
// 代表作用在属性上
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
- 新建一个Java 库 apt-processor 并添加依赖
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200326220441720.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xkeGx6MjI0,size_16,color_FFFFFF,t_70
// AutoService会自动在META-INF文件夹下生成Processor配置信息文件,该文件里就是实现该服务接
// 口的具体实现类。而当外部程序装配这个模块的时候,
// 就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的
// 注入
implementation 'com.google.auto.service:auto-service:1.0-rc6'
// JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件。这个框架功能非
// 常有用,我们可以很方便的使用它根据注解、数据库模式、协议格式等来对应生成代码。通过这种自动化生
// 成代码的方式,可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作
implementation 'com.squareup:javapoet:1.10.0'
implementation project(':apt-annotation')
BindViewProcessor 继承 AbstractProcessor
@AutoService(Processor.class)
//@SupportedAnnotationTypes({"com.example.apt_annotation"})
//@SupportedSourceVersion()
public class BindViewProcessor extends AbstractProcessor {
// 辅助打印信息类
private Messager mMessager;
// Element 指的是一系列与之相关的接口集合,用于建模java编程语言元素的接口,它们位于javax.lang.model.element包下面
// Element 是代表程序的一个元素,这个元素可以是:包、类/接口、属性变量、方法/方法形参、泛型参数。
// Element 所代表的元素只在编译期可见,用于保存元素在编译期的各种状态,而Type所代表的元素是运行期可见,用于保存元素在运行期的各种状态。
private Elements mElementUtils;
private Map<String, ClassCreatorProxy> mProxyMap = new HashMap<>();
/**
* 初始化方法 可以获取到
* Elements
* 用来对程序元素进行操作的实用工具方法。
* Filer
* 在生成java文件时候使用。javapoet配合使用
* Types
* 用来对类型进行操作的实用工具方法。
* Messager
* 提供注释处理器用来报告错误消息、警告和其他通知的方式。可以传递元素、注释和注释值,以提供消息的位置提示。
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mMessager = processingEnv.getMessager();
mElementUtils = processingEnv.getElementUtils();
}
/**
* 指出注解处理器 处理哪种注解
* 在 jdk1.7 中,我们可以使用顶部的注解 @SupportedAnnotationTypes({"com.example.apt_annotation"})代替
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
}
/**
* 指定当前注解器使用的Jdk版本
* 在 jdk1.7 中,我们可以使用注解 @SupportedSourceVersion()代替
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 编译期扫描指定注解之后会回调到这个方法
* 我们要处理的就是
* 1.收集 Class 内的所有被 @BindView 注解的成员变量;
* 2.根据上一步收集的内容,生成 .java 源文件。
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
mMessager.printMessage(Diagnostic.Kind.NOTE, "processing...");
mProxyMap.clear();
//得到所有的注解
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();
//elements的信息保存到mProxyMap中
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);
}
//通过javapoet生成java文件
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;
}
}
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);
}
/**
* 加入Method
*
* @param builder
*/
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() {
return mPackageName + "." + mBindingClassName;
}
public TypeElement getTypeElement() {
return mTypeElement;
}
//======================
/**
* 创建Java代码
* javapoet
*
* @return
*/
public TypeSpec generateJavaCode2() {
TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethods2())
.build();
return bindingClass;
}
/**
* 加入Method
* javapoet
*/
private MethodSpec generateMethods2() {
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() {
return mPackageName;
}
}
- BindViewTools 类绑定Activity 通过反射调用初始化View的方法
public class BindViewTools {
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();
}
}
}
- 调用如下:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
TextView mTextView;
@BindView(R.id.btn)
Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BindViewTools.bind(this);
mTextView.setText("Hello World");
mButton.setText("success");
}
}
- 整个项目执行流程为:
编译器通过扫描我们在ActivityView中使用的@BindeView注解,然后回调到 BindViewProcessor 的 process() 方法,然后在Build中生成 app/build/intermediates/classes/debug/com/example/gavin/apttest/MainActivity_ViewBinding.class ,接着通过 BindViewTools.bind(this); 反射调用到 MainActivity_ViewBinding.class 的 bind 方法,从而初始化View完成。下面是生成的 MainActivity_ViewBinding.class 代码。
public class MainActivity_ViewBinding {
public MainActivity_ViewBinding() {
}
public void bind(MainActivity host) {
host.mButton = (Button)((Button)host.findViewById(2131165218));
host.mTextView = (TextView)((TextView)host.findViewById(2131165321));
}
}