上一篇我说了下Butterknife的基本用法(http://blog.csdn.net/bit_kaki/article/details/74926076),但是我们也知道Butterknife是基于注解来实现的,所以要搞清楚它的原理首先我们需要搞清注解是什么,这一篇我将主要介绍下注解的原理和用法。
注解是Java里一个重要的概念,可以用来修饰类、字段和方法等。它的基本结构就是一个@加上一个字符串,放在需要修饰的类、字段、方法前面,比如我们熟悉的@Override,在一个方法前面加上它,表明接下来的方法执行时候会重写其父类中相应方法。而在我们的ButterKnife框架里,功能的实现基本上都是依靠注解来完成。所以,我们先来研究下注解到底是什么?
注解的英文是Annotation,官方对它定义是:
* Annotation:Release 5.0 of the JDK introduced a metadata facility called annotations.
* Annotations provide data about a program that is not part of the program,such as naming
* the author of a piece of code or instructing the compiler to suppress specific errors.
* An annotation has no effect on how the code performs. Annotations use the form @annotation
* and may be applied to a program's declarations: its classes,fields,methods,and so on.
* The annotation appears first and often (by convention) on its own line,and may include optional arguments
翻译一下大概是:
在JDK 5版本中引入的元数据功能称为注解。注解提供了关于程序不属于程序的数据,例如命名一段代码的作者或指示编译器来抑制特定的错误。注解对代码的执行方式没有影响。注解使用表单@注解,可以应用于程序的声明:它的类、字段、方法等。注解往往(按惯例)在其自己的行上首先出现,并且可以包括可选参数。
这段话挺枯燥的,简要举个例子,注解就是一种特殊的标记,类似于我们QQ空间的朋友印象。我们可以给每个朋友贴一些标记,比如富二代、大美妞、傻白甜等,而对于这些标记的意思,并没有在我们的QQ空间里说明出来,但是大家通过这些标记已经了解到朋友的一些特点。另外这些标记并没改变我们朋友的属性,有没有这些标记,他的根本属性也没变。
也许这个例子不算很合适,但是通过这个例子我想说明下我对注解的理解有以下几个特点:
1.注解不会影响到它所修饰元素的根本属性,即使去掉它,代码里也不会报错;
2.注解可以提供该段代码没有的数据和内容,供于该段代码使用;
3.注解里包含的都是些通用方法和数据,用以简化代码。
好了,有了一个基本了解后,我们来看看如果使用注解吧。
注解的定义和接口很像,就是多了一个@,而使用的时候只需在其需要修饰的类、字段、方法的首行上标注一下就行:
private @interface testAnnotation{
}
@testAnnotation
private TextView textView;
当然,注解里也可以添加一些参数,比如:
private @interface testAnnotation{
int id() default 1;
String value();
}
这时候就需要在使用时候注解后面输入参数,比如:
@testAnnotation(id=2,value = "haha")
private TextView textView;
不过当注解参数里有default,也就是默认值时候,该想参数可以不用再次输入,比如:
@testAnnotation(id=2,value = "haha")
private TextView textView;
也是正确的,此时的id默认是1。
另外还有一种特殊情况是当注解里的参数只有一个且为value时候,可以在使用适合直接输入参数,比如:
private @interface testAnnotation{
String value();
}
@testAnnotation("haha")
private TextView textView;
注解除了可以修饰的类、字段、方法外,还可以修饰其他注解,比如我们看看ButterKnife中的@OnClick注解,它的代码为:
@Target(METHOD)
@Retention(CLASS)
@ListenerClass(
targetType = "android.view.View",
setter = "setOnClickListener",
type = "butterknife.internal.DebouncingOnClickListener",
method = @ListenerMethod(
name = "doClick",
parameters = "android.view.View"
)
)
public @interface OnClick {
/** View IDs to which the method will be bound. */
int[] value() default { View.NO_ID };
}
可以看到它还被其他几个注解所注解。我们都知道类里有个概念叫基类——Object,指的是所有类里最基本的类,其他类都是继承于它。那么注解里是否有类似的概念呢,那当然有了,我们称之为元注解。
和基类只有一个相比,元注解一共有五个,分别是:@Retention、@Documented、@Target、@Inherited、@Repeatable。其中前四个是在java1.5里提出注解概念时候就有,后一个是在java1.8里才新加的。它们的意思分别是:
@Retention:决定这个注解的存活时间;
@Documented:被该注解修饰的类可以被javadoc工具提取成文档;
@Target:表明该注解可以修饰哪些元素;
@Inherited:被该注解修饰的注解有继承性;
@Repeatable:表明该注解可以多次应用修饰同一个元素。
这里单独说明下@Retention,官方对其解释为:
* Indicates how long annotations with the annotated type are to
* be retained. If no Retention annotation is present on
* an annotation type declaration, the retention policy defaults to
* {@code RetentionPolicy.CLASS}.
可以看出这个元注解只能用来修饰注解类型,并且决定了该注解的存活时间,默认值为(Retationpolicy.CLASS)
然后看下它具体的值:
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
翻译一下大概就是:
Retationpolicy.SOURCE : Annotation只保留在原代码中,当编译器编译的时候就会抛弃它。
Retationpolicy.CLASS:编译器将把Annotation记录在Class文件中,不过当java程序执行的时候,JVM将抛弃它。
Retationpolicy.RUNTIME:在Retationpolicy.CLASS的基础上,JVM执行的时候也不会抛弃它,所以我们一般在程序中可以通过反射来获得这个注解,然后进行处理。
有人会问注解存活时间的意义是什么呢?注解的提取,往往需要一个手段,那就是反射。而这个注解需要在运行时候提取,就必须要将它的@Retention设置为Retationpolicy.RUNTIME。但是如果实在运行时候进行反射提取的话,对设备性能的消耗也是很高的,所以有些工具将它们的注解在编译时候就提取了,其对应的@Retention就是Retationpolicy.CLASS,比如我们使用ButterKnife就是如此。对于ButterKnife使用的方法,我们将在后面介绍。
还是写个例子来说明比较好,就以模仿写下我们熟悉的ButterKnife的控件绑定的注解吧。
首先是定义一个注解:
@Retention(RUNTIME)
public @interface Bind {
int value();
}
public class BKnife {
public static void bind(Activity activity){
Class clas=activity.getClass();
for(Field s:clas.getDeclaredFields()){
if(s.isAnnotationPresent(Bind.class)){
try {
Bind viewInjectAnnotation = s.getAnnotation(Bind.class);
int viewId = viewInjectAnnotation.value();
Method method = clas.getMethod("findViewById",int.class);
Object resView = method.invoke(activity, viewId);
s.setAccessible(true);
s.set(activity, resView);
}catch (Exception e){
}
}
}
}
}
public class MainActivity3 extends AppCompatActivity {
@Bind(R.id.textView1)
TextView textView1;
@Bind(R.id.textView2)
TextView textView2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_annotation );
BKnife.bind(this);
textView1.setText("第一个textView");
textView2.setText("第二个textView");
}
}