前言:
当我们需要对一段代码进行拦截修改的时候,我们有很多方案,最常见的是通过反射。但是反射是在运行时的工作,对性能有很大的影响,所以提出了APT技术,该技术可以在编译期对代码进行拦截,并进行修改,生成新的可执行的类。
注解反射
当我们使用注解的时候,大部分都是配合反射一起使用,通过反射可以轻易获取到注解上的类或者属性,然后在对该属性进行代码修改。现在让我们通过反射注解的方式对组件进行初始化工作。
//通过注解生成View;
private void getAllAnnotationView() {
//获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields){
try {
//获取该元素上的所有注解,包含从父类继承
if(field.getAnnotations() != null){
//确定注解类型 给@getViewTo 赋值
if(field.isAnnotationPresent(GetViewTo.class)){
//允许修改反射属性
field.setAccessible(true);
GetViewTo getViewTo = field.getAnnotation(GetViewTo.class);
//findViewById将注解的id,找到View注入成员变量中
field.set(this, findViewById(getViewTo.value()));
}
//给@Title 赋值
if(field.isAnnotationPresent(Title.class)){
//允许修改反射属性
field.setAccessible(true);
Title abc = field.getAnnotation(Title.class);
//findViewById将注解的id,找到View注入成员变量中
field.set(this, abc.value());
}
}
}catch (Exception e){
}
}
}
以上的代码是对添加了 @GetViewTo 属性的变量进行赋值。然后我们可以在项目中直接使用:
@GetViewTo(R.id.btn)
private Button btn;
这样我们通过反射的方式对组件进行初始化就完成了!!
APT的使用
APT是什么意思,他其实是AOP的一种实现方案,aop就是面向切面的开发,而安卓aop三剑客分别是:apt,aspectJ,javassist;
分类 | 说明 | 案例 |
---|---|---|
APT(注解处理器) | 定义编译期的注解,再通过继承Proccesor实现代码生成逻辑,实现了编译期生成代码的逻辑。(compile任务前,修改java文件) | DataBinding,Dagger2, ButterKnife, EventBus3 |
AspectJ | Jake Wharton,支持编译期和加载时代码注入 ( class阶段,修改java代码) | 主要用于性能监控,日志埋点等 |
Javassist | 编译期间修改class文件,与之相似的ASM,在class文件被转化为dex文件之前去修改(修改的.class) | 热更新,ARouter |
ASM (字节码修改器) | ASM是一种基于java字节码层面的代码分析和修改工具,ASM的目标是生成,转换和分析已编译的java class文件,可使用ASM工具读/写/转换JVM指令集。通俗点讲就是来处理javac编译之后的class文件 (修改的.class) |
这四种aop思想,目前我们先只说APT技术,同样是做组件的初始化工作:
1)首先我们创建一个为 apt-annotation 的java lib moudle(此处是java lib) 插件
在apt-annotation中创建一个注解类 BindView
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
2) 创建一个为 apt-compiler 的java lib moudle(此处是java lib) 插件
我们创建一个BindViewProcessor类
/**
* Created by malei on 2020/9/14
* Describe:
*/
@SupportedSourceVersion(SourceVersion.RELEASE_7) //java的支持版本
@SupportedAnnotationTypes("com.example.apt_annotation.BindView") //标注注解处理器支持的注解类型
public class BindViewProcessor extends AbstractProcessor {
private Filer mFilerUtils; // 文件管理工具类
private Types mTypesUtils; // 类型处理工具类
private Elements mElementsUtils; // Element处理工具类
private Map<TypeElement, Set<ViewInfo>> mToBindMap = new HashMap<>(); //用于记录需要绑定的View的名称和对应的id
//要绑定的View的信息载体
class ViewInfo {
String viewName; //view的变量名
int id; //xml中的id
public ViewInfo(String viewName, int id) {
this.viewName = viewName;
this.id = id;
}
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mFilerUtils = processingEnv.getFiler();
mTypesUtils = processingEnv.getTypeUtils();
mElementsUtils = processingEnv.getElementUtils();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set != null && set.size() != 0) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);//获得被BindView注解标记的element
categories(elements);//对不同的Activity进行分类
//对不同的Activity生成不同的帮助类
for (TypeElement typeElement : mToBindMap.keySet()) {
//获取要生成的帮助类中的所有代码
String code = generateCode(typeElement);
//构建要生成的帮助类的类名
String helperClassName = typeElement.getQualifiedName() + "$$Autobind";
//输出帮助类的java文件,在这个例子中就是MainActivity$$Autobind.java文件
//输出的文件在build->source->apt->目录下
try {
JavaFileObject jfo = mFilerUtils.createSourceFile(helperClassName, typeElement);
Writer writer = jfo.openWriter();
writer.write(code);
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
return false;
}
private String generateCode(TypeElement typeElement) {
//获取要绑定的View所在类的名称
String rawClassName = typeElement.getSimpleName().toString();
//获取要绑定的View所在类的包名
String packageName = ((PackageElement) mElementsUtils.getPackageOf(typeElement)).getQualifiedName().toString();
//要生成的帮助类的名称
String helperClassName = rawClassName + "$$Autobind";
StringBuilder builder = new StringBuilder();
builder.append("package ").append(packageName).append(";\n"); //构建定义包的代码
builder.append("import com.example.apt_api.IBindHelper;\n\n"); //构建import类的代码
builder.append("public class ").append(helperClassName).append(" implements ").append("IBindHelper"); //构建定义帮助类的代码
builder.append(" {\n"); //代码格式,可以忽略
builder.append("\t@Override\n"); //声明这个方法为重写IBindHelper中的方法
builder.append("\tpublic void inject(" + "Object" + " target ) {\n"); //构建方法的代码
for (ViewInfo viewInfo : mToBindMap.get(typeElement)) { //遍历每一个需要绑定的view
builder.append("\t\t"); //代码格式,可以忽略
builder.append(rawClassName + " substitute = " + "(" + rawClassName + ")" + "target;\n"); //强制类型转换
builder.append("\t\t"); //代码格式,可以忽略
builder.append("substitute." + viewInfo.viewName).append(" = "); //构建赋值表达式
builder.append("substitute.findViewById(" + viewInfo.id + ");\n"); //构建赋值表达式
}
builder.append("\t}\n"); //代码格式,可以忽略
builder.append('\n'); //代码格式,可以忽略
builder.append("}\n"); //代码格式,可以忽略
return builder.toString();
}
private void categories(Set<? extends Element> elements) {
//遍历每一个element
for (Element element : elements){
//被@BindView标注的应当是变量,这里简单的强制类型转换
VariableElement variableElement = (VariableElement) element;
//返回封装该元素的类
TypeElement enclosingElement = (TypeElement) variableElement.getEnclosingElement();
//views储存着一个Activity中将要绑定的view的信息
Set<ViewInfo> views = mToBindMap.get(enclosingElement);
//如果views不存在就new一个
if (views == null) {
views = new HashSet<>();
mToBindMap.put(enclosingElement, views);
}
//获取到一个变量的注解
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
//取出注解中的value值,这个值就是这个view要绑定的xml中的id
int id = bindAnnotation.value();
//把要绑定的View的信息存进views中
views.add(new ViewInfo(variableElement.getSimpleName().toString(), id));
}
}
}
build.gradle中需要添加依赖:
implementation project(path: ':apt-annotation')
3)创建一个android lib 包,提供向外支持:
创建接口:
public interface IBindHelper {
void inject(Object target);
}
创建工具类:
public class AutoBind {
private static volatile AutoBind instance = null;
public AutoBind() {
}
public static AutoBind getInstance() {
if(instance == null) {
synchronized (AutoBind.class) {
if (instance == null) {
instance = new AutoBind();
}
}
}
return instance;
}
public void inject(Object target) {
String className = target.getClass().getCanonicalName();
String helperName = className + "$$Autobind";
try {
IBindHelper helper = (IBindHelper) (Class.forName(helperName).getConstructor().newInstance());
helper.inject(target);
} catch (Exception e) {
e.printStackTrace();
}
}
}
gradle.buidle需要依赖:
api project(':apt-annotation')
4)最后我们在app gradle中需要依赖如下:
annotationProcessor project(':apt-compiler')
implementation project(':apt-api')