【Android】Annotation(注解)完全解析

来源:互联网 发布:飞行燃料 支持淘宝 编辑:程序博客网 时间:2024/06/09 22:30

文章一:Android注解式绑定控件,没你想象的那么难

Android开发中,有一个让人又爱又恨的方法叫findViewById(int);我想如果你是一民Android开发者,必然知道这个方法,

为什么说findViewById(int);让人又爱又恨呢?想必大家也是很有感触。
写一个布局,用Java代码写和用xml文件写,完成速度完全是无法比拟的。xml布局太方便了。
同样的,想获取一个控件的对象,如果你是使用的xml布局文件写的布局,那么你必须调用findViewById()这个方法。

?
1
TextView t = (TextView) findViewById(R.id.x);

这是我们最常见的 获取xml布局中一个textview对象的过程。
那么问题就来了,这特么奇葩的方法名也太长了吧!!!好吧,其实人家名字起的也没有错,要描述清楚这函数的含义,也必须这么多个字母。

可是你丫的返回一个View让我用的时候还得强转,这也太麻烦了吧。我一行代码总共也就80列(Eclipse默认),缩进八格(方法写在类里面,语句写在方法里面),
就算像上面的例子textView对象只有一个字母,id也只有一个字母,这一个初始化也要占我54列了。要是变量名再长点,缩进层次再深点,这一个初始化就两行了。
一个界面至少也有四个控件吧,这么复杂的初始化,太坑爹了。
有问题总会有对应的解决办法,下面我就向大家介绍一下KJFrameForAndroid框架使用注解解决这种麻烦。

KJFrameForAndroid框架项目地址:https://github.com/kymjs/KJFrameForAndroid。

了解注解:

从jdk1.5开始,Java提供了注解的功能,允许开发者定义和使用自己的注解类型,该功能由一个定义注解类型的语法和描述一个注解声明的语法,读取注解的API,一个使用注解修饰的class文件和一个注解处理工具组成。
首先,你需要接受一个关键字@interface ,噢,它可不是接口定义关键字,更不是OC里面的@interface关键字,是Java中表示声明一个注解类的关键字。
使用@interface 表示我们已经继承了java.lang.annotation.Annotation类,这是一个注解的基类接口,就好像Object类,现在你只需要知道它的存在就行了。
还有一条规定:在定义注解时,不能继承其他的注解或接口。
那么,这就是最简单的一个注解类

?
1
2
3
public @interface MyAnnotation {
 
}

然而通常在使用时我们都会给这个注解类加上两个注解:

@Target(ElementType.FIELD)、@Retention(RetentionPolicy.RUNTIME)
ElementType、RetentionPolicy是两个枚举类,ElementType.FIELD表示我们需要注解的是一个字段,以下是摘自JDK1.6文档中的介绍:

使用注解:

以下为KJFrameForAndroid框架中绑定控件注解部分的定义与使用:

?
1
2
3
4
5
6
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
    public int id();
    public boolean click() default false;
}
?
1
2
@BindView(id = R.id.x, click = true)
private TextView t;

我们可以看到,除了明显减少了代码量,还使得代码结构更加清晰。
其中,定义部分的id() 表示注解接受一个int类型的数据作为id所对应的值(就如使用中的id = R.id.xxx);
同理,定义部分的click表示接受一个Boolean类型的数据作为click对应的值,还可以设置一个默认值使用default修饰;

处理注解:

我们已经知道了注解怎么定义和使用,接下来就应该知道怎么处理了。
上面已经说了,bindview注解可以接受一个int类型的值和一个Boolean类型的值,那么这两个值接受了以后如何获取呢?
其实获取的方式很简单就是通过一个BindView类型的对象,调用这个对象来自声明中定义的两个方法——>id()或click()方法。
现在就有一个问题了,注解类型是不能直接new对象的,那么这个BindView对象从哪里来呢?
这时就需要用到Java的反射机制。我们知道,每一个继承自Object类的类都会继承一个getClass()方法,下面看一下这个方法的原型:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    /**
     * Returns the unique instance of {@link Class} that represents this
     * object's class. Note that {@code getClass()} is a special case in that it
     * actually returns {@code Class<? extends Foo>} where {@code Foo} is the
     * erasure of the type of the expression {@code getClass()} was called upon.
     * <p>
     * As an example, the following code actually compiles, although one might
     * think it shouldn't:
     * <p>
     * <pre>{@code
     *   List<Integer> l = new ArrayList<Integer>();
     *   Class<? extends List> c = l.getClass();}</pre>
     *
     * @return this object's {@code Class} instance.
     */
    public final native Class<?> getClass();

是一个native方法,根据注释我们知道,这个方法返回的是该类的Class对象,同时也是该类的二进制对象。
Class中有一个方法叫getDeclaredFields(),是用来返回这个类的全部字段,返回类型是Field[]
通过Field对象的getAnnotation(Class<?>)方法,我们可以获取到任何一个Class的对象,通过getAnnotation(Class<?>),我们就可以获取到BindView的对象了。

例如:

?
1
2
3
4
5
6
7
8
9
Field[] fields = currentClass.getClass().getDeclaredFields();
for(int i = 0; i < fields.length; i++){
 
    BindView bindView = field.getAnnotation(BindView.class);
     
    int viewId = bindView.id();  //这是我们传的id
     
    boolean clickLis = bindView.click(); //这是我们传的click
}

在Android项目中应用:

至此,我们已经了解了注解,并且知道怎么使用,怎么处理注解了,现在只剩下最后一个问题:在项目中使用。
很简单,传一个Activity对象,调用findViewById()不就行了。
于是,我们可以这样
activity.findViewById( bindView.id() );
最后在我们的Activity中调用这个函数就OK了。

以下是Android应用框架KJFrameForAndroid中使用注解绑定控件的核心代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
     * @param currentClass
     *            当前类,一般为Activity或Fragment
     * @param sourceView
     *            待绑定控件的直接或间接父控件
     */
    public static void initBindView(Object currentClass, View sourceView) {
        // 通过反射获取到全部属性,反射的字段可能是一个类(静态)字段或实例字段
        Field[] fields = currentClass.getClass().getDeclaredFields();
        if (fields != null && fields.length > 0) {
            for (Field field : fields) {
                // 返回BindView类型的注解内容
                BindView bindView = field.getAnnotation(BindView.class);
                if (bindView != null) {
                    int viewId = bindView.id();
                    boolean clickLis = bindView.click();
                    try {
                        field.setAccessible(true);
                        if (clickLis) {
                            sourceView.findViewById(viewId).setOnClickListener(
                                    (OnClickListener) currentClass);
                        }
                        // 将currentClass的field赋值为sourceView.findViewById(viewId)
                        field.set(currentClass, sourceView.findViewById(viewId));
                    catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

其实安卓中的注解式绑定控件(也是所谓的IOC控制反转在安卓中的一种应用)其实本质的使用就是Java基础中反射的使用。值得一提的是,反射执行的效率是很低的
如果不是必要,应当尽量减少反射的使用,因为它会大大拖累你应用的执行效率。
顺带一提:我一直很排斥注解,因为类反射的效率太低了。现在有很多安卓应用开发框架,比如KJFrameForAndroid, xUtils, afinal, thinkAndroid,这些框架都是使用反射来起到注解绑定控件。
更有的框架甚至是一切东西都使用注解去完成,我只能说注解便捷,但请慎用。



文章二:最火框架XUtils之注解机制详解

在上一篇文章Android 最火的快速开发框架XUtils中简单介绍了xUtils的基本使用方法,这篇文章说一下xUtils里面的注解原理。

      先来看一下xUtils里面demo的代码:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. @ViewInject(R.id.tabhost)  
  2.     private FragmentTabHost mTabHost;  
  3. @ViewInject(R.id.big_img)  
  4.     private ImageView bigImage;  

       可能好多人一看就说这是个what,其实这是Java core里面的内容,做JavaEE的应该很熟悉,像著名的spring框架就用了大量的注解。那到底什么是注解呢?下面详细讲解一下Java注解:

       注解(Annotation) 为我们在代码中添加信息提供了一种形式化的方法,是我们可以在稍后某个时刻方便地使用这些数据(通过 解析注解 来使用这些数据),常见的作用有以下几种:

  • 生成文档。这是最常见的,也是java 最早提供的注解。常用的有@see @param @return 等
  • 跟踪代码依赖性,实现替代配置文件功能。比较常见的是spring 2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都使用了这种配置来减少配置文件的数量。也是
  • 在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。

       包 java.lang.annotation 中包含所有定义自定义注解所需用到的原注解和接口。如接口java.lang.annotation.Annotation 是所有注解继承的接口,并且是自动继承,不需要定义时指定,类似于所有类都自动继承Object。


      Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。

Annotation类型里面的参数该怎么设定: 
   第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型. 
   第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和      String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String. 
   第三,如果只有一个参数成员,最好把参数名称设为"value",后加小括号.

1、元注解

     元注解是指注解的注解。包括  @Retention @Target @Document @Inherited四种。

1.1、@Retention: 定义注解的保留策略

    @Retention(RetentionPolicy.SOURCE)//注解仅存在于源码中,在class字节码文件中不包含
    @Retention(RetentionPolicy.CLASS)// 默认的保留策略,注解会在class字节码文件中存在,但运行时无法得
    @Retention(RetentionPolicy.RUNTIME)// 注解会在class字节码文件中存在,在运行时可以通过反射获取到

1.2、@Target:定义注解的作用目标

其定义的源码为: 
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
     public @interface Target {
         ElementType[] value();
     }

    @Target(ElementType.TYPE)   //接口、类、枚举、注解

    @Target(ElementType.FIELD) //字段、枚举的常量
    @Target(ElementType.METHOD) //方法
    @Target(ElementType.PARAMETER) //方法参数
    @Target(ElementType.CONSTRUCTOR)  //构造函数
    @Target(ElementType.LOCAL_VARIABLE)//局部变量
    @Target(ElementType.ANNOTATION_TYPE)//注解
    @Target(ElementType.PACKAGE) ///包   
      其中的@interface是一个关键字,在设计annotations的时候必须把一个类型定义为@interface,而不能用class或interface关键字,由以上的源码可以知道,他的elementType 可以有多个,一个注解可以为类的,方法的,字段的等等。

1.3、@Document:说明该注解将被包含在javadoc中
 
1.4、@Inherited:说明子类可以继承父类中的该注解

2、java 注解的自定义

下面是自定义注解的一个例子

    @Retention(RetentionPolicy.RUNTIME)定义的这个注解是注解会在class字节码文件中存在,在运行时可以通过反射获取到。

    @Target({ElementType.TYPE,ElementType.METHOD})因此这个注解可以是类注解,也可以是方法的注解

这样一个注解就自定义好了,当然注解里面的成员可以为基本的数据类型,也可以为数据,Object等等

大概了解了一下Java注解机制,下面就说一说xUtils里面用到的注解,以及思维流程:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. package com.lidroid.xutils.view.annotation;  
  2.   
  3. import java.lang.annotation.ElementType;  
  4. import java.lang.annotation.Retention;  
  5. import java.lang.annotation.RetentionPolicy;  
  6. import java.lang.annotation.Target;  
  7.   
  8. @Target(ElementType.TYPE)  
  9. @Retention(RetentionPolicy.RUNTIME)  
  10. public @interface ContentView {  
  11.     int value();  
  12. }  
以上是ContentView的注解,一些声明、参数。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. private static void injectObject(Object handler, ViewFinder finder) {  
  2.   
  3.        Class<?> handlerType = handler.getClass();  
  4.   
  5.        // inject ContentView  
  6.        ContentView contentView = handlerType.getAnnotation(ContentView.class);  
  7.        if (contentView != null) {  
  8.            try {  
  9.                Method setContentViewMethod = handlerType.getMethod("setContentView"int.class);  
  10.                setContentViewMethod.invoke(handler, contentView.value());  
  11.            } catch (Throwable e) {  
  12.                LogUtils.e(e.getMessage(), e);  
  13.            }  
  14.        }}  

      以上是ViewUtils里面的一个静态注解对象函数,里面用到了上面声明的ContentView注解,getAnnotation是得到注解对象,handler是我们的activity传进来的指针,通过指针得到Class类型(这个是类的类)的handlerType,handlerType通过getMethod动态加载setContentView,setContentView大家都很熟悉就是Android里面的加载布局的函数,然后得到一个Method进行反射机制,实现函数加载。

      setContentViewMethod.invoke(handler, contentView.value());这句话也可以这么理解,那就是handler有setContentViewMethod这个方法,setContentViewMethod这个方法的参数是contentView.value()。

这样就明白了为什么这样

    @ContentView(R.layout.main)
     public class MyActivity extends FragmentActivity 就可以实现加载布局的操作了,其他的xUtils的注解操作也是类似的。

下面是一个简单流程图:



文章三:Java Annotation 及几个常用开源项目注解原理简析



转自http://my.oschina.net/kymjs/blog/305882
http://blog.csdn.net/luohai859/article/details/38019959
http://www.trinea.cn/android/java-annotation-android-open-source-analysis/
3 0
原创粉丝点击