前言

Annotation——注解,JDK1.5新增加的功能。它能够添加到 Java 源代码的语法元数据。类、方法、变量、参数、包都可以被注解,可用来将信息元数据与程序元素进行关联。目前很多开源库都使用到了注解,最熟悉的ButtonKnife中的@ViewInject(R.id.x)就可以替代findViewId,不懂这一块技术的同学第一眼看上去肯定会一脸懵逼,下面会手把手带大家写出ButtonKnife的注解使用。使用注解可以简化代码,提高开发效率。本文简单介绍下注解的使用,并对几个 Android 开源库的注解使用原理进行简析。

1、作用

  • 标记,用于告诉编译器一些信息 ;
  • 编译时动态处理,如动态生成代码 ;
  • 运行时动态处理,如得到注解信息。

2、分类

  • 标准 Annotation, 包括 Override, Deprecated, SuppressWarnings。也都是Java自带的几个 Annotation,上面三个分别表示重写函数,不鼓励使用(有更好方式、使用有风险或已不在维护),忽略某项 Warning;
  • 元 Annotation ,@Retention, @Target, @Inherited, @Documented。元 Annotation 是指用来定义 Annotation 的 Annotation,在后面 Annotation 自定义部分会详细介绍含义;
  • 自定义 Annotation , 表示自己根据需要定义的 Annotation,定义时需要用到上面的元 Annotation 这里只是一种分类而已,也可以根据作用域分为源码时、编译时、运行时 Annotation。通过 @interface 定义,注解名即为自定义注解名。

一、自定义注解

例如,注解@MethodInfo:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface MethodInfo {String author() default "annotation@gmail.com";String date();int version() default 1;
}

使用到了元Annotation:

  • @Documented 是否会保存到 Javadoc 文档中 ;
  • @Retention 保留时间,可选值 SOURCE(源码时),CLASS(编译时),RUNTIME(运行时),默认为 CLASS,值为 SOURCE 大都为 Mark Annotation,这类 Annotation 大都用来校验,比如 Override, Deprecated, SuppressWarnings ;
  • @Target 用来指定修饰的元素,如 CONSTRUCTOR:用于描述构造器、FIELD:用于描述域、LOCAL_VARIABLE:用于描述局部变量、METHOD:用于描述方法、PACKAGE:用于描述包、PARAMETER:用于描述参数、TYPE:用于描述类、接口(包括注解类型) 或enum声明。
  • @Inherited 是否可以被继承,默认为 false。

注解的参数名为注解类的方法名,且:

  • 所有方法没有方法体,没有参数没有修饰符,实际只允许 public & abstract 修饰符,默认为 public ,不允许抛异常;
  • 方法返回值只能是基本类型,String, Class, annotation, enumeration 或者是他们的一维数组;
  • 若只有一个默认属性,可直接用 value() 函数。一个属性都没有表示该 Annotation 为 Mark Annotation。
public class App {@MethodInfo(author = “annotation.cn+android@gmail.com”,date = "2011/01/11",version = 2)public String getAppName() {return "appname";}
}

调用自定义MethodInfo 的示例,这里注解的作用实际是给方法添加相关信息: author、date、version  。

二、实战注解Butter Knife

首先,先定义一个ViewInject注解。

public @interface ViewInject {  int value() default  -1;
}

紧接着,为刚自定义注解添加元注解。

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

再定义一个注解LayoutInject

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

定义一个基础的Activity。

package cn.wsy.myretrofit.annotation;import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;import java.lang.reflect.Field;public class InjectActivity extends AppCompatActivity {private int mLayoutId = -1;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);displayInjectLayout();displayInjectView();}/*** 解析注解view id*/private void displayInjectView() {if (mLayoutId <=0){return ;}Class<?> clazz = this.getClass();Field[] fields = clazz.getDeclaredFields();//获得声明的成员变量for (Field field : fields) {//判断是否有注解try {if (field.getAnnotations() != null) {if (field.isAnnotationPresent(ViewInject.class)) {//如果属于这个注解//为这个控件设置属性field.setAccessible(true);//允许修改反射属性ViewInject inject = field.getAnnotation(ViewInject.class);field.set(this, this.findViewById(inject.value()));}}} catch (Exception e) {Log.e("wusy", "not found view id!");}}}/*** 注解布局Layout id*/private void displayInjectLayout() {Class<?> clazz = this.getClass();if (clazz.getAnnotations() != null){if (clazz.isAnnotationPresent(LayouyInject.class)){LayouyInject inject = clazz.getAnnotation(LayouyInject.class);mLayoutId = inject.value();setContentView(mLayoutId);}}}
}

首先,这里是根据映射实现设置控件的注解,java中使用反射的机制效率性能并不高。这里只是举例子实现注解。ButterKnife官方申明不是通过反射机制,因此效率会高点。

package cn.wsy.myretrofit;
import android.os.Bundle;
import android.widget.TextView;
import cn.wsy.myretrofit.annotation.InjectActivity;
import cn.wsy.myretrofit.annotation.LayouyInject;
import cn.wsy.myretrofit.annotation.ViewInject;@LayoutInject(R.layout.activity_main)
public class MainActivity extends InjectActivity {@ViewInject(R.id.textview)private TextView textView;@ViewInject(R.id.textview1)private TextView textview1;@ViewInject(R.id.textview2)private TextView textview2;@ViewInject(R.id.textview3)private TextView textview3;@ViewInject(R.id.textview4)private TextView textview4;@ViewInject(R.id.textview5)private TextView textview5;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//设置属性textView.setText("OK");textview1.setText("OK1");textview2.setText("OK2");textview3.setText("OK3");textview4.setText("OK4");textview5.setText("OK5");}
}

上面直接继承InjectActivity即可,文章上面也有说过:LayouyInject为什么作用域是TYPE,首先在加载view的时候,肯定是优先加载布局啊,**ButterKnife**也不例外。因此选择作用域在描述类,并且存在运行时。

二、解析Annotation原理

1、运行时 Annotation 解析

(1) 运行时 Annotation 指 @Retention 为 RUNTIME 的 Annotation,可手动调用下面常用 API 解析

method.getAnnotation(AnnotationName.class);
method.getAnnotations();
method.isAnnotationPresent(AnnotationName.class);

其他 @Target 如 Field,Class 方法类似 。

  • getAnnotation(AnnotationName.class) 表示得到该 Target 某个 Annotation 的信息,一个 Target 可以被多个 Annotation 修饰;
  • getAnnotations() 则表示得到该 Target 所有 Annotation ;
  • isAnnotationPresent(AnnotationName.class) 表示该 Target 是否被某个 Annotation 修饰;

(2) 解析示例如下:

public static void main(String[] args) {try {Class cls = Class.forName("cn.trinea.java.test.annotation.App");for (Method method : cls.getMethods()) {MethodInfo methodInfo = method.getAnnotation(
MethodInfo.class);if (methodInfo != null) {System.out.println("method name:" + method.getName());System.out.println("method author:" + methodInfo.author());System.out.println("method version:" + methodInfo.version());System.out.println("method date:" + methodInfo.date());}}} catch (ClassNotFoundException e) {e.printStackTrace();}
}

以之前自定义的 MethodInfo 为例,利用 Target(这里是 Method)getAnnotation 函数得到 Annotation 信息,然后就可以调用 Annotation 的方法得到响应属性值  。

2、编译时 Annotation 解析

(1) 编译时 Annotation 指 @Retention 为 CLASS 的 Annotation,甴 apt(Annotation Processing Tool) 解析自动解析。

使用方法:

  • 自定义类集成自 AbstractProcessor;
  • 重写其中的 process 函数 这块很多同学不理解,实际是 apt(Annotation Processing Tool) 在编译时自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法去处理。

(2) 假设之前自定义的 MethodInfo 的 @Retention 为 CLASS,解析示例如下:

@SupportedAnnotationTypes({ "cn.trinea.java.test.annotation.MethodInfo" })
public class MethodInfoProcessor extends AbstractProcessor {@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {HashMap<String, String> map = new HashMap<String, String>();for (TypeElement te : annotations) {for (Element element : env.getElementsAnnotatedWith(te)) {MethodInfo methodInfo = element.getAnnotation(MethodInfo.class);map.put(element.getEnclosingElement().toString(), methodInfo.author());}}return false;}
}

SupportedAnnotationTypes 表示这个 Processor 要处理的 Annotation 名字。
process 函数中参数 annotations 表示待处理的 Annotations,参数 env 表示当前或是之前的运行环境
process 函数返回值表示这组 annotations 是否被这个 Processor 接受,如果接受后续子的 rocessor 不会再对这个 Annotations 进行处理

三、几个 Android 开源库 Annotation 原理简析

1、Retrofit

(1) 调用

@GET("/users/{username}")
User getUser(@Path("username") String username);

(2) 定义

@Documented
@Target(METHOD)
@Retention(RUNTIME)
@RestMethod("GET")
public @interface GET {String value();
}

从定义可看出 Retrofit 的 Get Annotation 是运行时 Annotation,并且只能用于修饰 Method

(3) 原理

private void parseMethodAnnotations() {for (Annotation methodAnnotation : method.getAnnotations()) {Class<? extends Annotation> annotationType = methodAnnotation.annotationType();RestMethod methodInfo = null;for (Annotation innerAnnotation : annotationType.getAnnotations()) {if (RestMethod.class == innerAnnotation.annotationType()) {methodInfo = (RestMethod) innerAnnotation;break;}}……}
}

RestMethodInfo.java 的 parseMethodAnnotations 方法如上,会检查每个方法的每个 Annotation, 看是否被 RestMethod 这个 Annotation 修饰的 Annotation 修饰,这个有点绕,就是是否被 GET、DELETE、POST、PUT、HEAD、PATCH 这些 Annotation 修饰,然后得到 Annotation 信息,在对接口进行动态代理时会掉用到这些 Annotation 信息从而完成调用。 因为 Retrofit 原理设计到动态代理,这里只介绍 Annotation。

2、Butter Knife

(1) 调用

@InjectView(R.id.user)
EditText username;

(2) 定义

@Retention(CLASS)
@Target(FIELD)
public @interface InjectView {int value();
}

可看出 Butter Knife 的 InjectView Annotation 是编译时 Annotation,并且只能用于修饰属性

(3) 原理

@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {Map<TypeElement, ViewInjector> targetClassMap = findAndParseTargets(env);for (Map.Entry<TypeElement, ViewInjector> entry : targetClassMap.entrySet()) {TypeElement typeElement = entry.getKey();ViewInjector viewInjector = entry.getValue();try {JavaFileObject jfo = filer.createSourceFile(viewInjector.getFqcn(), typeElement);Writer writer = jfo.openWriter();writer.write(viewInjector.brewJava());writer.flush();writer.close();} catch (IOException e) {error(typeElement, "Unable to write injector for type %s: %s", typeElement, e.getMessage());}}return true;
}

ButterKnifeProcessor.java 的 process 方法如上,编译时,在此方法中过滤 InjectView 这个 Annotation 到 targetClassMap 后,会根据 targetClassMap 中元素生成不同的 class 文件到最终的 APK 中,然后在运行时调用 ButterKnife.inject(x) 函数时会到之前编译时生成的类中去找。

3、ActiveAndroid

(1) 调用


@Column(name = “Name")
public String name;

(2) 定义

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {……
}

可看出 ActiveAndroid 的 Column Annotation 是运行时 Annotation,并且只能用于修饰属性
(3) 原理

Field idField = getIdField(type);
mColumnNames.put(idField, mIdName);List<Field> fields = new LinkedList<Field>(ReflectionUtils.getDeclaredColumnFields(type));
Collections.reverse(fields);for (Field field : fields) {if (field.isAnnotationPresent(Column.class)) {final Column columnAnnotation = field.getAnnotation(Column.class);String columnName = columnAnnotation.name();if (TextUtils.isEmpty(columnName)) {columnName = field.getName();}mColumnNames.put(field, columnName);}
}

TableInfo.java 的构造函数如上,运行时,得到所有行信息并存储起来用来构件表信息。

————————————————————————

最后一个问题,看看这段代码最后运行结果:


public class Person {private int    id;private String name;public Person(int id, String name) {this.id = id;this.name = name;}public boolean equals(Person person) {return person.id == id;}public int hashCode() {return id;}public static void main(String[] args) {Set<Person> set = new HashSet<Person>();for (int i = 0; i < 10; i++) {set.add(new Person(i, "Jim"));}System.out.println(set.size());}
}

答案:示例代码运行结果应该是 10 而不是 1,这个示例代码程序实际想说明的是标记型注解 Override 的作用,为 equals 方法加上 Override 注解就知道 equals 方法的重载是错误的,参数不对。

Android 注解Annotation及在流行框架中使用的原理相关推荐

  1. php 接收curl json数据格式,curl发送 JSON格式POST数据的接收,以及在yii2框架中的实现原理【精细剖析】...

    1.通过curl发送json格式的数据,譬如代码: function http_post_json($url, $jsonStr) { $ch = curl_init(); curl_setopt($ ...

  2. Android之记录并研究Volley框架中知识点

    转载请标明出处: http://blog.csdn.net/hai_qing_xu_kong/article/details/50916258 本文出自:[顾林海的博客] ##前言 在上一遍<对 ...

  3. Android OkHttp + Glide + RecyclerView + ButterKnife流行框架的综合实现

    文章目录 要求 运行效果 任务描述 框架地址 分析 代码实现 Movie INetCallBack MovieOkHttpUtils MovieRecyclerViewAdapter MovieBiz ...

  4. php框架中数据库模型层原理,简单模拟ThinkPHP框架模型层对数据库的链式操作-Go语言中文社区...

    在接口被实例化以后的每个方法中return 类本身就可以达到链式操作. 改善了写多行代码执行一次操作的缺点. 下面请看我的代码,若有疑问,请留言给我. class Instance{ public s ...

  5. SpringBoot整合流行框架(上) 究其原理 去其糟粕 如何快速上手所有流行框架整合的方法一文足以~

  6. android注解的作用,Android 用注解来提升代码质量

    Android 用注解来提升代码质量 Android,注解,annotation 2018.07.13 Android 提供了一个注解的 support 包,这个注解包配合 IDE 可以用来提升我的代 ...

  7. 开发自己的山寨Android注解框架

    目录 开发自己的山寨Android注解框架 开发自己的山寨Android注解框架 参考 Github黄油刀 Overview 在上一章我们学习了Java的注解(Annotation),但是我想大家可能 ...

  8. Android 自定义注解(Annotation)

    现在市面上很多框架都有使用到注解,比如butterknife库.EventBus库.Retrofit库等等.也是一直好奇他们都是怎么做到的,注解的工作原理是啥.咱们能不能自己去实现一个简单的注解呢.注 ...

  9. Java中的注解(Annotation)处理器解析

    Java中的注解(Annotation)是一个很神奇的东西,特别现在有很多Android库都是使用注解的方式来实现的.一直想详细了解一下其中的原理.很有幸阅读到一篇详细解释编写注解处理器的文章.本文的 ...

最新文章

  1. DNS tunnel的原理及实战
  2. C++ QT中namespace使用?
  3. 为什么说多道程序概念得到了中断和通道技术的支持?
  4. 硬核 | 关于Linux内核的简明知识
  5. vml的简易画板_2
  6. python怎样清除csv中的数据_使用d清除CSV文件中的数据
  7. C语言开定时器做呼吸灯程序,单片机制作呼吸灯的C语言程序怎么样编写
  8. eclipse编译Duet固件的完整过程
  9. Ubuntu 18.04.3 LTS - 安装 Spring Tool Suite 4
  10. WordPress – wp-rocket插件的简单设置以及如何加速网站
  11. 程序员开发了自己的产品怎样推广?说一说我的免费在线客服系统推广经验
  12. 【2021春招】2021年阿里笔试真题3.6/3.8/3.10/3.12记录
  13. Cadence Allegro 17.4学习记录开始06-PCB Editor 17.4快捷键的说明和中英文的切换和操作界面放大缩小设置
  14. 微信记录怎么恢复?恢复已删除微信历史记录的4种方式
  15. Mysql数据库数据拆分之分库分表总结
  16. 完全背包问题完全背包求具体方案
  17. [论文笔记|VIO]ICE-BA: Incremental, Consistent and Efficient Bundle Adjustment for Visual-Inertial SLAM
  18. 人机大战,历史的见证
  19. cocos creator粒子不变色_Cocos Creator 3D 粒子系统初战: 不要钱的酷炫火焰拿走不谢!...
  20. Tank Game V0.2

热门文章

  1. Java编写的画图板,功能非常齐全,完整代码 附详细设计报告
  2. 有N个台阶,一步可以走一梯或者两梯,请问有多少种走法
  3. 共享单车蓝牙锁方案phy6222系列蓝牙芯片
  4. Python(x,y)
  5. 【STK初探】创建一条奔月轨道
  6. Linux监控操作系统CPU、内存、磁盘、网络和dstat
  7. mapper同时添加数据只能添加一条_Mybatis第二章——多表同时插入和级联查询
  8. Python绘制世界疫情地图
  9. 线。段。树--树状数组-主席树
  10. 实验6 8255并行接口实验【微机原理】【实验】