看完这篇文章你会了解到什么
- 什么是注解
- 注解有什么用,咱们为什么要用注
- 注解的生命周期,编译时注解和运转时注解区别
引用他人对注解的解说,注解能够了解成标签。
什么是注解
在代码中咱们最常见的注解应该是他 @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) 那么也能够称他为元注解。
注解的效果
@Retention(RetentionPolicy.SOURCE)
源代码时期的注解,仅存在于.java文件中
- 用于代码检查例如 @NonNull /@Nullable ,资源引用约束例如 @DrawableRes ,@StringRes,提醒过错,过期例如 @Deprecated
- 在编译期间处理,.java文件编译生成class文件时期,经过开发者注册的注解处理器(AnnotationProcessor)对注解进行处理,运用JavaPoet生成模板代码,提高开发功率。
@Retention(RetentionPolicy.RUNTIME)
- 注解在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为此做出了很大奉献。 github.com/square/java…
- 调用者运用注解的时分,代码简练
注解的生命周期,编译时注解和运转时注解区别
- 首先声明周期就不一样,一个在编译期class字节码阶段。一个被ClassLoder 从class字节码到内存。
- 编译时期注解一般会生成各种代码完结各种操作。运转时期注解,程序运转时期获取到你运用的各种注解,例如retrofit 运转时注解加动态署理完结网络恳求的各种操作。
- 运转时注解一般会经过反射的操作,影响功能。