Android Butterknife 框架源码解析(2)——谈谈Java的注解

  上一篇我说了下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){

                }
            }
        }
    }
}

       最后就是在Activity里完成绑定事件

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");
    }

}

       布局文件很简单,就是两个重叠的TextView,所以运行的效果就如下所示:

Android Butterknife 框架源码解析(2)——谈谈Java的注解_第1张图片

你可能感兴趣的:(Android,Android开源框架源码分析)