Android开发你需要知道的注解(Annotation)

rcxv2526 7年前
   <p>一、什么是注解</p>    <ul>     <li>1、注解的作用</li>     <li> <p>2、注解都有哪些</p> </li>    </ul>    <p>二、自定义注解</p>    <ul>     <li> <p>1、RetentionPolicy.SOURCE</p> </li>     <li> <p>2、RetentionPolicy.RUNTIME</p> </li>     <li> <p>3、RetentionPolicy.CLASS</p> </li>    </ul>    <p>【说在前面的话】</p>    <ul>     <li> <p>要想 看懂很多开源库 ,如Arouter, dagger,Butter Knife等,不得不先看懂注解;</p> </li>     <li> <p>想更好地提升开发效率和代码质量,注解可以帮上很大的忙;</p> </li>    </ul>    <h2>一、什么是注解</h2>    <p>java.lang.annotation,接口 Annotation,在JDK5.0及以后版本引入。</p>    <p>注解是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用Annotation,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证、处理或者进行部署。</p>    <p>在运行时读取需要使用Java反射机制进行处理。</p>    <p>Annotation不能运行,它只有成员变量,没有方法。Annotation跟public、final等修饰符的地位一样,都是程序元素的一部分,Annotation不能作为一个程序元素使用。</p>    <p>其实大家都是用过注解的,只是可能没有过深入了解它的原理和作用,比如肯定见过 @Override , @Deprecated 等。</p>    <h2>1、注解的作用</h2>    <p>注解将一些本来重复性的工作,变成程序自动完成,简化和自动化该过程。比如用于生成Java doc,比如编译时进行格式检查,比如自动生成代码等,用于提升软件的质量和提高软件的生产效率。</p>    <h2>2、注解都有哪些</h2>    <p>平时我们使用的注解有来自JDK里包含的,也有Android SDK里包含的,也可以自定义。</p>    <p>2.1、JDK定义的元注解</p>    <p>Java提供了四种元注解,专门负责新注解的创建工作,即注解其他注解。</p>    <ul>     <li> <p>@Target</p> <p>定义了Annotation所修饰的对象范围,取值:</p>      <ul>       <li> <p>ElementType.CONSTRUCTOR :用于描述构造器</p> </li>       <li> <p>ElementType.FIELD :用于描述域</p> </li>       <li> <p>ElementType.LOCAL_VARIABLE :用于描述局部变量</p> </li>       <li> <p>ElementType.METHOD :用于描述方法</p> </li>       <li> <p>ElementType.PACKAGE :用于描述包</p> </li>       <li> <p>ElementType.PARAMETER :用于描述参数</p> </li>       <li> <p>ElementType.TYPE :用于描述类、接口(包括注解类型) 或enum声明</p> </li>      </ul> </li>     <li> <p>@Retention</p> <p>定义了该Annotation被保留的时间长短,取值:</p> </li>    </ul>    <p>- RetentionPoicy.SOURCE :注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;用于做一些检查性的操作,比如 @Override 和 @SuppressWarnings</p>    <p>- RetentionPoicy.CLASS: 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;用于在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife )</p>    <p>- RetentionPoicy.RUNTIME :注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;用于在运行时去动态获取注解信息。</p>    <ul>     <li> <p>@Documented</p> <p>标记注解,用于描述其它类型的注解应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化,不用赋值。</p> </li>     <li> <p>@Inherited</p> <p>标记注解,允许子类继承父类的注解。 这里一开始有点理解不了,需要断句一下,允许子类继承 父类的注解 。示例:</p> </li>    </ul>    <pre>  <code class="language-java">Target(value = ElementType.TYPE)    @Retention(RetentionPolicy.RUNTIME)    @Inherited    public @interface Sample {         public String name() default "";        }      @Sample    class Test{    }      class Test2 extents Test{    }</code></pre>    <p>这样类Test2其实也有注解@Sample 。</p>    <p>如果成员名称是value,在赋值过程中可以简写。如果成员类型为数组,但是只赋值一个元素,则也可以简写。</p>    <p>示例以下三个写法都是等价的。</p>    <p>正常写法</p>    <pre>  <code class="language-java">@Documented  @Retention(value = RetentionPolicy.RUNTIME)  @Target(value = {ElementType.ANNOTATION_TYPE})  public @interface Target {      ElementType[] value();  }</code></pre>    <p>省略value的写法(只有成员名称是value时才能省略)</p>    <pre>  <code class="language-java">@Documented  @Retention(RetentionPolicy.RUNTIME)  @Target({ElementType.ANNOTATION_TYPE})  public @interface Target {      ElementType[] value();  }</code></pre>    <p>成员类型是数组,只赋值一个元素的简写</p>    <pre>  <code class="language-java">@Documented  @Retention(RetentionPolicy.RUNTIME)  @Target(ElementType.ANNOTATION_TYPE)  public @interface Target {      ElementType[] value();  }</code></pre>    <p>2.2 JDK内置的其他注解</p>    <p>@Override、@Deprecated、@SuppressWarnings、@SafeVarargs、@FunctionalInterface、@Resources 等。</p>    <p>2.3 Android SDK内置的注解</p>    <p>Android SDK 内置的注解都在包com.android.support:support-annotations里,下面以'com.android.support:support-annotations:25.2.0'为例</p>    <ul>     <li> <p>资源引用限制类:用于限制参数必须为对应的资源类型</p> <p>@AnimRes @AnyRes @ArrayRes @AttrRes @BoolRes @ColorRes等</p> </li>     <li> <p>线程执行限制类:用于限制方法或者类必须在指定的线程执行</p> <p>@AnyThread @BinderThread @MainThread @UiThread @WorkerThread</p> </li>     <li> <p>参数为空性限制类:用于限制参数是否可以为空</p> <p>@NonNull @Nullable</p> </li>     <li> <p>类型范围限制类:用于限制标注值的值范围</p> <p>@FloatRang @IntRange</p> </li>     <li> <p>类型定义类:用于限制定义的注解的取值集合</p> <p>@IntDef @StringDef</p> </li>     <li> <p>其他的功能性注解:</p> <p>@CallSuper @CheckResult @ColorInt @Dimension @Keep @Px @RequiresApi @RequiresPermission @RestrictTo @Size @VisibleForTesting</p> </li>    </ul>    <h2>二、自定义注解</h2>    <p>使用收益最大的,还是需要根据自身需求自定义注解。下面依次介绍三种类型的注解自定义示例:</p>    <h2>1、RetentionPolicy.SOURCE</h2>    <p>一般函数的参数值有限定的情况,比如View.setVisibility 的参数就有限定,可以看到View.class源码里</p>    <p>除了 IntDef ,还有 StringDef</p>    <pre>  <code class="language-java">@IntDef({VISIBLE, INVISIBLE, GONE})   @Retention(RetentionPolicy.SOURCE)   public @interface Visibility {}      public static final int VISIBLE = 0x00000000;      public static final int INVISIBLE = 0x00000004;    public static final int GONE = 0x00000008;        public void setVisibility(@Visibility int visibility) {      setFlags(visibility, VISIBILITY_MASK);  }</code></pre>    <h2>2、RetentionPolicy.RUNTIME</h2>    <p>运行时注解的定义如下:</p>    <pre>  <code class="language-java">// 适用类、接口(包括注解类型)或枚举    Retention(RetentionPolicy.RUNTIME)    Target(ElementType.TYPE)    public @interface ClassInfo {        String value();    }    // 适用field属性,也包括enum常量    @Retention(RetentionPolicy.RUNTIME)    @Target(ElementType.FIELD)    public @interface FieldInfo {        int[] value();    }    // 适用方法    @Retention(RetentionPolicy.RUNTIME)    @Target(ElementType.METHOD)    public @interface MethodInfo {        String name() default "long";        int age() default 27;    }</code></pre>    <p>定义一个测试类来使用这些注解:</p>    <pre>  <code class="language-java">/**    * 测试运行时注解    */    @ClassInfo("Test Class")    public class TestRuntimeAnnotation {            @FieldInfo(value = {1, 2})        public String fieldInfo = "FiledInfo";              @MethodInfo(name = "BlueBird")        public static String getMethodInfo() {            return return fieldInfo;        }    }</code></pre>    <p>使用注解:</p>    <pre>  <code class="language-java">/**    * 测试运行时注解    */    private void _testRuntimeAnnotation() {        StringBuffer sb = new StringBuffer();        Class<?> cls = TestRuntimeAnnotation.class;        Constructor<?>[] constructors = cls.getConstructors();        // 获取指定类型的注解        sb.append("Class注解:").append("\n");        ClassInfo classInfo = cls.getAnnotation(ClassInfo.class);        if (classInfo != null) {            sb.append(cls.getSimpleName()).append("\n");            sb.append("注解值: ").append(classInfo.value()).append("\n\n");        }            sb.append("Field注解:").append("\n");        Field[] fields = cls.getDeclaredFields();        for (Field field : fields) {            FieldInfo fieldInfo = field.getAnnotation(FieldInfo.class);            if (fieldInfo != null) {                sb.append(field.getName()).append("\n");                sb.append("注解值: ").append(Arrays.toString(fieldInfo.value())).append("\n\n");            }        }            sb.append("Method注解:").append("\n");        Method[] methods = cls.getDeclaredMethods();        for (Method method : methods) {            MethodInfo methodInfo = method.getAnnotation(MethodInfo.class);            if (methodInfo != null) {                sb.append(Modifier.toString(method.getModifiers())).append(" ")                        .append(method.getName()).append("\n");                sb.append("注解值: ").append("\n");                sb.append("name: ").append(methodInfo.name()).append("\n");                sb.append("age: ").append(methodInfo.age()).append("\n");            }        }            System.out.print(sb.toString());    }</code></pre>    <p>所做的操作都是通过反射获取对应元素,再获取元素上面的注解,最后得到注解的属性值。因为涉及到反射,所以运行时注解的效率多少会受到影响,现在很多的开源项目使用的是编译时注解。</p>    <h2>3、RetentionPolicy.CLASS</h2>    <p>3.1 添加依赖</p>    <p>如果Gradle 插件是2.2以上的话,不需要添加以下 android-apt 依赖。</p>    <pre>  <code class="language-java">classpath 'com.android.tools.build:gradle:2.2.1'</code></pre>    <p>在整个工程的 build.gradle 中添加 android-apt 的依赖</p>    <pre>  <code class="language-java">buildscript {        repositories {            jcenter()            mavenCentral()  // add        }        dependencies {            classpath 'com.android.tools.build:gradle:2.1.2' //2.2以上无需添加apt依赖           classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'  // add        }    }</code></pre>    <p>android-apt :</p>    <p>android-apt 是一个Gradle插件,协助Android Studio 处理annotation processors, 它有两个目的:</p>    <ul>     <li> <p>允许配置只在编译时作为注解处理器的依赖,而不添加到最后的APK或library</p> </li>     <li> <p>设置源路径,使注解处理器生成的代码能被Android Studio正确的引用</p> </li>    </ul>    <p>伴随着 Android Gradle 插件 2.2 版本的发布,近期 android-apt 作者在官网发表声明证实了后续将不会继续维护 android-apt,并推荐大家使用 Android 官方插件提供的相同能力。也就是说,大约三年前推出的 android-apt 即将告别开发者,退出历史舞台,Android Gradle 插件提供了名为 annotationProcessor 的功能来完全代替 android-apt。</p>    <p>3.2 定义要使用的注解</p>    <p>建一个Java库来专门放注解,库名为:annotations</p>    <p>定义注解</p>    <pre>  <code class="language-java">@Retention(RetentionPolicy.CLASS)    @Target(ElementType.TYPE)    public @interface MyAnnotation {        String value();    }</code></pre>    <p>3.3 定义注解处理器</p>    <p>另外建一个Java库工程,库名为:processors</p>    <p>这里必须为Java库,不然会找不到javax包下的相关资源</p>    <p>build.gradle 要依赖以下:</p>    <pre>  <code class="language-java">compile 'com.google.auto.service:auto-service:1.0-rc2'   compile 'com.squareup:javapoet:1.7.0'        compile(project(':annotations'))</code></pre>    <p>其中,</p>    <p>auto-service 自动用于在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件;</p>    <p>javapoet用于产生 .java 源文件的辅助库,它可以很方便地帮助我们生成需要的.java 源文件</p>    <p>示例:</p>    <pre>  <code class="language-java">package com.example;    import com.google.auto.service.AutoService;  import com.squareup.javapoet.JavaFile;  import com.squareup.javapoet.MethodSpec;  import com.squareup.javapoet.TypeSpec;    import java.io.IOException;  import java.util.LinkedHashSet;  import java.util.Set;    import javax.annotation.processing.AbstractProcessor;  import javax.annotation.processing.Filer;  import javax.annotation.processing.ProcessingEnvironment;  import javax.annotation.processing.Processor;  import javax.annotation.processing.RoundEnvironment;  import javax.lang.model.SourceVersion;  import javax.lang.model.element.Element;  import javax.lang.model.element.ElementKind;  import javax.lang.model.element.Modifier;  import javax.lang.model.element.TypeElement;    @AutoService(Processor.class)  public class MyProcessor extends AbstractProcessor {        private Filer filer;        @Override      public synchronized void init(ProcessingEnvironment processingEnv) {          super.init(processingEnv);          // Filer是个接口,支持通过注解处理器创建新文件          filer = processingEnv.getFiler();      }        @Override      public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {          for (TypeElement element : annotations) {              //新建文件              if (element.getQualifiedName().toString().equals(MyAnnotation.class.getCanonicalName())) {                  // 创建main方法                  MethodSpec main = MethodSpec.methodBuilder("main")                          .addModifiers(Modifier.PUBLIC, Modifier.STATIC)                          .returns(void.class)                          .addParameter(String[].class, "args")                          .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")                          .build();                  // 创建HelloWorld类                  TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")                          .addModifiers(Modifier.PUBLIC, Modifier.FINAL)                          .addMethod(main)                          .build();                    try {                      // 生成 com.example.HelloWorld.java                      JavaFile javaFile = JavaFile.builder("com.example", helloWorld)                              .addFileComment(" This codes are generated automatically. Do not modify!")                              .build();                      //    生成文件                      javaFile.writeTo(filer);                  } catch (IOException e) {                      e.printStackTrace();                  }              }          }            //在Gradle console 打印日志          for (Element element : roundEnv.getElementsAnnotatedWith(MyAnnotation.class)) {              System.out.println("------------------------------");              // 判断元素的类型为Class              if (element.getKind() == ElementKind.CLASS) {                  // 显示转换元素类型                  TypeElement typeElement = (TypeElement) element;                  // 输出元素名称                  System.out.println(typeElement.getSimpleName());                  // 输出注解属性值System.out.println(typeElement.getAnnotation(MyAnnotation.class).value());              }              System.out.println("------------------------------");          }          return true;      }        @Override      public Set<String> getSupportedAnnotationTypes() {          Set<String> annotataions = new LinkedHashSet<String>();          annotataions.add(MyAnnotation.class.getCanonicalName());          return annotataions;      }        @Override      public SourceVersion getSupportedSourceVersion() {          return SourceVersion.latestSupported();      }  }</code></pre>    <p>3.4 在代码中使用定义的注解:</p>    <p>需要依赖上面的两个java库annotations和processors</p>    <pre>  <code class="language-java">import com.example.MyAnnotation;    @MyAnnotation("test")  public class MainActivity extends AppCompatActivity {        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);      }  }</code></pre>    <p>编译后就会生成指定包名的指定文件,如图:</p>    <p><img src="https://simg.open-open.com/show/39af1e419bac0235d4e9860bfdd789f8.png"></p>    <p>3.5 注解处理器的辅助接口</p>    <p>在自定义注解处理器的初始化接口,可以获取到以下4个辅助接口:</p>    <pre>  <code class="language-java">public class MyProcessor extends AbstractProcessor {            private Types typeUtils;        private Elements elementUtils;        private Filer filer;        private Messager messager;            @Override        public synchronized void init(ProcessingEnvironment processingEnv) {            super.init(processingEnv);            typeUtils = processingEnv.getTypeUtils();            elementUtils = processingEnv.getElementUtils();            filer = processingEnv.getFiler();            messager = processingEnv.getMessager();        }    }</code></pre>    <p>Types : Types是一个用来处理TypeMirror的工具</p>    <p>Elements : Elements是一个用来处理Element的工具</p>    <p>Filer : 一般我们会用它配合JavaPoet来生成我们需要的.java文件</p>    <p>Messager : Messager提供给注解处理器一个报告错误、警告以及提示信息的途径</p>    <p>3.5 带有注解的库提供给第三方</p>    <p>以下例子默认用gradle插件2.2以上,不再使用apt</p>    <p>一般使用编译时注解的库,都会有三个module:</p>    <ul>     <li> <p>定义注解的module , java库,xxxx-annotations</p> </li>     <li> <p>实现注解器的module, java库,xxxx-compiler</p> </li>     <li> <p>提供对外接口的module, android库,xxxx-api</p> </li>    </ul>    <p>module xxxx-api的依赖这么写:</p>    <p>`dependencies {</p>    <pre>  <code class="language-java">annotationProcessor 'xxxx-compiler:1.0.0'  compile ' xxxx-annotations:1.0.0'  //....others</code></pre>    <p>}</p>    <p>`</p>    <p>然后第三方使用时,可以像如下这样依赖:</p>    <pre>  <code class="language-java">dependencies {      ...      compile 'com.google.dagger:dagger:2.9'      annotationProcessor 'com.google.dagger:dagger-compiler:2.9'  }</code></pre>    <p>sample project见 <a href="/misc/goto?guid=4959749948314083234" rel="nofollow,noindex">AnnotationSample</a></p>    <p>参考链接:</p>    <p><a href="/misc/goto?guid=4959749948436555472" rel="nofollow,noindex">http://www.jb51.net/article/8...</a></p>    <p><a href="/misc/goto?guid=4959749948539698836" rel="nofollow,noindex">http://blog.csdn.net/github_3...</a></p>    <p><a href="/misc/goto?guid=4959749948636762248" rel="nofollow,noindex">http://blog.csdn.net/github_3...</a></p>    <p><a href="/misc/goto?guid=4959749948731544506" rel="nofollow,noindex">https://www.zhihu.com/questio...</a></p>    <p><a href="/misc/goto?guid=4959749948838083999" rel="nofollow,noindex">https://github.com/alibaba/AR...</a></p>    <p><a href="/misc/goto?guid=4959749948926826747" rel="nofollow,noindex">http://blog.csdn.net/asce1885...</a></p>    <p> </p>    <p>来自: github.com/ShowJoy-com/showjoy-blog</p>    <p> </p>