测试页面2
注解反射和动态代理
注解
注解的定义
注解的作用或者意义是什么?
注解本身没有任何含义,单独的注解就是一种或注解,它需要结合其他如反射、插桩等技术才有意义
Java注解(Annotation)又称Java标注,是JDK1.5引入的一种注解机制。是元数据的一种形式,提供关于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有用直接影响
自定义注解
使用@interface自定义注解
1 | //保留时 |
元注解
在定义注解时,注解类也能够使用其他的注解声明。在JDK1.5中提供了**用来对注解类进行注解的注解类,我们称之为meta-annotation(元注解)**。
声明的注解允许作用于哪些节点使用**@Target**声明:
TYPEFIELDMETHODPARAMETERCONSTRUCTORLOCAL_VARIABLEANNOTATION_TYPEPACKAGETYPE_PARAMETERTYPE_USEMODULERECORD_COMPONENT
保留级别有**@Retention**声明。其中保留级别如下
RetentionPolicy.SOURCE标记的注解仅保留在源级别,并被编译器忽略
RetentionPolicy.CLASS标记的注解在编译时由编译器保留,但Java虚拟机(JVM)会忽略
Retention.RUNTIME标记的注解由JVM保留,因此运行时环境可以使用它
SOURCE<CLASS<ROUTIME, 即CLASS包含了SOURCE, ROUTIME包含SOURCE、CLASS>
注意:
CLASS级别的注解保留到class,会被jvm抛弃,但在Android中运行的是dex、class级别注解会被抛弃,runtime注解也和Java一样,被虚拟机识别解析
实际应用场景
注解的应用场景
根据注解的保留级别不同,对注解的使用自然存在不同场景。由注解的三个不同保留级别可知,注解作用于:源码、字节码、运行时,以下是一些案例
| 级别 | 技术 | 使用场景 |
|---|---|---|
| 源码 | APT | 在编译期能够获取注解与注解声明的类包括类中所有成员的信息,一般用于生成额外的辅助类 |
| 字节码 | 字节码增强 | 在编译出Class后,通过修改Class数据以实现修改代码逻辑目的。对于是否需要修改的区分或者修改为不同逻辑的判断可以使用注解 |
| 运行时 | 反射 | 在程序运行期间,通过反射技术动态或者注解与其元素,从而完成不同的逻辑判定 |
限制参数取值范围
使用枚举的缺点:每个值都会编译成一个对象(对象占用内存运算:对象头 + 实际数据 + 对齐),更耗内存
自定义注解:又可以节省内存,又可以限制入参
APT注解处理器
(annotation processor tools)注解处理器
注意:
常用的第三方库中应用:glide arouter butterknife ann hilt…
-processor <class1>[,<class2>,<class3>...]要运行的注释处理程序的名称; 绕过默认的搜索进程
javac -procerssor apt.jar xxx.javaapt其实就可以看成javac的一个插件
-processor <class1>[,<class2>,<class3>...]要运行的注释处理程序的名称; 绕过默认的搜索进程
apt:不会打包进入APK,只会在编译时参与编译
自己实现APT
创建模块
添加依赖
注意:创建的是Java模块,而不是Android模块,Android模块不能给Java模块依赖
创建注解类
在annotation模块中创建注解:
1 |
|
创建实现类
在compile模块下创建注解处理器的实现类
1 | public class TestProcessor extends AbstractProcessor { |
在process方法,由javac调用,写什么代码就做什么事情
注册
在compile模块的main文件夹下创建:resource -> META-INF -> services -> javax.annotation.prrocessing.Processor中进行注册
写上APT实现类
1
com.wuliner.compile.TestProcessor
配置允许APT处理的注解
以下两种方法选一种就可以了
- 重写该实现类的
getSuppotedAnnotationTypes方法
1 | /** |
使用
@SupportedAnnotationTypes()注解括号中写注解类的全类名,如
@SupportedAnnotationTypes({"com.wuliner.annotation.MyClass"})
1 |
|
引入APT所在模块
在build.gradle
Java中使用
annotationProcessor,如annotationProcessor project('compiler')kolin中使用kapt
注意:apt不会打包进入APK,只会在编译时参与编译
APT的使用
在类名前添加注解,如
@MyClass在Processor实现类中
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
public class CwProcessor extends AbstractProcessor {
/**
* javac 调用此方法
* @param set
* @param roundEnvironment
* @return
*/
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//写什么代码就做什么事情
//process:javac编译时的一个回调
Messager messager = processingEnv.getMessager();
//打印日志
messager.printMessage(Diagnostic.Kind.NOTE, "=========>");
return false;
}
/**
* 允许此注解处理器处理的注解
* @return
*/
// @Override
// public Set<String> getSupportedAnnotationTypes() {
// return super.getSupportedAnnotationTypes();
// }
}注:
使用Message打印日志:
1
2
3Messager messager = processingEnv.getMessager();
//打印日志
messager.printMessage(Diagnostic.Kind.NOTE, "=========>");引入注解模块
使用
annotationProcessor引入注解处理器模块, 若使用kotlin,则使用kapt1
annotationProcessor(project(":compile"))
apt不会打包进入apk,只会在编译时参与编译
在Build窗口中查看日志
为什么APT中process方法会执行多次?
Java将源码编译成class文件的过程:
java编译过程:词法分析、语法分析、填充符号表、注解处理器处理注解、语义分析、解语法糖、生成字节码。
注解处理器可以增删改抽象语法树的任意元素。执行到注解处理器,都会重新执行词法分析、语法分析、填充符号表步,直到注解处理器不再对语法树进行修改为止,每一次的循环过程都称为一次Round。
process第一个参数set集合是要处理的注解集合,如果这个集合为null了,就不需要处理了。写代码就这样去做。
1
2
3if(!set.isEmpty()){
//…执行处理
}或者通过
roundEnvironment.processingOver()将round结束
字节码增强(插桩)
字节码增强技术相当于是一把打开运行时JVM的钥匙,利用它可以动态地对运行中的程序做修改,也可以跟踪JVM运行中程序的状态。
此外,我们平时使用的动态代理、AOP也于字节码增强密切相关,它们实质上还是利用各种手段生成或修改符合规范的字节码文件。
综上所述,掌握字节码增强后可以高效的定位并快速修复一些棘手的问题(如线上性能问题,方法出现不可控的出入参需要紧急加日志等问题),也可以在开发中减少冗余代码,大大提高开发效率
反射
反射实现findViewById
通过反射获取类的成员
1 | public static void injectView(Activity activity) { |
注意:
Filed获得自己+父类的成员(不包括private,只能是public)
DecleredFile只能获得自己的成员(不包括父类,所有作用域)
可以通过getSuperClass()获取父类的class文件
创建注解
创建注解用来标识,用于筛选
1 |
|
在需要标识的属性上使用注解
这一传入了TextView对应的id,用来实现findViewById的功能
1 |
|
筛选Field
1 | public class InjectUtils { |
先通过
Filed的isAnnotationPresent()方法,传入自定义注解的class文件用来判断属性是否被InjectView注解声明再通过
getAnnotation()方法,传入注解的class文件,获得对应的注解类再通过这个类获取对应的属性,获得注解中设置的id后,获取对应的View
然后通过反射设置属性的值
先用
filed.setAccessible(true)设置访问权限,允许操作private的属性通过
filed.set(activity, view),将TextView值设置为对应的View
调用方法
通过反射获取泛型的真实类型
Type类
TypeVariable泛型类型变量。可以泛型上下限等信息
ParameterType具体的泛型类型,可以获得元数据中泛型签名类型(泛型真实类型)
GenericArrayType当需要描述的类型是泛型类的数据时,比如List[],Map[],此接口会作为Type的实现
WildcardType通配符泛型,获得上下限信息
1 | Type type = new TypeRefrence<Response<Data>>().getType(); //不行正常运行 |
有花括号,代表是匿名内部类,没有添加的话就代表一个对象





