一、Java 注解介绍
ButterKnife插件让我们从繁琐的findViewById函数中解脱出来。深入过其原理的同学都知道,使用了Java的高级技术Annotation。一般来说Java的注解有三种方式:
- RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
- RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中,使用的是Java Annotation Processing 用于编译时扫描和解析Java注解的工具。
- RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,实用的是反射机制可以在程序运行时可以获取到它们。
二、自定义Java 注解
接下来开始仿照ButterKnife定义自己我们自己的BindView
/**
* @Retention 用于声明注解生效的生命周期, 有三个枚举值可以选择
* 1.RetentionPolicy.SOURCE 注解只保留在源码上,编译成class时自动被编译器抹去
* 2.RetentionPolicy.CLASS 注解只保留在字节码上,vm加载字节码时自动抹去
* 3.RetentionPolicy.RUNTIME 注解永久保留,可以被vm加载到内存
* 由于我们想在运行时对Filed上的注解进行反射操作,因此Retention值必须设为RUNTIME
*
* @target 用于指定注解可以声明在哪些成员上面,我们设置的值有Filed和Method
* 由于我们当前注解类是要声明在Filed上,因此我们设置为ElementType.FILED
*
* @interface 是声明注解类组合关键字
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value() default -1;
}
这样在实际的使用过程中就可以这样子来写了。
@BindView(R.id.btn)
protected Button mBtn;
2.1 编译时注解
在编译时候如果想要获取注解就只能通过继承于AbstractProcessor的方式了。
如下代码
public class MyAnnotationProcessor extends AbstractProcessor {
// 主要用于生产文件
private Filer mFiler;
// 主要是日志打印
private Messager mMessager;
// 获取注解元素
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
// 初始化
mFiler = processingEnvironment.getFiler();
mMessager = processingEnvironment.getMessager();
mElementUtils = processingEnvironment.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<>();
annotations.add(BindView.class.getCanonicalName());
return annotations;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set,
RoundEnvironment roundEnvironment) {
Set<? extends Element> bindViewElements = roundEnvironment
.getElementsAnnotatedWith(BindView.class);
for (Element element : bindViewElements) {
//1.获取包名
PackageElement packageElement = mElementUtils.getPackageOf(element);
String pkName = packageElement.getQualifiedName().toString();
note(String.format("package = %s", pkName));
//2.获取包装类类型
TypeElement enclosingElement = (TypeElement) element
.getEnclosingElement();
String enclosingName = enclosingElement.getQualifiedName().toString();
note(String.format("enclosindClass = %s", enclosingElement));
//因为BindView只作用于filed,所以这里可直接进行强转
VariableElement bindViewElement = (VariableElement) element;
//3.获取注解的成员变量名
String bindViewFiledName = bindViewElement.getSimpleName().toString();
//3.获取注解的成员变量类型
String bindViewFiledClassType = bindViewElement.asType().toString();
//4.获取注解元数据
BindView bindView = element.getAnnotation(BindView.class);
int id = bindView.value();
note(String.format("%s %s = %d", bindViewFiledClassType
, bindViewFiledName, id));
//4.生成文件
createFile(enclosingElement, bindViewFiledClassType
, bindViewFiledName, id);
return true;
}
return false;
}
private void createFile(TypeElement enclosingElement
, String bindViewFiledClassType, String bindViewFiledName, int id) {
String pkName = mElementUtils.getPackageOf(enclosingElement)
.getQualifiedName().toString();
try {
JavaFileObject jfo = mFiler.createSourceFile(pkName + ".ViewBinding"
, new Element[]{});
Writer writer = jfo.openWriter();
writer.write(brewCode(pkName, bindViewFiledClassType
, bindViewFiledName, id));
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private String brewCode(String pkName, String bindViewFiledClassType,
String bindViewFiledName, int id) {
StringBuilder builder = new StringBuilder();
builder.append("package " + pkName + ";\n\n");
builder.append("//Auto generated by apt,do not modify!!\n\n");
builder.append("public class ViewBinding { \n\n");
builder.append("public static void main(String[] args){ \n");
String info = String.format("%s %s = %d", bindViewFiledClassType
, bindViewFiledName, id);
builder.append("System.out.println(\"" + info + "\");\n");
builder.append("}\n");
builder.append("}");
return builder.toString();
}
private void note(String msg) {
mMessager.printMessage(Diagnostic.Kind.NOTE, msg);
}
private void note(String format, Object... args) {
mMessager.printMessage(Diagnostic.Kind.NOTE, String.format(format, args));
}
}
OK,这样子就可以在编译的时候获取到注解的所有信息了,有了这些信息,想干啥都可以了。
参考博客:
注解处理器(Annotation Processor)简析
2.2 运行时注解
运行时注解关键技术就是反射了。
定义ViewBind 注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewBind {
//value是所有注解类的默认属性名
int value();
}
定义Click 注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Click {
//int[]数组用于绑定多个id
int[] value();
}
运行时反射处理注解
public static void bind(final Activity activity){
/**
* 通过字节码获取activity中所有字段,并且使用getDeclaredFields方法
* 因为这样才能获取到任何权限修饰的Field。包括private
*/
Field[] declaredFileds = activity.getClass().getDeclaredFields();
for (int i=0;i<declaredFileds.length;i++){
Field field = declaredFileds[i];
//设置为可访问,即使为私有,暴力反射
field.setAccessible(true);
//获取字段上的注解对象
ViewBind annotation = field.getAnnotation(ViewBind.class);
if (annotation!=null){
int id = annotation.value();
//获取控件
View view = activity.findViewById(id);
try {
//参数1为当前Field所属的对象
//参数2为要给Field设置的值
field.set(activity,view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
Method[] declaredMehtod = activity.getClass().getDeclaredMethods();
for (int i=0;i<declaredMehtod.length;i++){
final Method method = declaredMehtod[i];
method.setAccessible(true);
//获取方法上的注解
Click annotation = method.getAnnotation(HunterKnife.Click.class);
//如果没有循环下一个
if (annotation == null){
continue;
}
//获取注解中的数据
int[] ids = annotation.value();
for (int j=0;j<ids.length;j++){
final View button = activity.findViewById(ids[i]);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
method.invoke(activity,button);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
}