一 案例:
这篇文章将简单介绍下自定义注解:自定义ButterKnife,用注解实现下View的findViewById和onClick事件;
自定义注解一般分为两步:第一步 声明注解,第二步 解析注解。
1 自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyBindView {
int value() default -1 ;
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyOnClick {
int[] value() default {-1};
}
2 自定义注解解析器
import android.app.Activity;
import android.view.View;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MyButterKnife {
public static void init(final Activity activity){
Class<?> clz = activity.getClass();
Field[] fields = clz.getDeclaredFields();
Method[] methods = clz.getDeclaredMethods();
//findViewById
for(Field field : fields){
if (field.isAnnotationPresent(MyBindView.class)){
MyBindView myBindView = field.getAnnotation(MyBindView.class);
if (myBindView != null){
try {
field.set(activity, activity.findViewById(myBindView.value()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
//onClick
for (final Method method : methods){
if (method.isAnnotationPresent(MyOnClick.class)){
MyOnClick onClick = method.getAnnotation(MyOnClick.class);
if (onClick != null && onClick.value().length > 0){
for(int id : onClick.value()){
View view = activity.findViewById(id);
if (view != null){
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
method.invoke(activity, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
});
}
}
}
}
}
}
}
这里运用了反射来操作,调用bind方法后,先用cls.getDeclaredFields();获取所有的全局变量,然后再对每个变量用field.getAnnotation(BindView.class);来获取这个变量上面的注解,如果存在的话 int ids = bindView.value(); 来获取我们传入的资源,最后调用findViewById:
field.set(activity, activity.findViewById(myBindView.value()));
3 使用
public class TestActivity extends Activity {
@MyBindView(R.id.tv)
TextView textView;
@MyOnClick(R.id.tv)
void tvClick(View view){
Toast.makeText(this, "点击了", Toast.LENGTH_LONG).show();
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
MyButterKnife.init(this);
textView.setText("111111");
}
}
4 类结构
注意:
使用注解的的类要和声明注解类在一个包下,否则会报这种错误:
Class java.lang.Class<MyAnnotation.MyButterKnife> cannot access field android.widget.TextView zzf.com.testkotlin2.TestActivity.textView of class java.lang.Class<zzf.com.testkotlin2.TestActivity>
二 注解
1 什么是注解
对于很多初次接触的开发者来说应该都有这个疑问?Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。
2 注解的原理:
注解本质是一个继承了Annotation 的特殊接口,其具体实现类是Java 运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java 运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke 方法。该方法会从memberValues 这个Map 中索引出对应的值。而memberValues 的来源是Java 常量池。
3 注解的作用
简化代码,提高开发效率。
注意:肯定是能提高代码开发效率,并不一定能提供程序运行效率。
三 元注解
元注解是用来定义其他注解的注解(在自定义注解的时候,需要使用到元注解来定义我们的注解)。java.lang.annotation提供了四种元注解:@Retention、 @Target、@Inherited、@Documented。
元注解是用来修饰注解的注解。在自定义注解的时候我们肯定都是要用到元注解的。因为我们需要定义我们注解的是方法还是变量,注解的存活时间等等。
1 @Target
@Target元注解用来表明我们注解可以出现的地方,参数是一个ElementType类型的数组,所以@Target可以设置注解同时出现在多个地方。比如既可以出现来类的前面也可以出现在变量的前面。
@Target元注解ElementType枚举(用来指定注解可以出现的地方):
2 @Retention
@Retention表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)。参数是RetentionPolicy枚举对象。
RetentionPolicy的枚举类型有(默认值为CLASS.):
3 @Document
@Document表明我们标记的注解可以被javadoc此类的工具文档化。
4 @Inherited
@Inherited表明我们标记的注解是被继承的。比如,如果一个父类使用了@Inherited修饰的注解,则允许子类继承该父类的注解。