注解是个好东西!!!
刚接触的时候感觉很难又很神奇,在日常开发接触到的框架里面Eventbus,Aroute,Retrofit,ButterKnife等等一大堆便捷的工具,都会涉及到注解。用起来相当方便简洁,就能完成很多麻烦复杂且重复的工作,还能配合一些设计模式一起使用(工厂模式就很合适),会有很好的效果,但是自己却又不理解是怎么操作的,就去参考各路资料并进行一些简单小结,实现自定义注解。
注解是什么
注解是在编译和运行期间对代码的进行解析,配置,注释说明,等辅助性作用
笼统来说就是一个修饰符,标记某个地方,能够让系统根据这个注解找到对应的点,获取到一些信息,然后利用这些信息去完成我们的目的(比如检测,赋值什么的)。
注解的基本组成
1. @Retention 标明对应注解的生命周期
@Retention 对应类型值 注解生命周期
RetentionPolicy.SOURCE 编译完成class文件时,注解作用消失
RetentionPolicy.CLASS 编译完成后当没虚拟机加载的时候,注解消失
RetentionPolicy.RUNTIME 一直保存到运行时,注解作用一直保留
2. @Target 标明对应注解的生命周期
@Target 对应类型值 设置注解出现地点
ElementType.TYPE 接口、类、枚举、注解
ElementType.FIELD 字段、枚举的常量
ElementType.METHOD 方法
ElementType.PARAMETER 方法参数
ElementType.CONSTRUCTOR 构造函数
ElementType.LOCAL_VARIABLE 局部变量
ElementType.ANNOTATION_TYPE 注解
ElementType.PACKAGE 包
3. @Inherited 标明注解能否被继承(默认false)
4. @Document 标明该注解能被工具文档化
这几个系统提供注解又称为元注解,用于说明注解的作用,即是注解的注解
自定义注解
自定义注解一般分为两种,一种是编译时的注解,另一种是运行时的注解,两者根据业务逻辑需要进行使用。两种自定义注解实现有区别,先简单说说对应作用
- 自定义运行时注解
运行时的注解作用,代码运行时通过反射机制获取到对应的信息,然后进行一系列的操作,赋值,检测,绑定等等,需要注意设置要是运行时的注解,@Retention(RetentionPolicy.RUNTIME)
写一个类似Butterknife作用的绑定控件注解,先创建对应的注解(无聊顺便写上kottin的),当然Butterknife没那么简单,还有哈希表一类的缓存等等,需要考虑到反射性能问题。
@Target(value = {ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface BindViewJava {
int value() default 0;
int[] onClick() default 0;
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class BindViewKotlin(
val id: Int = 0,
val ids : IntArray = [0]
)
在编写对应注解的处理操作,根据反射获取到@BindViewJava的注解,在获取赋值在注解上的值value,进行控件view绑定,进行一系列操作,可以根据需要进行实现更多的便利操作,减少很多无用代码,本例子只是实现绑定View和View点击事件
public class AnnotationProcessor {
public static void bind(final Activity object){
try {
final Class<? extends Activity> aClass = object.getClass();
Field[] fields = aClass.getFields();//获取字段注解, public fields
Method[] methods = aClass.getMethods();
for (Field field : fields) {
BindViewJava annotation = field.getAnnotation(BindViewJava.class);
if (annotation != null && annotation.value() != 0){
View view = object.findViewById(annotation.value());
field.set(object, view); //将初始化的View返回给对应的class, 设置接收者为Activity
}
}
for (final Method method : methods){
BindViewJava annotation = method.getAnnotation(BindViewJava.class); //查找对应方法注解
if (annotation != null && annotation.onClick() != null && annotation.onClick().length != 0){
final int[] ints = annotation.onClick();
for (int i = 0; i < ints.length; i++) {
object.findViewById(ints[i]).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
method.invoke(object, v); //回调对应点击方法, 设置接收者为Activity
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
});
}
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
相对来说比较简单粗糙,但是也能实现到我需要的控件绑定效果,搞清楚了运行时注解原理,就是运行期间通过反射获取带有对应类的信息,在进行处理。
最后上一下Activity,布局就是简单的TextVIew和两个按钮。
public class mainActivity extends AppCompatActivity {
@BindViewJava(value = R.id.content)
public TextView mTvContent;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AnnotationProcessor.bind(this);
initView();
}
private void initView(){
mTvContent.setText("View绑定");
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@BindViewJava(onClick = {R.id.button_one, R.id.button_two})
public void onClick(View view){
switch (view.getId()){
case R.id.button_one:
Toast.makeText(this, "这是绑定按钮一", Toast.LENGTH_SHORT).show();
break;
case R.id.button_two:
Toast.makeText(this, "这是绑定按钮二", Toast.LENGTH_SHORT).show();
break;
}
}
}
一般对于项目来说如果在每个Activity都进行绑定的话,会相当麻烦毕竟有很多个,所以一般都考虑放在基类Activity里面进行绑定,可是实验了一下,如果直接移植到基类绑定,绑定点击事件会找不到ID资源,报错。
- 自定义编译时注解
编译时的注解作用,编译期间扫描对应的注解,在自定义的注解处理器处理注解,生成对应的java文件进行使用,同样需要设置是编译期的注解@Retention(RetentionPolicy.CLASS),其他元注解根据业务需要编写
同样实现一个例子说明,开始说了可以搭配一些设计模式使用,搭配工厂模式实现实体类的生产,同样的目的也是为了简洁代码。
实现步骤
1. 自定义注解
- 按照上面的方式定义一个自己的注解,周期为编译时期
/**
* 生成类注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
@Inherited
public @interface ClassFactory {
Class<?> className();
Class<?> superClass();
}
2. 自定义注解处理器
@AutoService(ClassFactory.class)
public class AnimalProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(ClassFactory.class)) {
// 检查被注解为@ClassFactory的元素是否是一个类
if (annotatedElement.getKind() != ElementKind.CLASS) {
return true; // 退出处理
}
//对注解的类进行处理
}
return false;
}
}
3. 声明注解,绑定注解
@ClassFactory(className = duck.class, superClass = animal.class)
public class duck extends animal {
@Override
public String type() {
return "鸭";
}
@Override
public String run() {
return "双脚直立行走";
}
}