Android注解的理解与使用

南笙酒味 提交于 2019-11-30 03:47:12

为什么是注解

作为Android开发人员,先看一些熟悉的代码:

setContentView(R.layout.activity_main);  Toolbar toolbar = findViewById(R.id.toolbar);  new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.xxx, parent, false))); 

像这样的不可省略代码,大量出现在各个地方,一遍一遍的被复制,复制过程中改变很少,或者根本没有改变。在计算机编程中称之为样板代码。经常使用类似的样板,直接后果导致程序员编写更多代码,做更少的工作。这显然是不可以忍受的。

《重构改善既有代码的设计》章节三:代码的坏味道中,我们学到,多个相同的程序结构,将他们合二为一,程序会变得更好。最直观体现就是使用工具类抽取公共方法各处调用。这样就可以更少的代码,更高的效率了。类似的,使用注解解决Android中的样板代码问题。

上面的引子的目的是要直观地说明,为什么需要注解?术语话一些如下:

> 每当你创建描述符性质的类和接口的时,一旦其中包含重复性工作,就可以考虑简化与自动化该过程。

> 如果你想为应用设置很多的常量或参数,xml是一个很好的选择,因为它不会同特定的代码相连。如果你想把某个方法声明为服务,那么使用Annotation会更好一些,因为这种情况下需要注解和方法紧密耦合起来,开发人员也必须意识到这一点。

注解如何工作

示例:

package java.lang; @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { } 

这是JDK的源码,可以说里面几乎啥也没写,我们平时在重写父类或接口方法时,使用@Override,编译器就知道调用我们重写后的方法。这说明编译器在某些地方根据这个注解,对程序做了对应的处理。

我们来看下注解的定义:

> 源代码的元数据,即描述数据的数据。

可以看到注解是描述数据用的,根据我们在@Override上的推断,还应该有相对应的代码,根据这个描述(注解)来处理注解标记了的类,方法,变量,参数。

所以,当自定义注解时候,要定义注解,还要根据注解实现对应的业务逻辑。

注解类型

java.lang.Override这个注解类的实现。看到自定义注解上还有注解,这称之为元注解

元注解

Documented

@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.ANNOTATION_TYPE}) public @interface Documented { } 

没有值,简单的标记注解,标识是否将注解信息包含在java文档中

Retention

@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.ANNOTATION_TYPE}) public @interface Retention {   RetentionPolicy value(); } 
public enum RetentionPolicy {   SOURCE,   CLASS,   RUNTIME;    private RetentionPolicy() {   } } 

标识在什么时候使用该注解,注解的生命周期。有三个值:SOURCE,CLASS,RUNTIME

  • RetentionPolicy.SOURCE:有效期在源码阶段,在编译阶段丢弃,这些注解在编译结束后不会有任何意义,也不会写入字节码中。
  • RetentionPolicy.CLASS:有效期至字节码文件。在类加载的时候丢弃,注解默认使用这种方式。
  • RetentionPolicy.RUNTIME:始终有效,在运行时也会保留。因此可以使用反射读取该注解的信息,自定义注解通常使用这种方式。

Target

@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.ANNOTATION_TYPE}) public @interface Target {   ElementType[] value(); } 
public enum ElementType {   TYPE,   FIELD,   METHOD,   PARAMETER,   CONSTRUCTOR,   LOCAL_VARIABLE,   ANNOTATION_TYPE,   PACKAGE,   TYPE_PARAMETER,   TYPE_USE;    private ElementType() {   } } 

表示在什么地方使用该注解。如果没有指定,则该注解可以放在任意地方。

  • ElementType.TYPE:用于描述类,接口(包括注解类型),Enum
  • ElementType.FIELD:用于描述实例变量(包括枚举的常量)
  • ElementType.METHOD:用于描述方法
  • ElementType.PARAMETER:用于描述参数
  • ElementType.CONSTRUCTOR:用于构造方法
  • ElementType.LOCAL_VARIABLE:用于描述局部变量
  • ElementType.ANNOTATION_TYPE :用于描述注解
  • ElementType.PACKAGE:用于描述包
  • ElementType.TYPE_PARAMETER:since 1.8 表示该注解能写在类型变量的声明语句中
  • ElementType.TYPE_USE:since 1.8 表示该注解能写在使用类型的任何语句中

Inherited

@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { } 

表示该注解类型被自动继承,如果用户在当前类中查询这个元注解类型,但是当前类的声明中不包含这个元注解类型,那么将自动查询其父类,直至查到该注解或到达顶层类。

内建注解

@Override

@Target({ElementType.METHOD}) @Retention(RetentionPolicy.SOURCE) public @interface Override { } 
  • 适用于方法
  • 仅保留在源码阶段
  • 用于表示重写超类方法,如果带有此标记的方法没有在超类出现,那么编译器报错

@Deprecated

@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE}) public @interface Deprecated { } 
  • 注解信息包含在java文档中
  • 适用于所有地方
  • 始终有效
  • 表示过时了的api

@SuppressWarnings

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings {   String[] value(); } 
  • 适用于所有地方
  • 仅保留在源码阶段
  • 表示抑制警告

自定义注解

butterknife 为例

annotations

@Target(METHOD) @Retention(RUNTIME) @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. */   @IdRes int[] value() default { View.NO_ID }; } 
  • 适用于方法
  • 始终有效
  • 自定义元注解,标识该注解使用的api范围

自定义APT

Annotation Processor Tool这里我们可以自己使用反射实现,也可以继承javax.annotation.processing.AbstractProcessor

public class MyProcessor extends AbstractProcessor {       @Override     public synchronized void init(ProcessingEnvironment processingEnv) {         super.init(processingEnv);     }       @Override     public Set<string> getSupportedAnnotationTypes() {         return null;     }       @Override     public SourceVersion getSupportedSourceVersion() {         return SourceVersion.latestSupported();     }       @Override     public boolean process(Set<!--? extends TypeElement--> annotations, RoundEnvironment roundEnv) {         return true;     } } 

getSupportedAnnotationTypes:该处理器是处理哪些注解的。

butterknife实现:

 @Override public Set<string> getSupportedAnnotationTypes() {     Set<string> types = new LinkedHashSet&lt;&gt;();     for (Class<!--? extends Annotation--> annotation : getSupportedAnnotations()) {       types.add(annotation.getCanonicalName());     }     return types;   }    private Set<class<? extends annotation>&gt; getSupportedAnnotations() {     Set<class<? extends annotation>&gt; annotations = new LinkedHashSet&lt;&gt;();      annotations.add(BindAnim.class);     annotations.add(BindArray.class);     annotations.add(BindBitmap.class);     annotations.add(BindBool.class);     annotations.add(BindColor.class);     annotations.add(BindDimen.class);     annotations.add(BindDrawable.class);     annotations.add(BindFloat.class);     annotations.add(BindFont.class);     annotations.add(BindInt.class);     annotations.add(BindString.class);     annotations.add(BindView.class);     annotations.add(BindViews.class);     annotations.addAll(LISTENERS);      return annotations;   } 

getSupportedSourceVersion:支持的java版本,一般不需要重写。

process(Set<!--? extends TypeElement--> annotations, RoundEnvironment roundEnv):扫描和处理注解并生成java代码。代码生成java源码比较痛苦,使用javapoet

butterknifeBindView实现:

 // Process each @BindView element.     for (Element element : env.getElementsAnnotatedWith(BindView.class)) {       // we don't SuperficialValidation.validateElement(element)       // so that an unresolved View type can be generated by later processing rounds       try {         parseBindView(element, builderMap, erasedTargetNames);       } catch (Exception e) {         logParsingError(element, BindView.class, e);       }     } 

注册到javac

@AutoService(Processor.class)  public class MyProcessor extends AbstractProcessor { ... } 
implementation 'com.google.auto.service:auto-service:1.0-rc5' 

javac注册自定义的注解处理器,然后javac在编译时才会调用我们自定义的处理方法。

调用

普通方法:

submit.setOnClickListener(new View.OnClickListener() {     @Override public void onClick(View v) {         // TODO ...     } }) 

lamda:

submit.setOnClickListener(v -&gt; {     // TODO ... }) 

butterknife:

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

脑图

实战

网上有很多关于AnnotationProcessorTool的示例,尤其以仿写butterknife的为多。要手敲一个例子,加深一下印象。

前提要点:

  1. 只有注解而没有对应处理器,那注解将毫无作用
  2. 注解处理器生成java源码,而不是字节码
  3. 如果读了《深入理解java虚拟机 第四部分:程序编译与代码优化》就更好了,没读过也不影响
  4. 使用JavaPoetgoogleAutoService

可能出现的问题

Android Studio 启用Annotation Process Tool

在我的Adnroid 3.4版本上,需要设置启用一下,步骤如下:

File -> Close Project 进入:

Configure中找到Project Defaults层级里的Settings,如图:

勾选Enable annotation processing

@AutoService(Processor.class)没有生成源码

我们自己编写也好,运行网上的源码也好,如果构建顺利,目录.\app\build\generated\source\apt\debug下会出现在注解处理器中指定生成的.java文件。也有可能构建没有出错,但是什么也没有生成。

网上通用教程,一般有两种方式把注解处理器注册到javac。手写javax.annotation.processing.Processor文件和使用 com.google.auto.service:auto-service:1.0-rc5库。

没有生成源码,就是javac没有执行我们的注解处理器中的逻辑。

本人的实践,确定已经启用了注解处理器的前提下,使用 com.google.auto.service:auto-service:1.0-rc5库没有生成源码,手写文件则成功生成了,这花费了我一上午去找原因,感觉很不值得,中断了连贯的学习,打击也挺大的。

Javapoet

这是个好用的库,方便生成Java源文件,建议跟着网上教程,花费一个小时左右时间,敲一下例子,提高直观感觉,然后再实际使用会比较顺畅。

调试APT

这个自行搜索吧,远程调试配合Android Studio的快速构建,总是产生Connection refused让人用得好难受,每次调试都要或Clean或改代码或Invalidate Caches,把我给虐惨了,所以 本人实践中是直接查看产生的java文件的。 </https:></class<?></class<?></string></string></string></https:></https:>

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!