Android注解快速入门和实用解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:
本文讲的是 Android注解快速入门和实用解析, 首先什么是注解?@Override就是注解,它的作用是:
  • 检查是否正确的重写了父类中的方法。
  • 标明代码,这是一个重写的方法。

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

Android注解快速入门和实用解析

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

Android注解快速入门和实用解析

作为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:运行时才生效。

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

Android注解快速入门和实用解析

@Target

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

  • TYPE:类、接口、枚举、注解类型。
  • FIELD:类成员(构造方法、方法、成员变量)。
  • METHOD:方法。
  • PARAMETER:参数。
  • CONSTRUCTOR:构造器。
  • LOCAL_VARIABLE:局部变量。
  • ANNOTATION_TYPE:注解。
  • PACKAGE:包声明。
  • TYPE_PARAMETER:类型参数。
  • TYPE_USE:类型使用声明。

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


 
 
  1. /** 
  2.   * Nullable表明 
  3.   * bind方法的参数target和返回值Data可以为null 
  4.   */ 
  5.  @Nullable  
  6.  public static Data bind(@Nullable Context target) { 
  7.    //do someThing and return 
  8.    return bindXXX(target); 
  9.  } 

@Inherited

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

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

  • 类Child继承了父类Parent的@AInherited,不继承@BNotInherited;
  • 重写的方法testOverride()不继承Parent的任何注解;
  • testNotOverride()因为没有被重写,所以注解依然生效。

 
 
  1. @Retention(RetentionPolicy.RUNTIME)   
  2. @Inherited   
  3. public @interface AInherited {   
  4.     String value();   
  5. }   
  6. @Retention(RetentionPolicy.RUNTIME)   
  7. public @interface BNotInherited {   
  8.     String value();   
  9. }   
  10.  
  11. @AInherited("Inherited")   
  12. @BNotInherited("没Inherited")   
  13. public class Parent {   
  14.  
  15.     @AInherited("Inherited")   
  16.     @BNotInherited("没Inherited")   
  17.     public void testOverride(){   
  18.  
  19.     }   
  20.     @AInherited("Inherited")   
  21.     @BNotInherited("没Inherited")   
  22.     public void testNotOverride(){ 
  23.     } 
  24. }   
  25.  
  26. /** 
  27.   * Child继承了Parent的AInherited注解 
  28.   * BNotInherited因为没有@Inherited声明,不能被继承 
  29.   */ 
  30. public class Child extends Parent {   
  31.  
  32.   /** 
  33.    * 重写的testOverride不继承任何注解 
  34.    * 因为Inherited不作用在方法上 
  35.    */ 
  36.     @Override   
  37.     public void testOverride() {   
  38.     }   
  39.  
  40.   /** 
  41.    * testNotOverride没有被重写 
  42.    * 所以注解AInherited和BNotInherited依然生效。 
  43.    */ 

2、自定义注解

2.1 运行时注解

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

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


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

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

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


 
 
  1. public class MainActivity extends AppCompatActivity { 
  2.  
  3.     @getViewTo(R.id.textview) 
  4.     private TextView mTv; 
  5.  
  6.     @getViewTo(R.id.button) 
  7.     private Button mBtn; 
  8.  
  9.     @Override 
  10.     protected void onCreate(Bundle savedInstanceState) { 
  11.         super.onCreate(savedInstanceState); 
  12.         setContentView(R.layout.activity_main); 
  13.  
  14.         //通过注解生成View; 
  15.         getAllAnnotationView(); 
  16.     } 
  17.  
  18.     /** 
  19.      * 解析注解,获取控件 
  20.      */ 
  21.     private void getAllAnnotationView() { 
  22.         //获得成员变量 
  23.         Field[] fields = this.getClass().getDeclaredFields(); 
  24.  
  25.         for (Field field : fields) { 
  26.           try { 
  27.             //判断注解 
  28.             if (field.getAnnotations() != null) { 
  29.               //确定注解类型 
  30.               if (field.isAnnotationPresent(GetViewTo.class)) { 
  31.                 //允许修改反射属性 
  32.                 field.setAccessible(true); 
  33.                 GetViewTo getViewTo = field.getAnnotation(GetViewTo.class); 
  34.                 //findViewById将注解的id,找到View注入成员变量中 
  35.                 field.set(this, findViewById(getViewTo.value())); 
  36.               } 
  37.             } 
  38.           } catch (Exception e) { 
  39.           } 
  40.         } 
  41.       } 
  42.  

2.2 编译时注解

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

Android注解快速入门和实用解析

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

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

Android注解快速入门和实用解析

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

  • @AutoService(Processor.class),谷歌提供的自动注册注解,为你生成注册Processor所需要的格式文件(com.google.auto相关包)。
  • init(ProcessingEnvironment env),初始化处理器,一般在这里获取我们需要的工具类。
  • getSupportedAnnotationTypes(),指定注解处理器是注册给哪个注解的,返回指定支持的注解类集合。
  • getSupportedSourceVersion() ,指定java版本。
  • process(),处理器实际处理逻辑入口。

 
 
  1. @AutoService(Processor.class) 
  2. public class CustomProcessor extends AbstractProcessor { 
  3.  
  4.     /** 
  5.      * 注解处理器的初始化 
  6.      * 一般在这里获取我们需要的工具类 
  7.      * @param processingEnvironment 提供工具类Elements, Types和Filer 
  8.      */ 
  9.     @Override 
  10.     public synchronized void init(ProcessingEnvironment env){  
  11.         super.init(env); 
  12.         //Element代表程序的元素,例如包、类、方法。 
  13.         mElementUtils = env.getElementUtils(); 
  14.  
  15.         //处理TypeMirror的工具类,用于取类信息 
  16.         mTypeUtils = env.getTypeUtils(); 
  17.  
  18.          //Filer可以创建文件 
  19.         mFiler = env.getFiler(); 
  20.  
  21.         //错误处理工具 
  22.         mMessages = env.getMessager(); 
  23.     } 
  24.  
  25.     /** 
  26.      * 处理器实际处理逻辑入口 
  27.      * @param set 
  28.      * @param roundEnvironment 所有注解的集合 
  29.      * @return  
  30.      */ 
  31.     @Override 
  32.     public boolean process(Set<? extends TypeElement> annoations,  
  33.       RoundEnvironment env) { 
  34.         //do someThing 
  35.     } 
  36.  
  37.     //指定注解处理器是注册给哪个注解的,返回指定支持的注解类集合。 
  38.     @Override 
  39.     public Set<String> getSupportedAnnotationTypes() {  
  40.           Set<String> sets = new LinkedHashSet<String>(); 
  41.  
  42.           //大部分class而已getName、getCanonicalNam这两个方法没有什么不同的。 
  43.           //但是对于array或内部类等就不一样了。 
  44.           //getName返回的是[[Ljava.lang.String之类的表现形式, 
  45.           //getCanonicalName返回的就是跟我们声明类似的形式。 
  46.           sets(BindView.class.getCanonicalName()); 
  47.  
  48.           return sets; 
  49.     } 
  50.  
  51.     //指定Java版本,一般返回最新版本即可 
  52.     @Override 
  53.     public SourceVersion getSupportedSourceVersion() { 
  54.         return SourceVersion.latestSupported(); 
  55.     } 
  56.  

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

  1. 遍历得到源码中,需要解析的元素列表。
  2. 判断元素是否可见和符合要求。
  3. 组织数据结构得到输出类参数。
  4. 输入生成java文件。
  5. 错误处理。

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

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


 
 
  1. package android.demo; // PackageElement  
  2. // TypeElement 
  3. public class DemoClass {  
  4.     // VariableElement 
  5.     private boolean mVariableType;  
  6.     // VariableElement 
  7.     private VariableClassE m VariableClassE;  
  8.     // ExecuteableElement 
  9.     public DemoClass () { 
  10.     }  
  11.     // ExecuteableElement 
  12.     public void resolveData (Demo data   //TypeElement ) { 
  13.     } 

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

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

Android注解快速入门和实用解析

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


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

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的参数中。

好了,通过上面的流程,是不是把编译时注解的生成和使用连接起来了呢?有问题还请各位留言谈论。


本文作者: 佚名

来源:51CTO

原文标题:Android注解快速入门和实用解析
相关文章
|
2月前
|
Java 开发工具 Android开发
Android与iOS开发环境搭建全解析####
本文深入探讨了Android与iOS两大移动操作系统的开发环境搭建流程,旨在为初学者及有一定基础的开发者提供详尽指南。我们将从开发工具的选择、环境配置到第一个简单应用的创建,一步步引导读者步入移动应用开发的殿堂。无论你是Android Studio的新手还是Xcode的探索者,本文都将为你扫清开发道路上的障碍,助你快速上手并享受跨平台移动开发的乐趣。 ####
|
1月前
|
存储 Linux API
深入探索Android系统架构:从内核到应用层的全面解析
本文旨在为读者提供一份详尽的Android系统架构分析,从底层的Linux内核到顶层的应用程序框架。我们将探讨Android系统的模块化设计、各层之间的交互机制以及它们如何共同协作以支持丰富多样的应用生态。通过本篇文章,开发者和爱好者可以更深入理解Android平台的工作原理,从而优化开发流程和提升应用性能。
|
1月前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
2月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
165 2
|
2月前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
68 2
|
2月前
|
前端开发 Java 开发者
Spring MVC中的控制器:@Controller注解全解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序控制层的核心。它不仅简化了控制器的定义,还提供了灵活的请求映射和处理机制。本文将深入探讨`@Controller`注解的用法、特点以及在实际开发中的应用。
128 0
|
2月前
|
开发框架 Dart Android开发
安卓与iOS的跨平台开发:Flutter框架深度解析
在移动应用开发的海洋中,Flutter作为一艘灵活的帆船,正引领着开发者们驶向跨平台开发的新纪元。本文将揭开Flutter神秘的面纱,从其架构到核心特性,再到实际应用案例,我们将一同探索这个由谷歌打造的开源UI工具包如何让安卓与iOS应用开发变得更加高效而统一。你将看到,借助Flutter,打造精美、高性能的应用不再是难题,而是变成了一场创造性的旅程。
|
2月前
|
安全 Java Linux
深入解析Android系统架构及其对开发者的意义####
【10月更文挑战第21天】 本文旨在为读者揭开Android操作系统架构的神秘面纱,探讨其如何塑造现代移动应用开发格局。通过剖析Linux内核、硬件抽象层、运行时环境及应用程序框架等关键组件,揭示Android平台的强大功能与灵活性。文章强调了理解Android架构对于开发者优化应用性能、提升用户体验的重要性,并展望了未来技术趋势下Android的发展方向。 ####
59 0
|
2月前
|
安全 5G Android开发
安卓与iOS的较量:技术深度解析
【10月更文挑战第24天】 在移动操作系统领域,安卓和iOS无疑是两大巨头。本文将深入探讨这两个系统的技术特点、优势和不足,以及它们在未来可能的发展方向。我们将通过对比分析,帮助读者更好地理解这两个系统的本质和内涵,从而引发对移动操作系统未来发展的深思。
61 0
|
3月前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
57 0

推荐镜像

更多