在Android开发中经常会使用到各种框架,如Retrofit、ButterKnife等,而这些框架中往往通过定义一些注解提供给我们使用,并且通过遵循框架暴露的规则就可以很方便的使用框架从而简化我们的开发过程。因此,今天我们就了解下注解的定义以及如何使用。
注解,为在代码中添加信息提供了一种形式化的方法,使我们可以在稍后的某个时刻非常方便的使用这些数据。
Java中提供了三种类型的注解:
Java提供了四种元注解,元注解用来负责注解其他的注解:
我们看一下如何自己定义一个注解:
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 Test {
String value() default "";
}
//注解的使用
public class Testable {
@Test
public void test() {
System.out.println("test");
}
}
可以看到,注解的定义跟接口的定义很像,但是它使用的是 @interface 关键字,并且需要用到元注解,上述注解定义中的 @Target 表示该注解应用于方法,@Retention 表示可以应用于运行时。在内部定义了一个类似方法的元素value,元素在注解中以无参的方式声明,可以通过default关键字指定其默认值。
在注解定义的内部可以不定义任何元素,这种称为标记注解。标记注解在程序中可以起到对某个内容标记的作用。
在注解中,还可以定义多个元素,当分析处理注解时,程序可以利用这些元素的值。下面是一个多元素的注解定义和使用:
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface UseCase {
public int id();
public String desc() default "no description";
}
//注解的使用
public class StringUtils {
@UseCase(id = 3, desc = "hello")
public void check(String s) {
System.out.println("check " + s);
}
@UseCase(id = 5)
public void print(String s) {
System.out.println(s);
}
}
提醒:如果注解中只有一个元素定义,最好把元素名称设为value,因为在使用该注解时,value作为key可省略,在使用的时候比较方便。
注解的解析
我们通过一个完整的例子来看下注解的解析。
定义一个注解(其中 @Target 中指定了该注解可以作用于方法、字段和类上):
package com.test.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description {
String value() default "no desc" ;
}
使用并解析注解信息:
package com.test.annotation;
public interface People {
public String name();
public int age();
public void work();
}
package com.test.annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@Description("I am Class")
public class Child implements People {
@Description("name filed")
private String name;
@Override
@Description("name method")
public String name() {
return name;
}
@Override
@Description
public int age() {
return 0;
}
@Override
public void work() {
}
public static void main(String[] args) {
try {
Class> cls = Class.forName("com.test.annotation.Child");
//Class> cls = Child.class;
//判断类是否存在这个注解
boolean isExist = cls.isAnnotationPresent(Description.class);
if (isExist) {
//获取类的注解
Description des = cls.getAnnotation(Description.class);
System.out.println(des.value());
}
//获取类的所有属性
Field[] fields = cls.getDeclaredFields();
for (Field f : fields) {
//判断该属性是否存在这个注解
boolean exist = f.isAnnotationPresent(Description.class);
if (exist) {
//获取该属性的注解
Description des = f.getAnnotation(Description.class);
System.out.println(des.value());
}
}
//获取类的所有公开方法
Method[] methods = cls.getMethods();
for (Method m : methods) {
//判断该方法是否存在这个注解
boolean exist = m.isAnnotationPresent(Description.class);
if (exist) {
//获取该方法的注解
Description des = m.getAnnotation(Description.class);
System.out.println(des.value());
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出打印结果:
I am Class
name filed
name method
no desc
在该例中为了获取到注解信息,还用到了Java的反射机制获取类、方法和属性。并且为了更好的展示注解的使用,我们还通过类的继承关系看到了JDK的注解@Override的使用。
另外,解析注解信息还可以通过下面的方式(其实注解的解析相关的方法有很多,详细可以查看文档或者在开发环境中进行查看):
Method[] methods = cls.getMethods();
for (Method m : methods) {
//获取该方法的所有注解
Annotation[] annotations = m.getAnnotations();
for (Annotation a : annotations) {
if (a instanceof Description) {
System.out.println(((Description) a).value());
}
}
}
简单框架的实现
下面通过实现一个简单的框架来看下注解在框架中的使用,该功能实现通过在Android Activity中对View进行注解,从而避免view通过findViewById的初始化过程。
首先定义一个注解:
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 BindView {
int value();
}
框架的实现,获取注解,并进一步处理(其中用到了反射相关的操作,对反射不了解的话最好先去了解一下):
import android.app.Activity;
import android.view.View;
import java.lang.reflect.Field;
public class BindUtils {
public static void bindView(Activity activity) {
Class extends Activity> cls = activity.getClass();
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
boolean exist = field.isAnnotationPresent(BindView.class);
if (exist) {
BindView bindView = field.getAnnotation(BindView.class);
if (bindView != null) {
int viewId = bindView.value();
View view = activity.findViewById(viewId);
field.setAccessible(true);
try {
field.set(activity, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
具体使用:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.textView)
private TextView textView;
@BindView(R.id.bottom_layout)
private LinearLayout bottomLayout;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BindUtils.bindView(this); //需要放在setContentView后面
textView.setText("hello");
bottomLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
}
可以看到通过这个简单的框架,我们不需要再通过findViewById对view进行初始化了,而是框架帮我们做了这部分工作。
参考:《Java编程思想》
https://www.jianshu.com/p/b560b30726d4