java 注解 android:ButterKnife

一、基本概念

注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。

我们都知道在Java代码中使用注释是为了提升代码的可读性,也就是说,注释是给人看的(对于编译器来说没有意义)。注解可以看做是注释的“强力升级版",它可以向编译器、虚拟机等解释说明一些事情(也就是说它对编译器等工具也是“可读”的)。比如我们非常熟悉的@Override注解,它的作用是告诉编译器它所注解的方法是重写的父类中的方法,这样编译器就会去检查父类是否存在这个方法,以及这个方法的签名与父类是否相同。

也就是说,注解是描述Java代码的代码,它能够被编译器解析,注解处理工具在运行时也能够解析注解。除了向编译器等传递一些信息,我们也可以使用注解生成代码。比如我们可以使用注解来描述我们的意图,然后让注解解析工具来解析注解,以此来生成一些”模板化“的代码。比如Hibernate、Spring等框架大量使用了注解,来避免一些重复的工作。注解是一种”被动“的信息,必须由编译器或虚拟机来“主动”解析它,它才能发挥自己的作用。

使用Annotation之前(甚至在使用之后),XML被广泛的应用于描述元数据。不知何时开始一些应用开发人员和架构师发现XML的维护越来越糟糕了。他们希望使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的(在某些情况下甚至是完全分离的)代码描述。如果你在Google中搜索“XML vs. annotations”,会看到许多关于这个问题的辩论。最有趣的是XML配置其实就是为了分离代码和配置而引入的。上述两种观点可能会让你很疑惑,两者观点似乎构成了一种循环,但各有利弊。下面我们通过一个例子来理解这两者的区别。

假如你想为应用设置很多的常量或参数,这种情况下,XML是一个很好的选择,因为它不会同特定的代码相连。如果你想把某个方法声明为服务,那么使用Annotation会更好一些,因为这种情况下需要注解和方法紧密耦合起来,开发人员也必须认识到这点。

另一个很重要的因素是Annotation定义了一种标准的描述元数据的方式。在这之前,开发人员通常使用他们自己的方式定义元数据。例如,使用标记interfaces,注释,transient关键字等等。每个程序员按照自己的方式定义元数据,而不像Annotation这种标准的方式。

目前,许多框架将XML和Annotation两种方式结合使用,平衡两者之间的利弊。

二、元注解

元注解即用来描述注解的注解。Java5.0定义的元注解:

  • @Target,注解用于什么地方
  • @Retention,什么时候使用该注解
  • @Documented,注解是否将包含在JavaDoc中
  • @Inherited,是否允许子类继承该注解

1.@Target
用于描述注解的使用范围(即:被描述的注解可以用在什么地方)。取值(ElementType)有:
1.CONSTRUCTOR:用于描述构造器 
2.FIELD:用于描述实例变量
3.LOCAL_VARIABLE:用于描述局部变量 
4.METHOD:用于描述方法 
5.PACKAGE:用于记录java文件的package信息
6.PARAMETER:用于描述参数 
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明

2.@Retention
表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)。取值(RetentionPoicy)有:

  • SOURCE:
    在源文件中有效(即源文件保留),在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
  • CLASS:
    在class文件中有效(即class保留),在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式。
  • RUNTIME:
    在运行时有效(即运行时保留),始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

3.@Documented
****@****Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。

**4.@Inherited **
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
  注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
  当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

三、自定义注解

参考
扯扯Java反射与注解
怎样理解 java 注解和运用注解编程
怎样优雅地使用java注解
retrofit 注解学习

要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation对象,因为你除此之外没有别的获取注解对象的方法。
<pre>
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Example{
public static String STRING_DATA = "String";
public String name() default "New Example";
public String description();
public int id() default 0;
}
</pre>

自定义注解方式与JAVA接口的定义非常相似。不同的是,interface关键字前面加上了 “@”来作出区分这是注解而不是接口。并且注解定义中你可以看到在 name()和id()后面都有一个" default“,这表示当你使用该注解时,如果你没有指定这项的值,它将会使用default后面的作为默认值。而注解上方的另外两个注解则指定了注解的有效策略和注解对应的对象类型。

<pre>
//IConfig.java:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IConfig {
public String name();
public String type();
public static String DInteger = "Integer";
public static String DDouble = "Double";
public static String DString = "String";
public static String DLong = "Long";
public static String DChar = "Character";
}

//status.java:
public class Status {
@IConfig(name = "ontime.starttime", type = IConfig.DInteger)
public static int data = 1;
public static final int d = 2;
}

//test:
public static void main(String[] args) throws ClassNotFoundException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, SecurityException, InvocationTargetException{
//加载这个类
Class<?> cls = Class.forName("cn.sunflyer.test.Status");
//这里是获取类中的全部公开的变量 即变量修饰符需要为public
Field fs[] = cls.getFields();
if(fs != null){
//遍历变量数组
for(Field x:fs){
//打印出变量名称和修饰符的值,可以通过Modifier类中的常量对比获取修饰符类型
System.out.println("名称 " + x.getName() + " 修饰符 " + x.getModifiers());
Annotation rms = x.getAnnotation(IConfig.class);
//如果获取到IConfig注解,则输出内容并根据注解动态修改数据
if(rms != null){
IConfig ic = (IConfig)rms;
System.out.println("注解 name : " + ic.name());
System.out.println("注解 type : " + ic.type());
String da = "1234";
Object ras = null;
if(ic.type().equals(IConfig.DString)){
ras = da;
}else{
//加载JAVA基本数据类型的封装对象类
Class<?> fCls = Class.forName("java.lang." + ic.type());
//获取指定方法
Method ms = fCls.getMethod("valueOf", String.class);
//调用方法
ras = ms.invoke(null , da);
}
x.set(null, ras);
}
}
}
}
</pre>

运行后的结果为:
名称 data 修饰符 9
注解 name : ontime.starttime
注解 type : Integer
名称 d 修饰符 25

四、android注解 ButterKnife

ButterKnife框架原理
深入理解 ButterKnife,让你的程序学会写代码
Android注解那些事儿
专门为Android View设计的绑定注解ButterKnife
偷懒插件Android Butterknife Zelezny
使用 @Bind注解并传入一个View ID,Butter Knife 就可以找到并且自动地对你的布局中的View进行转换并绑定到类成员上。

class ExampleActivity extends Activity {
  @Bind(R.id.title) TextView title;
  @Bind(R.id.subtitle) TextView subtitle;
  @Bind(R.id.footer) TextView footer;

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
    // TODO Use fields...
  }
}

请注意,相比与缓慢的反射机制,Butter Knife的代码是生成的,因此不必担心注解的性能问题。可能很多人都觉得ButterKnife在bind(this)方法执行的时候通过反射获取ExampleActivity中所有的带有@Bind注解的属性并且获得注解中的R.id.xxx值,最后还是通过反射拿到Activity.findViewById()方法获取View,并赋值给ExampleActivity中的某个属性.这是一个注解库的实现方式,比较原始,一个很大的缺点就是在Activity运行时大量使用反射会影响App的运行性能,造成卡顿以及生成很多临时Java对象更容易触发GC。ButterKnife显然没有使用这种方式,它用了Java Annotation Processing技术,就是在Java代码编译成Java字节码的时候就已经处理了。

为什么你用@Bind、@OnClick等注解标注的属性或方法必须是public或protected的,因为ButterKnife是通过ExampleActivity.this.editText
来注入View的。

为什么要这样呢?有些注入框架比如roboguice你是可以把View设置成private的,答案就是性能。如果你把View设置成private,那么框架必须通过反射来注入View,不管现在手机的CPU处理器变得多快,如果有些操作会影响性能,那么是肯定要避免的,这就是ButterKnife与其他注入框架的不同。

调用bind来生成这些代码,你可以查看或调试这些代码。
例如上面的例子,生成的代码大致如下所示:

public void bind(ExampleActivity activity) {
  activity.subtitle = (android.widget.TextView) activity.findViewById(2130968578);
  activity.footer = (android.widget.TextView) activity.findViewById(2130968579);
  activity.title = (android.widget.TextView) activity.findViewById(2130968577);
}

绑定资源到类成员上可以使用@BindBool、@BindColor、@BindDimen、@BindDrawable、@BindInt、@BindString。使用时对应的注解需要传入对应的id资源,例如@BindString你需要传入R.string.id_string的字符串的资源id。

class ExampleActivity extends Activity {
  @BindString(R.string.title) String title;
  @BindDrawable(R.drawable.graphic) Drawable graphic;
  @BindColor(R.color.red) int red; // int or ColorStateList field
  @BindDimen(R.dimen.spacer) Float spacer; // int (for pixel size) or float (for exact value) field
  // ...
}

侦听器绑定

@OnClick(R.id.submit)
public void submit() {
  // TODO submit data to server...
}

@OnClick(R.id.submit)
public void sayHi(Button button) {
  button.setText("Hello!");
}

//同时指定多个id的控件到同一个事件监听上:

@OnClick({ R.id.door1, R.id.door2, R.id.door3 })
public void pickDoor(DoorView door) {
  if (door.hasPrizeBehind()) {
    Toast.makeText(this, "You win!", LENGTH_SHORT).show();
  } else {
    Toast.makeText(this, "Try again", LENGTH_SHORT).show();
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容

  • 本文章涉及代码已放到github上annotation-study 1.Annotation为何而来 What:A...
    zlcook阅读 29,081评论 15 116
  • 整体Retrofit内容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李头阅读 6,356评论 4 31
  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和...
    九尾喵的薛定谔阅读 3,123评论 0 2
  • 今天没有午休,大脑犯困,下午的第二节课偶然的闪过了高一跟小坤斗嘴怄气的小时光。 那时的我们是三个人一排靠...
    奚夷阅读 240评论 6 0
  • me:妈妈捏的橡皮泥在哪呢? 星:可能它去旅游了吧,想到外面的世界看看。 me:我刚刚还看到它在这呢,不会去旅游的...
    星辰妈咪阅读 540评论 0 0