Android中使用注解

在Java Web的开始中注解的使用相当的相当的广泛,比如Controller,Service,Component等的注解能够很好的解决耦合的问题。现在在 Android中也开始用的越来越广泛,比如ButterKnife,Retrofit,EventBus等等都选择使用注解来配置。

在现阶段的Android开发中,注解越来越流行起来,按照处理时期,注解又分为两种类型,一种是运行时注解,另一种是编译时注解,运行时注解由于性能问题被一些人所诟病,编译时注解的核心原理依赖APT(Annotation Processing Tools)实现,例如,著名的ButterKnife,Dragger,Retrofit等开源库都是基于APT。那么编译期注解是如何工作的?

编译时Annotation解析的基本原理是,在某些代码元素上(如类型、函数、字段等)添加注解,在编译时编译器会检查AbstractProcessor的子类,并且调用该类型的process函数,然后将添加了注解的所有元素都传递到process函数中,使得开发人员可以在编译器进行相应的处理,例如,根据注解生成新的Java类,这也就是ButterKnife等开源库的基本原理。

在编译处理的时候,是分开处理的。如果在某个处理中产生了新的Java源文件,那么就需要另外一个处理来处理新生成的源文件,如此反复,直到没有新文件被生成为止。在完成处理之后,再对Java代码进行编。JDK 5中提供了APT工具用来对注解进行处理。APT是一个命令行工具,与之配套的还有一套用来描述程序语义结构的Mirror API,Mirror API描述的是程序在编译时的静态结构,通过Mirror API可以获取到被注解的Java类型元素的信息,从而提供相应的处理逻辑,具体的处理工作交给APT工具来完成。编写注解处理器的核心是AnnotationProcessorFactoryAnnotationProcessor两个接口,后者便是德是注解处理器,从前者则是为某些注解类型创建注解处理器的工厂。

对于编译器来说,代码中的元素结构是基本不变的,例如,组成代码的基本元素有包,类,函数,字段,字段参数,变量。JDK中为这些元素定义了一个基类,也就是Element类,它有如下几个子类:

  1. PackageElement——包元素,包含了某个包下的信息,可以获取到包名等;
  2. TypeElement——类型元素,如某个字段属于某种类型;
  3. ExecutableElement——可执行元素,代表了函数类型的元素;
  4. VariableElement——变量元素;
  5. TypeParameterElement——类型参数元素。

因为注解可以指定作用在哪些元素上,因此,通过上述的抽象来对应这些元素,例如下面的这个注解,指定的是只能作用于函数上面,并且这个注解只能保留在class文件中:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Test {

    String value();
}

这个注解因为只能作用于函数类型,因此,它对应的元素类型就是ExecutableElement当我们想通过APT处理这个注解的时候就可以获取目标对象上的Test注解,并且将所有这些元素转换为ExecutableElement元素,以便获取到他们对应的信息。

我们看看你元素基类的的实现(在线文档地址):

package javax.lang.model.element;

import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ElementVisitor;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.type.TypeMirror;

public interface Element {
    TypeMirror asType();
    //获取元素类型
    ElementKind getKind();

    List<? extends AnnotationMirror> getAnnotationMirrors();

    <A extends Annotation> A getAnnotation(Class<A> var1);
    //获取修饰符 如public,static ,final等
    Set<Modifier> getModifiers();

    Name getSimpleName();

    Element getEnclosingElement();

    List<? extends Element> getEnclosedElements();

    boolean equals(Object var1);

    int hashCode();
    //接受访问者的访问
    <R, P> R accept(ElementVisitor<R, P> var1, P var2);
}

我们看到Element定义了一个代码元素的一些通用接口,其中很显眼的就是accept函数,这个函数接受一个ElementVisitor和类型为P的参数。

public interface ElementVisitor<R, P> {
    //访问元素
    R visit(Element e, P p);

    R visit(Element e);

    //访问包元素
    R visitPackage(PackageElement e, P p);

    //访问类型元素
    R visitType(TypeElement e, P p);

   //访问变量元素
    R visitVariable(VariableElement e, P p);

    //访问克而执行元素
    R visitExecutable(ExecutableElement e, P p);

    //访问参数元素
    R visitTypeParameter(TypeParameterElement e, P p);

    //处理位置的元素类型,这是为了应对后续Java语言的扩折而预留的接口,例如后续元素类型添加了,那么通过这个接口就可以处理上述没有声明的类型
    R visitUnknown(Element e, P p);
}

在ElementgVisitor中定义了多个visit接口,每个接口处理一种元素类型,这就是典型的访问者模式。我们制定,一个类元素和函数元素是完全不一样的,他们的结构不一样,因此,在编译器对他们的操作肯定是不一样,通过访问者模式正好可以解决数据结构与数据操作分离的问题,避免某些操作污染数据对象类。

Andrioid使用编译时注解来Bind View

在Android开发张,我们接触的比较多的编译期注解的库应该就是ButterKnife,这个库是针对View,资源ID,部分事件等进行注解的开源库,它能够去除掉一些不怎么雅观的样板式代码,使得我们的代码更加简洁,易于维护,同事给予APT也能使得它的效率得到保证。ButterKnife是针对View等进行注解的开源库,如果要对对象进行注解可以使用Square公司的Dagger开源库,目前该库已经交由Google维护,新的名字为Dagger 2

class ExampleActivity extends Activity {
  @BindView(R.id.user) EditText username;
  @BindView(R.id.pass) EditText password;

  @BindString(R.string.login_error) String loginErrorMessage;

  @OnClick(R.id.submit) void submit() {
    // TODO call server...
  }

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
    // TODO Use fields...
  }
}

例如上面的,使用BindView进行加载View,不再需要findviewById并进行强转的代码。

ButterKnife.bind(this);要放在setContentView之后,使用控件的代码之前。

注解只存在于class文件中,因为,一旦过了编译期就不再需要它了。

使用编译时注解实现View注入的实例请查看下一文章 (//www.zhangningning.com.cn/blog/Android/android_rentention_sample.html