Android注解基础介绍快速入门与解读

首先什么是注解?@Override就是注解,它的作用是:

1、检查是否正确的重写了父类中的方法。
2、标明代码,这是一个重写的方法。

1、体现在于:检查子类重写的方法名与参数类型是否正确;检查方法private/final/static等不能被重写。实际上@Override对于应用程序并没有实际影响,从它的源码中可以出来。

2、主要是表现出代码的可读性。

作为Android开发中熟知的注解,Override只是注解的一种体现,更多时候,注解还有以下作用:

降低项目的耦合度。

自动完成一些规律性的代码。

自动生成java代码,减轻开发者的工作量。

一、注解基础快读

1、元注解

元注解是由java提供的基础注解,负责注解其它注解,如上图Override被@Target@Retention修饰,它们用来说明解释其它注解,位于sdk/sources/android-25/java/lang/annotation路径下。

元注解有:

@Retention:注解保留的生命周期

@Target:注解对象的作用范围。

@Inherited:@Inherited标明所修饰的注解,在所作用的类上,是否可以被继承。

@Documented:如其名,javadoc的工具文档化,一般不关心。

@Retention

Retention说标明了注解被生命周期,对应RetentionPolicy的枚举,表示注解在何时生效:

SOURCE:只在源码中有效,编译时抛弃,如上面的@Override

CLASS:编译class文件时生效。

RUNTIME:运行时才生效。

如下图,com.android.support:support-annotations中的Nullable注解,会在编译期判断,被注解的参数是否会空,具体后续分析。

@Target

Target标明了注解的适用范围,对应ElementType枚举,明确了注解的有效范围。

TYPE:类、接口、枚举、注解类型。

FIELD:类成员(构造方法、方法、成员变量)。

METHOD:方法。

PARAMETER:参数。

CONSTRUCTOR:构造器。

LOCAL_VARIABLE:局部变量。

ANNOTATION_TYPE:注解。

PACKAGE:包声明。

TYPE_PARAMETER:类型参数。

TYPE_USE:类型使用声明。

如上图所示,@Nullable可用于注解方法,参数,类成员,注解,包声明中,常用例子如下所示:

 /**
  * Nullable表明
  * bind方法的参数target和返回值Data可以为null
  */
 @Nullable
 public static Data bind(@Nullable Context target) {  //do someThing and return
  return bindXXX(target);
 }

@Inherited

注解所作用的类,在继承时默认无法继承父类的注解。除非注解声明了 @Inherited。同时Inherited声明出来的注,只对类有效,对方法/属性无效。

如下方代码,注解类@AInherited声明了Inherited ,而注解BNotInherited 没有,所在在它们的修饰下:

类Child继承了父类Parent的@AInherited,不继承@BNotInherited

重写的方法testOverride()不继承Parent的任何注解;

testNotOverride()因为没有被重写,所以注解依然生效。

@Retention(RetentionPolicy.RUNTIME)
@Inherited public @interface AInherited {
  String value();
}
@Retention(RetentionPolicy.RUNTIME)
public @interface BNotInherited {
  String value();
} 

@AInherited("Inherited")
@BNotInherited("没Inherited")
public class Parent { 

  @AInherited("Inherited")
  @BNotInherited("没Inherited")
  public void testOverride(){ 

  }
  @AInherited("Inherited")
  @BNotInherited("没Inherited")
  public void testNotOverride(){
  }
} 

/**
 * Child继承了Parent的AInherited注解
 * BNotInherited因为没有@Inherited声明,不能被继承
 */public class Child extends Parent { 

 /**
  * 重写的testOverride不继承任何注解
  * 因为Inherited不作用在方法上
  */
  @Override
  public void testOverride() {
  } 

 /**
  * testNotOverride没有被重写
  * 所以注解AInherited和BNotInherited依然生效。
  */}

2、自定义注解

2.1 运行时注解

了解了元注解后,看看如何实现和使用自定义注解。这里我们简单介绍下运行时注解RUNTIME,编译时注解CLASS留着后面分析。

首先,创建一个注解遵循: public @interface 注解名 {方法参数},如下方@getViewTo注解:

@Target({ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)public @interface getViewTo {  int value() default -1;
}

然后如下方所示,我们将注解描述在Activity的成员变量mTvmBtn中,在App运行时,通过反射将findViewbyId得到的控件,注入到mTvmBtn中。

是不是很熟悉,有点ButterKnife的味道?当然,ButterKnife比这个高级多,毕竟反射多了影响效率,不过我们明白了,可以通过注解来注入和创建对象,这样可以在一定程度节省代码量。

public class MainActivity extends AppCompatActivity {  @getViewTo(R.id.textview)  private TextView mTv;  @getViewTo(R.id.button)  private Button mBtn;  @Override
  protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);    //通过注解生成View;
    getAllAnnotationView();
  }  /**
   * 解析注解,获取控件
   */
  private void getAllAnnotationView() {    //获得成员变量
    Field[] fields = this.getClass().getDeclaredFields();    for (Field field : fields) {     try {      //判断注解
      if (field.getAnnotations() != null) {       //确定注解类型
       if (field.isAnnotationPresent(GetViewTo.class)) {        //允许修改反射属性
        field.setAccessible(true);
        GetViewTo getViewTo = field.getAnnotation(GetViewTo.class);        //findViewById将注解的id,找到View注入成员变量中
        field.set(this, findViewById(getViewTo.value()));
       }
      }
     } catch (Exception e) {
     }
    }
   }

}

2.2 编译时注解

运行时注解RUNTIME如上2.1所示,大多数时候实在运行时使用反射来实现所需效果,这很大程度上影响效率,如果BufferKnife的每个View注入不可能如何实现。实际上,ButterKnife使用的是编译时注解CLASS,如下图X2.2,是ButterKnife的@BindView注解,它是一个编译时注解,在编译时生成对应java代码,实现注入。

说到编译时注解,就不得不说注解处理器 AbstractProcessor,如果你有注意,一般第三方注解相关的类库,如bufferKnike、ARouter,都有一个Compiler命名的Module,如下图X2.3,这里面一般都是注解处理器,用于编译时处理对应的注解。

注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。你可以对自定义注解,并注册相应的注解处理器,用于处理你的注解逻辑。

如下所示,实现一个自定义注解处理器,至少重写四个方法,并且注册你的自定义Processor,详细可参考下方代码CustomProcessor

@AutoService(Processor.class),谷歌提供的自动注册注解,为你生成注册Processor所需要的格式文件(com.google.auto相关包)。

init(ProcessingEnvironment env),初始化处理器,一般在这里获取我们需要的工具类。

getSupportedAnnotationTypes(),指定注解处理器是注册给哪个注解的,返回指定支持的注解类集合。

getSupportedSourceVersion() ,指定java版本。

process(),处理器实际处理逻辑入口。

@AutoService(Processor.class)public class CustomProcessor extends AbstractProcessor {  /**
   * 注解处理器的初始化
   * 一般在这里获取我们需要的工具类
   * @param processingEnvironment 提供工具类Elements, Types和Filer
   */
  @Override
  public synchronized void init(ProcessingEnvironment env){
    super.init(env);    //Element代表程序的元素,例如包、类、方法。
    mElementUtils = env.getElementUtils();    //处理TypeMirror的工具类,用于取类信息
    mTypeUtils = env.getTypeUtils();     //Filer可以创建文件
    mFiler = env.getFiler();    //错误处理工具
    mMessages = env.getMessager();
  }  /**
   * 处理器实际处理逻辑入口
   * @param set
   * @param roundEnvironment 所有注解的集合
   * @return
   */
  @Override
  public boolean process(Set<? extends TypeElement> annoations,
   RoundEnvironment env) {    //do someThing
  }  //指定注解处理器是注册给哪个注解的,返回指定支持的注解类集合。
  @Override
  public Set<String> getSupportedAnnotationTypes() {
     Set<String> sets = new LinkedHashSet<String>();     //大部分class而已getName、getCanonicalNam这两个方法没有什么不同的。
     //但是对于array或内部类等就不一样了。
     //getName返回的是[[Ljava.lang.String之类的表现形式,
     //getCanonicalName返回的就是跟我们声明类似的形式。
     sets(BindView.class.getCanonicalName());     return sets;
  }  //指定Java版本,一般返回最新版本即可
  @Override
  public SourceVersion getSupportedSourceVersion() {    return SourceVersion.latestSupported();
  }

}

首先,我们梳理下一般处理器处理逻辑:

1、遍历得到源码中,需要解析的元素列表。

2、判断元素是否可见和符合要求。

3、组织数据结构得到输出类参数。

4、输入生成java文件。

5、错误处理。

然后,让我们理解一个概念:Element,因为它是我们获取注解的基础。

Processor处理过程中,会扫描全部Java源码,代码的每一个部分都是一个特定类型的Element,它们像是XML一层的层级机构,比如类、变量、方法等,每个Element代表一个静态的、语言级别的构件,如下方代码所示。

package android.demo; // PackageElement// TypeElementpublic class DemoClass {  // VariableElement
  private boolean mVariableType;  // VariableElement
  private VariableClassE m VariableClassE;  // ExecuteableElement
  public DemoClass () {
  }  // ExecuteableElement
  public void resolveData (Demo data  //TypeElement ) {
  }
}

其中,Element代表的是源代码,而TypeElement代表的是源代码中的类型元素,例如类。然而,TypeElement并不包含类本身的信息。你可以从TypeElement中获取类的名字,但是你获取不到类的信息,例如它的父类。这种信息需要通过TypeMirror获取。你可以通过调用elements.asType()获取元素的TypeMirror

1、知道了Element,我们就可以通过process 中的RoundEnvironment去获取,扫描到的所有元素,如下图X2.4,通过env.getElementsAnnotatedWith,我们可以获取被@BindView注解的元素的列表,其中validateElement校验元素是否可用。

2、因为env.getElementsAnnotatedWith返回的,是所有被注解了@ BindView的元素的列表。所以有时候我们还需要走一些额外的判断,比如,检查这些Element是否是一个类:

@Override
 public boolean process(Set<? extends TypeElement> an, RoundEnvironment env) {  for (Element e : env.getElementsAnnotatedWith(BindView.class)) {   // 检查元素是否是一个类
   if (ae.getKind() != ElementKind.CLASS) {
      ...
   }
  }
  ...
}

3、javapoet (com.squareup:javapoet)是一个根据指定参数,生成java文件的开源库,有兴趣了解javapoet的可以看下javapoet——让你从重复无聊的代码中解放出来,在处理器中,按照参数创建出 JavaFile之后,通Filer利用javaFile.writeTo(filer);就可以生成你需要的java文件。

4、错误处理,在处理器中,我们不能直接抛出一个异常,因为在process()中抛出一个异常,会导致运行注解处理器的JVM崩溃,导致跟踪栈信息十分混乱。因此,注解处理器就有一个Messager类,一般通过messager.printMessage( Diagnostic.Kind.ERROR, StringMessage, element)即可正常输出错误信息。

至此,你的注解处理器完成了所有的逻辑。可以看出,编译时注解实在编译时生成java文件,然后将生产的java文件注入到源码中,在运行时并不会像运行时注解一样,影响效率和资源。

总结

我们就利用ButterKnife的流程,简单举例做个总结吧。

1、@BindView在编译时,根据Acitvity生产了XXXActivity$$ViewBinder.java。

2、Activity中调用的ButterKnife.bind(this);,通过this的类名字,加$$ViewBinder,反射得到了ViewBinder,和编译处理器生产的java文件关联起来了,并将其存在map中缓存,然后调用ViewBinder.bind()

3、在ViewBinder的bind方法中,通过id,利用ButterKnife的butterknife.internal.Utils工具类中的封装方法,将findViewById()控件注入到Activity的参数中。

好了,通过上面的流程,是不是把编译时注解的生成和使用连接起来了呢?

本文只是介绍了Android注解基础内容,解读实例代码的具体作用,更深入的关于Android注解内容可以阅读下面相关文章

(0)

相关推荐

  • Android 中的注解详细介绍

    注解是我们经常接触的技术,Java有注解,Android也有注解,本文将试图介绍Android中的注解,以及ButterKnife和Otto这些基于注解的库的一些工作原理. 归纳而言,Android中的注解大概有以下好处 提高我们的开发效率 更早的发现程序的问题或者错误 更好的增加代码的描述能力 更加利于我们的一些规范约束 提供解决问题的更优解 准备工作 默认情况下,Android中的注解包并没有包括在framework中,它独立成一个单独的包,通常我们需要引入这个包. dependencies

  • Android注解使用之ButterKnife 8.0详解

    前言: App项目开发大部分时候还是以UI页面为主,这时我们需要调用大量的findViewById以及setOnClickListener等代码,控件的少的时候我们还能接受,控件多起来有时候就会有一种想砸键盘的冲动.所以这个时候我们想着可以借助注解的方式让我们从这种繁重的工作中脱离出来,也让代码变得更加简洁,便于维护,今天主要学习一下只专注View.Resource.Action注解框架ButterKnife. ButterKnife介绍 ButterKnife是一个专注于Android系统的V

  • Android注解ButterKnife的基本使用

    ButterKnife的最新版本是8.4.0. 首先,需要导入ButterKnife的jar包. 在AndroidStudio中,File->Project Structure->Dependencies->Library dependency 搜索butterknife即可,第一个就是. 另外一种就是直接在build:grade(app)dependencies里添加: compile 'com.jakewharton:butterknife:8.4.0' annotationProc

  • Android AOP注解Annotation详解(一)

    Android 注解Annotation 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android AOP 注解详解及简单使用实例(三) Android AOP 等在Android上应用越来越广泛,例如框架ButterKnife,Dagger2,EventBus3等等,这里我自己总结了一个学习路程. - Java的注解Annotation - 注解处理解析器APT(Annotation Processing Tool)

  • Android AOP 注解详解及简单使用实例(三)

    Android  注解 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android AOP 注解详解及简单使用实例(三) 一.简介 在Android 里面 注解主要用来干这么几件事: 和编译器一起给你一些提示警告信息. 配合一些ide 可以更加方便快捷 安全有效的编写Java代码.谷歌出的support-annotations这个库 就是主要干这个的. 和反射一起 提供一些类似于spring 可配置的功能,方便简洁. 二

  • Android 反射注解与动态代理综合使用详解

    前言 本章内容主要研究一下java高级特性-反射.android注解.和动态代理的使用,通过了解这些技术,可以为了以后实现组件化或者Api hook相关的做一些技术储备. 反射 主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义. 反射是java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接.但是反射使用不当会成本很高 比较常用的方法 getDeclare

  • 自定义Android注解系列教程之注解变量

    前言 对于Android注解,或多或少都有一点接触,但相信大多数人都是在使用其它依赖库的时候接触的.因为有些库如果你想使用它就必须使用它所提供的注解.例如:ButterKnife.Dagger2.Room等等. 至于为何使用注解?使用过的应该都知道,最明显的就是方便.简洁.通过使用注解可以在项目编译阶段,帮助我们自动生成一些重复的代码,减轻我们的负担.典型的ButterKnife本质就是使用Android注解,通过注解来减少我们对view.findViewById的编写,提高我们的开发效率.上一

  • Android注解框架对比分析

    Java的注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,标记可以加在包,类,属性,方法,本地变量上.然后你可以写一个注解处理器去解析处理这些注解(人称编译时注解),也可以在程序运行时利用反射得到注解做出相应的处理(人称运行时注解). 开发Android程序时,没完没了的findViewById, setOnClickListener等等方法,已经让大多数开发者头疼不已.好在市面上有所谓的注解框架可以帮助开发者简化一些过程.比较流行的有butterknife

  • Android基于注解的6.0权限动态请求框架详解

    前言 安卓6.0之后,一些敏感权限需要进行动态请求,虽说编写请求授权代码并不难,但是每次一需要权限就需要在视图中添加一段代码,严重影响代码美观,同时也增加了一点点工作量. 于是,小盆友闲暇之余基于AOP封装了一个基于注解的权限请求框架.如果有幸加入您的项目,使用过程中有问题或是有哪些不便,请留言区或github上与我交流,共同进步.如果喜欢这个框架请给个star和❤️. github地址:https://github.com/zincPower/JPermission 先上图,看看效果...第一

  • Android中封装SDK时常用的注解总结

    前言 在工作中我们经常需要将功能模块封装成库供合作厂商调用, 如何写好一个健壮的Android Library有很多讲究,使用注解可以对SDK暴露给开发者的接口做出一些限制,从而尽可能地避免开发者错误地使用API. 下面我们介绍几种封装SDK时常用到的注解,需要的朋友们可以参考学习. 一.IntDef与StringDef 我们有时候会使用int常量或者String常量来代替枚举, 特别在你编写SDK的时候,你可以通过IntDef或者StringDef来限制接口可接受的参数. 比如,有一个 dis

  • Android 中的注解深入探究

    本文系GDG Android Meetup分享内容总结文章 注解是我们经常接触的技术,Java有注解,Android也有注解,本文将试图介绍Android中的注解,以及ButterKnife和Otto这些基于注解的库的一些工作原理. 归纳而言,Android中的注解大概有以下好处 提高我们的开发效率 更早的发现程序的问题或者错误 更好的增加代码的描述能力 更加利于我们的一些规范约束 提供解决问题的更优解 准备工作 默认情况下,Android中的注解包并没有包括在framework中,它独立成一个

  • 深入分析安卓(Android)中的注解

    归纳而言,Android中的注解大概有以下好处 1.提高我们的开发效率 2.更早的发现程序的问题或者错误 3.更好的增加代码的描述能力 4.更加利于我们的一些规范约束 5.提供解决问题的更优解 准备工作 默认情况下,Android中的注解包并没有包括在framework中,它独立成一个单独的包,通常我们需要引入这个包. dependencies { compile 'com.android.support:support-annotations:22.2.0' } 但是如果我们已经引入了 app

  • Android AOP之注解处理解释器详解(二)

    Android APO 注解处理解释器 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android AOP 注解详解及简单使用实例(三) 一.提取Annotation信息 当开发者使用了Annotation修饰了类.方法.Field等成员之后,这些Annotation不会自己生效,必须由开发者提供相应的代码来提取并处理Annotation信息.这些处理提取和处理Annotation的代码统称为APT(Annotation

随机推荐