Android注解

Android里面的注解一般有两种,一种运行时注解,另外一种是编译时注解。运行时注解是基于反射原理的,编译时注解这个就多点,常见的是基于apt的代码生成。反射,会在运行时候在对应的位置插入对应的操作;而编译时注解会在编译时候生成对应的代码进行编译。对于运行性能来说,反射更耗性能。本章将说下Android的注解。

概述

从下面这张图可以看到Java注解的结构分支。
Java注解

Java注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。

(1)元注解
元注解是指注解的注解,负责注解其他的注解。有@Retention @Target @Document @Inherited这四种。

(2)@Retention: 定义注解的保留策略
@Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到

(3)@Target:定义注解的作用目标
其定义的源码为:
@Target(ElementType.TYPE) //接口、类、枚举、注解
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///包

(4)@Document:说明该注解将被包含在javadoc中

(5)@Inherited:允许子类可以继承父类中的该注解

运行时注解

举例

参数注解

下面将用注解实现了一个findViewById功能。

(1) 定义注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.basedemo.annotation.diy;
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 ViewInject {

int value();

/* parent view id */
int parentId() default 0;
}
(2) 定义注解处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class ViewUtils {

private ViewUtils() {
}

public static void inject(Activity activity) {
injectObject(activity, new ViewFinder(activity));
}

@SuppressWarnings("ConstantConditions")
private static void injectObject(Object handler, ViewFinder finder) {

Class<?> handlerType = handler.getClass();

// inject view
Field[] fields = handlerType.getDeclaredFields();
if (fields != null && fields.length > 0) {
for (Field field : fields) {
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
try {
View view = finder.findViewById(viewInject.value(), viewInject.parentId());
if (view != null) {
field.setAccessible(true);
field.set(handler, view);
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
}

}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class ViewFinder {


private Activity activity;


public ViewFinder(Activity activity) {
this.activity = activity;
}

public View findViewById(int id) {
return activity.findViewById(id);
}

public View findViewById(int id, int pid) {
View pView = null;
if (pid > 0) {
pView = this.findViewById(pid);
}

View view = null;
if (pView != null) {
view = pView.findViewById(id);
} else {
view = this.findViewById(id);
}
return view;
}


}
(3) 注解的使用

这是activity里面的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

public class DIYAnnotationActivity extends AppCompatActivity {


@ViewInject(R.id.textView)
private TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_annotation);
//调用
ViewUtils.inject(this);

textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
textView.setText("成功了!");
}
});

}


}

布局文件比较简单就不罗列了。这里可以看出,反射用法过程是:定义注解–定义注解处理方法–调用。

动态代理

下面将在原有的基础上添加点击事件注解。

(1)定义注解

定义一个onclick声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.basedemo.annotation.diy;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
int[] value();

int[] parentId() default 0;
}

(2)定义注解处理

然后在ViewUtils里声明多一个方法。这是用了Java的动态代理方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class ViewUtils {
......省略
public static void inject(Activity activity) {
injectObject(activity, new ViewFinder(activity));
injectEvent(activity);
}
......省略
private static void injectEvent(final Activity activity) {
Class<? extends Activity> clazz = activity.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (final Method methodY : methods) {
OnClick click = methodY.getAnnotation(OnClick.class);
if (click != null) {

int[] viewId = click.value();
methodY.setAccessible(true);
Object listener = Proxy.newProxyInstance(View.OnClickListener.class.getClassLoader(),
new Class[]{View.OnClickListener.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return methodY.invoke(activity, args);
}
});

try {
for (int id : viewId) {
View v = activity.findViewById(id);
Method setClickListener = v.getClass().getMethod("setOnClickListener", View.OnClickListener.class);
setClickListener.invoke(v, listener);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
(3)在activity里面调用
1
2
3
4
@OnClick(R.id.tb_2)
public void onClick(View v) {
textView.setText("你按了button2");
}

方法注解

给setcontentview注解

(1)定义注解
1
2
3
4
5
6
7
8
9
10
11
12
package com.example.basedemo.annotation.diy;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
int value();
}
(2)注解处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void injectContentView(Object handler){
Class<?> handlerType = handler.getClass();
// inject ContentView

ContentView contentView = handlerType.getAnnotation(ContentView.class);
if (contentView != null) {
try {
Method setContentViewMethod = handlerType.getMethod("setContentView", int.class);
setContentViewMethod.invoke(handler, contentView.value());
} catch (Throwable e) {
e.printStackTrace();
}
}
}
(3)调用

activity里面调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@ContentView(R.layout.activity_annotation)
public class DIYAnnotationActivity extends AppCompatActivity {


@ViewInject(R.id.textView)
private TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_annotation);
ViewUtils.injectContentView(this);

ViewUtils.inject(this);

textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
textView.setText("成功了!");
}
});

}

@OnClick(R.id.tb_2)
public void onClick(View v) {
textView.setText("你按了button2");
}

}

编译时注解

介绍

APT(annotation processing tool) 是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件,APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件.简化工作量。但是在java8 开始apt就被移除了,使用JSR 269 API(Pluggable Annotation Processing API) 来处理注解,所以要用APT要定义编译的jdk版本为1.7。

实现一个apt工具分两步,一是实现处理器(实现接口AnnotationProcessor),二是实现返回此处理器的工厂类(实现接口AnnotationProcessorFactory)。

APT首先检测源代码文件中哪些annotation存在。然后APT将查找我们编写的annotation processor factories类,并且要求factories类提供处理源文件中所涉及的annotation的annotation processor。接下来,一个合适的annotation processors将被执行,如果在processors生成源代码文件时,该文件中含有annotation,则APT将重复上面的过程直到没有新文件生成。整个过程APT就像一个在编译时处理annotation的javac.

编写一个annotation processors需要使用java lib目录中的tools.jar提供的以下4个包:
apt相关资料

  • com.sun.mirror.apt: 和APT交互的接口;

  • com.sun.mirror.declaration: 用于模式化类成员、类方法、类声明的接口;

  • com.sun.mirror.type: 用于模式化源代码中类型的接口;

  • com.sun.mirror.util: 提供了用于处理类型和声明的一些工具。

  • javax.annotation.processing.SupportedAnnotationTypes:这个注解用来注册注解处理器要处理的注解类型。有效值为完全限定名(就是带所在包名和路径的类全名)-通配符(此次英语原文为Wildcards,就是?这个符号代表的类型。比如说List<? extends String,想要深入了解,可以看一下这里)也可以。

  • javax.annotation.processing.SupportedSourceVersion:这是用来注册注解处理器要处理的源代码版本。

  • javax.annotation.processing.SupportedOptions:这个注解用来注册可能通过命令行传递给处理器的操作选项。

0%