0%

自定义注解,打造自己的框架(上篇)

该系列介绍自定义注解,完成如下功能。

  • @BindView 代替 findViewById
  • @ClickResponder 代替 setOnClickListener
  • @LongClickResponder 代替 setOnLongClickListener
  • @IntentValue 代替 getIntent().getXXX
  • @UriValue 代替 getQueryParameter
  • @BroadcastResponder 代替 registerReceiver
  • @RouterModule、@RouterPath 来进行反依赖传递调用

使用编译时注解,生成辅助类来完成这些操作,尽量少的使用的反射功能。
该系列源码在https://github.com/huangyuanlove/AndroidAnnotation

本章先介绍一丢丢注解相关的东西,并且实现运行时注解

常见的注解:

@Override:被标记的方法一定是重写的父类方法,反之不一定;

@Deprecated:被标记的方法为过时方法,调用该方法时编辑器会有警告

@SuppressWarnings 指示编译器去忽略注解中声明的警告。

元注解 :作用在其他注解的注解

@Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。值有三种类型(RetentionPolicy.SOURCE;RetentionPolicy.CLASS;RetentionPolicy.RUNTIME)

@Target - 标记这个注解应该是哪种 Java 成员。常见的值ElementType.TYPE(作用于类)、ElementType.FIELD(作用于字段)、ElementType.METHOD(作用于方法)等

更多关于注解的东西自己搜一下,搜不到的话不要往下看了

太长不看系列总结

  1. 声明注解
  2. 解析运行时注解
    2.1 获取类的属性和方法
    2.2 找到添加注解的属性或者方法
    2.3 做自定义注解需要做的事情
  3. 解析编译时注解(需要编写注解处理器)
    3.1 注册注解处理器(@AutoService)
    3.2 拿到注解的属性和方法
    3.3 生成辅助文件的内容(通常会使用javapoet)
    3.4 写入文件
    3.5 编写提供给用户调用的方法

注解的声明

1
2
3
4
5
6
7
8
9
10
11
12
13
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) //该注解可以在运行时通过反射拿到
/**
* 用来代替findViewById
*/
public @interface BindView {
int value() ; //这里可以声明默认值 int value() default -1;
}
1
2
3
4
5
6
7
8
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
/**
* 用来代替setOnClickListener
*/
public @interface OnClick {
int id();
}
1
2
3
4
5
6
7
8
9
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
/**
* 用来代替ssetOnLongClickListener
*/
public @interface OnLongClick {
int id();
boolean result() default true;
}

反射

java中的反射真是个毁誉参半的东西,至于怎么用,自己搜一下吧。使用反射调用非静态方法时,第一个参数是方法所在类的实例;调用静态方法时,第一个参数传null即可。

实现

新建Activity,随便写点布局、控件什么的。

使用方式和Butterknife差不多。

1
2
3
4
5
6
7
8
9
10
11
12
@BindView(value = R.id.test_runtime_annotation)
Button testRuntimeAnnotation;

@OnLongClick(id = R.id.test_runtime_annotation)
void testRuntimeAnnotationOnLongClick(View v) {
Toast.makeText(this, "测试运行时注解:onLongClick", Toast.LENGTH_LONG).show();
}

@OnClick(id = R.id.test_runtime_annotation)
void setTestRuntimeAnnotationOnClick(View v) {
Toast.makeText(this, "测试运行时注解:onClick", Toast.LENGTH_LONG).show();
}

我们需要在onCreate中对注解进行操作,

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
private void initAnnotation() {
//反射获取所有声明的字段
Field fields[] = this.getClass().getDeclaredFields();

for (Field field : fields) {
field.setAccessible(true);
//判断字段是否有BindView注解
BindView bindView = field.getAnnotation(BindView.class);
if (bindView != null) {
try {
//对字段赋值
field.set(this, findViewById(bindView.value()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}

//反射获取所有方法
Method methods[] = this.getClass().getDeclaredMethods();
for (Method method : methods) {
method.setAccessible(true);
//判断方法是否有OnLongClick注解
OnLongClick onLongClick = method.getAnnotation(OnLongClick.class);
if (onLongClick != null) {
//对该方法的注解值对应的控件 设置OnLongClickListener
findViewById(onLongClick.id()).setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
try {
//方法调用
method.invoke(v.getContext(), v);

} catch (Exception e) {
e.printStackTrace();
}
return onLongClick.result();

}
});
}
//判断方法是否有OnClick注解
OnClick onClick = method.getAnnotation(OnClick.class);
if (onClick != null) {
//对该方法的注解值对应的控件 设置OnClickListener
findViewById(onClick.id()).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
//方法调用
method.invoke(v.getContext(), v);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
}

上面就简单的实现了o’nClick,onLongClick,BindView注解,只不过使用的运行时注解,通过反射来对字段和注解进行操作。如果量大的话,反射会消耗性能,我们可以通过注解在编译期间生成辅助类来进行操作,比如 PermissionsDispatcher

下一篇介绍一下这种方式,需要用到 javapoet 这么个东西


以上