首先带入问题。
1.什么是注解。
2.注解有什么用,我们为什么要用注解?
3.注解的生命周期,编译时注解和运行时注解区别。
引用别人对注解的解释,注解可以理解成标签。
在代码中我们最常见的注解应该是他 @Override ,用于重载父类的方法。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
点击 @Override 进到源码,中我们会发现下面这种结构
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
那么,参考源码的实现方式我们这样就可以生成自己的注解
public @interface TestAnnotation {
}
在我们代码中进行调用
@TestAnnotation
private void logOut() {
Log.i(TAG, "logOut: ");
}
只不过这种注解实现,除了能增加代码量,其他毫无意义 [手动狗头]。
上面@Override 注解中出现的 @Target 和 @Retention
这两个东西,一看感觉就是注解的参数,点进源码会发现官方给他们起了一个专门的名字,meta-annotation(元注解),其只能,表明声明的类型,仅用作复杂注释类型声明中的成员类型。它不能用于直接注释任何内容。(意思对注解进行注解)
/**
* <p>This {@code @Target} meta-annotation indicates that the declared type is
* intended solely for use as a member type in complex annotation type
* declarations. It cannot be used to annotate anything directly:
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
如果我们在其他的地方调用,as会弹出类似的报错。
元注解是注解中的一种,他通过 @Target(ElementType.ANNOTATION_TYPE) 指定其使用场景,只能用在注解上,即对注解进行注解
Target 支持的类型
ElementType | 类型范围 |
---|---|
TYPE | 类、接口(包括注释类型)或枚举声明 |
FIELD | 字段声明(包括枚举常量 |
METHOD | 方法 |
PARAMETER | 参数 |
CONSTRUCTOR | 构造方法 |
LOCAL_VARIABLE | 局部变量 |
ANNOTATION_TYPE | 注解 |
PACKAGE | 包 |
TYPE_PARAMETER | 类型参数(泛型) |
TYPE_USE | 使用类型注解 |
@Retention 指定注解的生命周期
SOURCE | CLASS | RUNTIME | |
---|---|---|---|
生命周期 | 源码阶段 | Class文件阶段 | 运行 |
解释 | .java文件(仅在我们开发过程中存在, 提示错误或警告) |
编译后从java变成.class文件 保存在字节码 |
ClassLoder 加载class字节码到内存 |
那么对第一个问题的总结 什么是注解,在代码层面上,只要使用@interface 的都是注解。如果我们指定他的Target在annotation上。@Target(ElementType.ANNOTATION_TYPE) 那么也可以称他为元注解。
2 注解的作用
- @Retention(RetentionPolicy.SOURCE)
源代码时期的注解,仅存在于.java文件中
1.用于代码检查例如@NonNull /@Nullable ,资源引用限制例如@DrawableRes,@StringRes,提醒错误,过时例如@Deprecated。
2.在编译期间处理,.java文件编译生成class文件时期,通过开发者注册的注解处理器(AnnotationProcessor)对注解进行处理,使用JavaPoet生成模板代码,提高开发效率。 - @Retention(RetentionPolicy.RUNTIME)
1.注解在class字节码文件中存在,通过反射获取到注解的对象以及参数等信息提供调用。容易实现但反射消耗性能不建议过度使用。
举个栗子
完善一下我们之前的TestAnnotation 添加两个参数,name 和 age
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
String name();
int age();
}
在创建一个UserBean类
public class UserBean {
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private String getName() {
return name;
}
@FunctionAnnotation
private void setName(@FunctionType String name) {
this.name = name;
}
@Override
public String toString() {
return "User{"
+ "name='" + name + '\''
+ ", age=" + age
+ '}';
}
}
创建用于绑定的Utils
public class AnnotationUtils {
public static void Inject(Activity activity) {
//万物皆对象。获取activity的class
Class<? extends Activity> c = activity.getClass();
// 获取所有字段
Field[] fields = c.getDeclaredFields();
//遍历拿到带有 @TestAnnotation 注解的字段
for (Field field : fields) {
if (field.isAnnotationPresent(TestAnnotation.class)) {
TestAnnotation annotation = field.getAnnotation(TestAnnotation.class);
if (annotation == null) {
break;
}
//TestAnnotation name
String name = annotation.name();
int old = annotation.age();
//类型的包名路径 例如:com.example.demo.data.UserBean
String packName = field.getType().getName();
//手动定义的对象名称,此例子使用的是mUser
String fieldName = field.getName();
try {
//创建一个user 这里使用包名路径
Class<?> fieldClass = Class.forName(packName);
UserBean user = (UserBean) fieldClass.newInstance();
//返回此Class对象对应类的、带指定形参列表的方法
Method declaredMethod = fieldClass.getDeclaredMethod("setName", String.class);
//设置可以访问私有权限
declaredMethod.setAccessible(true);
//调用方法
declaredMethod.invoke(user, name);
Method declaredMethodSetOld = fieldClass.getDeclaredMethod("setAge", int.class);
declaredMethodSetOld.setAccessible(true);
declaredMethodSetOld.invoke(user, old);
//设置可以访问私有权限
field.setAccessible(true);
//set对象
field.set(activity,user);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
}
}
MainActivity中使用AnnotationUtils注入UserBean 对象
public class MainActivity extends Activity {
@TestAnnotation(name = "xiao ming", old = 18)
private UserBean mUser;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AnnotationUtils.Inject(this);
System.out.println(" main activity user is " + mUser);
}
}
运行程序我们会得到如下的日志打印
I/System.out: main activity user is User{name='xiao ming', age=18}
注解的作用,小结
emmm,搞了一圈你就给我看这个?我直接new一个对象set进属性不就完了吗。搞这么一圈四不四有毛病。
对于我们自己写代码的话确实如此。而且代码业务越简单/单一,越不需要反射和注解。如果只要实现一句hello world,那搞别的操作真是画蛇添足。
我理解是这样的
反射 的场景在于,我们需要使用别人的代码或者android源码(依赖库)。且无法直接修改,或者调用,别人写的代码的情况。迫不得已 我们可以尝试使用反射去操作修改别人的对象,或者调用方法。
注解嘛,就厉害了。我们需要给别人提供服务,或者依赖库的时候。使用者通过使用,我们开发者定义的注解,按照我们定义的规则去标注使用者的代码,从而为开发者实现某些功能。方便使用者使用,和理解。
例如:retrofit ,我们就是使用者,按照retrofit开发者定义好的各种注解,例如@GET,@POST,按照retrofit开发者定义的规则去标注我们的接口方法。开发者通过读取我们的注解,帮我们实现各种逻辑。其他使用注解的框架同理,都是为了使用者便方便调用。
那么如果我们,需要给别人提供服务,或者我们要写依赖库的时候,就可以考虑是否用注解去实现了。如果上面说的,理解了。那应该也会理解,为什么上面举的栗子,明明只用反射也可以实习给user 设置 name 和 age,却偏偏要用注解了吧。(先后关系,先有我们定义好注解的TestAnnotation注解的规则,后面才有开发者使用TestAnnotation 注解去标注给User使用)。
注解的优势
- 上面的栗子运行时期注解,需要用到反射去完成各种操作,好像体现不出注解的优势。
- 源码时期注解提示安全以及报错
- 编译时期注解,在编译阶段可以为我们生成代码,实现各种功能例如早期的 JakeWharton 大神Butterknife ,生成代码实现findviewById操作。以及Goodle HIlt 依赖注入。
那我们如何实现编译时注解呢。Javapoet为此做出了很大贡献。 https://github.com/square/javapoet 具体使用方法请搜索引擎,输入Javapoet教学 [手动狗头]
对于第三个问题,注解的生命周期,编译时注解和运行时注解区别
你都看到这了,应该有个大致答案了。
- 首先声明周期就不一样,一个在编译期class字节码阶段。一个被ClassLoder 从class字节码到内存。
- 编译时期注解一般会生成各种代码实现各种操作。运行时期注解,程序运行时期获取到你使用的各种注解,例如retrofit 运行时注解 加 动态代理完成网络请求的各种操作。
- //todo以后想到了继续补充