MyException - 我的异常网
当前位置:我的异常网» Android » Android引文快速入门和实用解析

Android引文快速入门和实用解析

www.MyException.Cn  网友分享于:2013-11-16  浏览:0次
Android注解快速入门和实用解析

 

本文讲的是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的参数中。

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


阅读原文

文章评论

漫画:程序员的工作
漫画:程序员的工作
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
程序员应该关注的一些事儿
程序员应该关注的一些事儿
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
鲜为人知的编程真相
鲜为人知的编程真相
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
 程序员的样子
程序员的样子
程序员都该阅读的书
程序员都该阅读的书
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
旅行,写作,编程
旅行,写作,编程
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
中美印日四国程序员比较
中美印日四国程序员比较
程序员的鄙视链
程序员的鄙视链
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
10个调试和排错的小建议
10个调试和排错的小建议
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
如何成为一名黑客
如何成为一名黑客
一个程序员的时间管理
一个程序员的时间管理
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
老程序员的下场
老程序员的下场
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
Java程序员必看电影
Java程序员必看电影
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
编程语言是女人
编程语言是女人
那些争议最大的编程观点
那些争议最大的编程观点
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
每天工作4小时的程序员
每天工作4小时的程序员
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
程序员必看的十大电影
程序员必看的十大电影
我的丈夫是个程序员
我的丈夫是个程序员
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
代码女神横空出世
代码女神横空出世
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有